性能优化——Android面试《九》
作者:互联网
1 性能优化分析工具学习
System Trace
Hierarchy Viewer
TraceView
2 布局优化
布局优化相对比较容易,优化可以先从布局来展开。使用 Hierarchy Viewer 和开发者模
式中关于布局绘制的选项,可以查到一些问题然后进行修改。
布局嵌套过深:层级嵌套过深的话,深度遍历各个节点会非常消耗时间,这也是布局优化余地最大的一个点了。很多过深的层级是不必要的。如果布局真的很复杂,不深度嵌套没法实现想要的效果。可以尝试约束布局 Constraintlayout 。
使用合适的布局: 三种常见的 ViewGroup 的绘制速度:FrameLayout >LinerLayout > RelativeLayout。当然,如果用 RelativeLayout 可以避免布局嵌套的话是值得的。可以根据这些去决定选用什么样的布局。
使用 include 标签: 在布局文件中,标签可以指定插入一段布局文件到当前布局。这样的话既提高了布局复用,也减少了我们的代码书写。另外,merge标签可以和include的标签一起使用从而减少布局层级。
ViewStub 延时加载: 有些布局,比如网络出错的布局,没必要在所有时候都加载出来。使用 ViewStub 可以实现按需加载。ViewStub 本身没有宽高,加载起来几乎不消耗什么资源。当对他setVisibility(View.VISIBLE)的时候会调用它引用的真实布局填充到当前位置,从而实现了延时加载,节省了正常加载的时间。
移除 Activity 默认背景 只要我们不需要 Activity 的默认背景,就可以移除掉,以减少 Activity 启动时的渲染时间,提升启动效率。移动方法见下:
<style name="Theme.NoBackground" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@null</item>
</style>
3 线程优化
线程调度原理
- 任意时刻,只有一个线程占用CPU,处于运行状态
- 多线程并发,轮流获取CPU使用权
- JVM 负责线程的调度:按照特定的机制分配CPU使用权
线程的创建和销毁会带来比较大的性能开销。因此线程优化也很有必要。查看项目中是否存在随意 new thread,线程缺乏管理的情况。使用 AsyncTask 或者线程池对线程进行管理,可以提升 APP 的性能。另外,我比较推荐使用 Rxjava 来实现异步操作,既方便又优雅。
4 网络优化
-
连接复用:节省连接建立时间,如开启 keep-alive。于Android来说默认情况下HttpURLConnection和HttpClient都开启了keep-alive。
-
请求合并:即将多个请求合并为一个进行请求,比较常见的就是网页中的CSS Image Sprites。同一个页面数据尽量放到一个接口中去处理。
-
减少请求数据的大小:对于post请求,body可以做gzip压缩的,header也可以做数据压缩(不过只支持http 2.0)。 返回数据的body也可以做gzip压缩,body数据体积可以缩小到原来的30%左右(也可以考虑压缩返回的json数据的key数据的体积,尤其是针对返回数据格式变化不大的情况,支付宝聊天返回的数据用到了)。
-
根据用户的当前的网络质量来判断下载什么质量的图片(电商用的比较多)。
-
使用HttpDNS优化DNS:DNS存在解析慢和DNS劫持等问题,DNS 不仅支持 UDP,它还支持 TCP,但是大部分标准的 DNS 都是基于 UDP 与 DNS 服务器的 53 端口进行交互。HTTPDNS 则不同,顾名思义它是利用 HTTP 协议与 DNS 服务器的 80 端口进行交互。不走传统的 DNS 解析,从而绕过运营商的 LocalDNS 服务器,有效的防止了域名劫持,提高域名解析的效率。
-
大量数据的加载采用分页的方式;上传图片时,在必要的时候压缩图片。
5 Apk 包体优化
Apk 组成结构:
-
整体优化
分离应用的独立模块,以插件的形式加载
解压APK,重新用 7zip 进行压缩
用 apksigner 签名工具 替代 java 提供的 jarsigner 签名工具 -
资源优化
可以只用一套资源图片,一般采用 xhdpi 下的资源图片
通过扫描文件的 MD5 值,找出名字不同,内容相同的图片并删除
通过 Lint 工具扫描工程资源,移除无用资源
通过 Gradle 参数配置 shrinkResources=true(资源缩减)
对 png 图片压缩;图片资源考虑采用 WebP 格式
避免使用帧动画,可使用 Lottie 动画库
优先考虑能否用 shape 代码、.9 图、svg 矢量图、VectorDrawable 类来替换传统的图片 -
代码优化
启用混淆以移除无用代码(minifyEnabled true )
开启代码压缩( minifyEnabled true //打开代码压缩)
剔除 R 文件
用注解替代枚举 -
.arsc文件优化
移除未使用的备用资源来优化 .arsc 文件
android {
defaultConfig {
...
resConfigs "zh", "zh_CN", "zh_HK", "en"
}
}
- so库打包优化
只提供对主流架构的支持,比如 arm,对于 mips 和 x86 架构可以考虑不提供支持
android {
defaultConfig {
...
ndk {
abiFilters "armeabi-v7a"
}
}
}
6 内存优化(非常重要)
首先需要了解ava 内存回收机制——GC机制,Java 对象引用方式 —— 强引用、软引用、弱引用和虚引用。
基础知识:
6.1 Android 内存管理机制
1.针对进程的内存策略
进程的内存分配策略为:由 ActivityManagerService 集中管理所有进程的内存分配。
进程的内存回收策略为:首先Application Framework 决定回收的类型,当进程的内存空间紧张时会按照进程优先级由低到高的顺序自动回收进程及内存。
Android将进程分为5个优先级,具体如下:
真正执行回收进程的操作的是 Linux 内核。
梳理一下整体流程:
-
ActivityManagerService 对所有进程进行评分。
-
更新评分到 Linux 内核。
-
由 Linux 内核完成真正的内存回收。
2.针对对象、变量的内存策略
Android的对于对象、变量的内存策略同 Java
内存管理 = 对象 / 变量的内存分配 + 内存释放
-
内存分配策略
对象,变量的内存分配有系统负责,共有三种:静态分配、栈式分配、堆式分配,分别面向静态变量,动态变量和对象实例。 -
内存释放策略
对象,变量的内存释放由Java的垃圾回收器GC负责。
内存分配注意:(非常重要)
-
成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体)—因为他们属于类,类对象最终还是要被new出来的。
-
局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。—–因为他们属于方法当中的变量,生命周期会随着方法一起结束。
public class Sample {
// 该类的实例对象的成员变量s1、mSample1及指向的对象都存放在堆内存中
int s1 = 0;
Sample mSample1 = new Sample();
// 方法中的局部变量s2、mSample2存放在 栈内存
// 变量mSample2所指向的对象实例存放在 堆内存
public void method() {
int s2 = 0;
Sample mSample2 = new Sample();
}
}
// 变量mSample3的引用存放在栈内存中
// 变量mSample3所指向的对象实例存放在堆内存
// 该实例的成员变量s1、mSample1也存放在堆内存中
Sample mSample3 = new Sample();
6.2 Android的内存泄漏、内存溢出、内存抖动概念
-
内存泄露
即 ML (Memory Leak),指 程序在申请内存后,当该内存不需再使用但却无法被释放,归还给 程序的现象。对应用程序的影响:容易使得应用程序发生内存溢出,即OOM(out of Memory)
发生内存泄露的本质原因:
本质原因:持有引用者的生命周期>被引用者的生命周期
解释:本该回收的对象(该对象已经不再被使用),由于某些原因(如被另一个正在使用的对象引用)不能被回收。 -
内存抖动
优化方案
尽量避免频繁创建大量、临时的小对象
6.3 如何避免OOM(内存泄漏优化)。
1.减小对象的内存占用
1)使用更加轻量的数据结构
例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构,下图演示了HashMap的简要工作原理,相比起Android系统专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效在于他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱。
可以参考Android性能优化典范 - 第3季
2)避免在Android里面使用Enum
3)减小Bitmap对象的内存占用
Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用是很重要的,通常来说有下面2个措施:
inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。
4)使用更小的图片
在设计给到资源图片的时候,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用一张更小的图片。尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的InflationException。
2.内存对象的重复利用
1)复用系统自带的资源
Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。这样做不仅仅可以减少应用程序的自身负重,减小APK的大小,另外还可以一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去。
2)注意在ListView/GridView等出现大量重复子组件的视图里面对ConvertView的复用
3)Bitmap对象的复用
在ListView与GridView等显示大量图片的控件里面需要使用LRU的机制来缓存处理好的Bitmap。
4)避免在onDraw方法里面执行对象的创建
类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。
5)StringBuilder
在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
3.避免对象的内存泄露
内存对象的泄漏,会导致一些不再使用的对象无法及时释放,这样一方面占用了宝贵的内存空间,很容易导致后续需要分配内存的时候,空闲空间不足而出现OOM。显然,这还使得每级Generation的内存区域可用空间变小,gc就会更容易被触发,容易出现内存抖动,从而引起性能问题。(LeakCanary开源控件,可以很好的帮助我们发现内存泄露的情况)
1)注意Activity的泄漏
通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,我们需要特别注意以下两种情况导致的Activity泄漏:
内部类引用导致Activity的泄漏:非静态(匿名)内部类会默认持有外部类引用。
-
Handler导致的Activity泄漏(最经典的场景),如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象(removeCallbacksAndMessages(null)–同时清空消息队列 ,结束Handler生命周期)。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用 静态内部类 + 弱引用的方式。
-
线程造成的内存泄漏
在 Activity 内定义了一个匿名的 AsyncTask 对象,就有可能发生内存泄漏。如果 Activity 被销毁之后 AsyncTask 仍然在执行,那就会阻止垃圾回收器回收Activity 对象,进而导致内存泄漏,直到执行结束才能回收 Activity。
同样的,使用 Thread 和 TimerTask 也可能导致 Activity 泄漏。只要它们是通过匿名类创建的,尽管它们在单独的线程被执行,它们也会持有对 Activity 的强引用,进而导致内存泄漏。
总结:
内部类引起的泄漏不仅仅会发生在Activity上,其他任何内部类出现的地方,都需要特别留意!我们可以考虑尽量使用static类型的内部类,同时使用WeakReference的机制来避免因为互相引用而出现的泄露。
2)考虑使用Application Context而不是Activity Context
对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。(Activity Context被传递到其他实例中,这可能导致Activity自身被引用而发生泄漏)
3)注意临时Bitmap对象的及时回收
4)注意监听器的注销
在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的listener,需要记得及时remove这个listener。
5)注意WebView的泄漏
Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外不同的厂商出货的ROM里面WebView也存在着很大的差异。更严重的是标准的WebView存在内存泄露的问题,看这里WebView causes memory leak - leaks the parent Activity。所以通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
6)注意Cursor对象是否及时关闭
在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。
6.4 常用的内存检查工具。
(1)Memory Monitor 工具:
它是Android Studio自带的一个内存监视工具,它可以很好地帮助我们进行内存实时分析。通过点击Android Studio右下角的Memory Monitor标签,打开工具可以看见较浅蓝色代表free的内存,而深色的部分代表使用的内存从内存变换的走势图变换,可以判断关于内存的使用状态,例如当内存持续增高时,可能发生内存泄漏;当内存突然减少时,可能发生GC等,如下图所示。
(2)LeakCanary工具:
LeakCanary是Square公司基于MAT开发的一款监控Android内存泄漏的开源框架。其工作的原理是: 监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收(在ReferenceQueue中说明可以被回收,不存在泄漏;否则,可能存在泄漏,LeakCanary是执行一遍GC,若还未在ReferenceQueue中,就会认定为泄漏)。
如果Activity被认定为泄露了,就抓取内存dump文件(Debug.dumpHprofData);之后通过HeapAnalyzerService.runAnalysis进行分析内存文件分析;接着通过HeapAnalyzer (checkForLeak—findLeakingReference—findLeakTrace)来进行内存泄漏分析。最后通过DisplayLeakService进行内存泄漏的展示。
(3)Android Lint 工具:
Android Lint Tool 是Android Sutido种集成的一个Android代码提示工具,它可以给你布局、代码提供非常强大的帮助。硬编码会提示以级别警告,例如:在布局文件中写了三层冗余的LinearLayout布局、直接在TextView中写要显示的文字、字体大小使用dp而不是sp为单位,就会在编辑器右边看到提示。
7 电量优化
Battery Historian(电量使用记录分析工具)
Battery Historian是Android 5.0开始引入的新API。通过下面的指令,可以得到设备上的电量消耗信息。
电量优化的一些建议:
1.充电时执行任务
if (!checkForPower()) {
Toast.makeText(view.getContext(), "当前非充电状态", Toast.LENGTH_SHORT).show();
return;
}
/**
* 是否充电
* AC --- 交流电
* USB
* WireLess -- 无线充电
*
* @return
*/
private boolean checkForPower() {
IntentFilter mIntentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent intent = registerReceiver(null, mIntentFilter);
int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean isUsb = plugged == BatteryManager.BATTERY_PLUGGED_USB;
boolean isAc = plugged == BatteryManager.BATTERY_PLUGGED_AC;
boolean isWireless = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
//api >= 17
isWireless = plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
return (isUsb || isAc || isWireless);
}
2.连接Wifi后执行任务
我们知道wifi网络传输的电量消耗要比移动网络少很多,应该尽量减少移动网络下的数据传输,多在WiFi环境下传输数据,所以我们可以把一些不需要实时性的任务留到连接wifi后在执行
3.wake_lock
系统为了节省电量,CPU在没有任务忙的时候就会自动进入休眠。有任务需要唤醒CPU高效执行的时候,就会给CPU加wake_lock锁。但是使用wake_lock结束时需要释放锁,如果忘记释放,会使得CPU一直执行消耗电量,所以推荐使用带超时的wake lock或者WakefulBroadcastReceiver
wakeLock.acquire(timeout);
4.大量高频次的CPU唤醒及操作集中处理
5.定位
定位完成,及时关闭
如果需要实时定位,减少更新频率
根据实际情况,选择gsp定位还是网络定位,降低电量消耗
6.网络优化可以促进电量优化
标签:泄漏,对象,使用,面试,内存,Activity,Android,优化 来源: https://blog.csdn.net/qq_34512207/article/details/123110031