【转】Android 性能优化之内存检测、卡顿优化、耗电优化、A
原文(https://blog.csdn.net/csdn_aiyang/article/details/74989318)
导语
自2008年智能时代开始,Android操作系统一路高歌,10年智能机发展之路,如今 Android 9.0 代号P 都发布了,Android系统性能已经非常流畅了。但是,到了各大厂商手里,改源码自定系统,使得Android原生系统变得鱼龙混杂。另外,到了不同层次的开发工程师手里,因为开发技术的水平参差不齐,使得应用安装到配置极好的手机上,打开应用依然存在卡顿现象。最后,随着产品内容迭代,功能需求越来越复杂,UI页面也越来越丰富,也成为流畅运行的一种阻碍。性能优化提升用户体验成为开发工程师的使命,那么该如何完成这样一个使命呢?首先,我们需要借助性能检测工具来进行分析。
一、性能检测工具
(一)网易开源的Emmagee , https://github.com/NetEase/Emmagee
Emmagee(机关枪)是网易杭州研究院QA团队开发的一个简单易上手的Android性能监测小工具,主要用于监控单个App的CPU,内存,流量,启动耗时,电量,电流等性能状态的变化,且用户可自定义配置监控的频率以及性能的实时显示,并最终生成一份性能统计文件。
(1)优势
- 开源;
- 无需root权限;
- 支持2.2以及以上版本。但由于Google 安全限制,在7.0版本手机已不支持。
- 实时展示数据;
- 操作简单,支持自定义采集频率;
- CSV格式保存数据,方便为电脑使用Excel表格打开;
(2)检测案例步骤
1、在GitHub下载安装包Apk
2、运行Emmagee.app。可以设置采集频率。
3、点击APP"测试报告"查看,也可以配置邮件下发CSV文件。而我选择盗取文件到电脑上用Excel表格看。
(本地内部存储Emmagee目录”storage\sdcard0\Emmagee\某日期时间_APP包名.csv”的文件,即为监控数据)
$ adb shell //回车$ cd storage/sdcard0/Emmagee/ //回车$ ls //回车 ,结果如下: 20180605154517_com.daojia.csv20180605160747_me.ele.csv
*手机导出的csv文件出现乱码,原因是由于导出的CSV文件编码为UTF-8 。解决办法:(Windows)使用记事本打开另存为“ANSI编码”的CSV格式文件即可。(Mac OS)文本编辑打开重新保存即可。
上面分别是对到家点餐APP、饿了么点餐APP的CSV检测数据。移动文件到电脑Excel打开查看:
(二)腾讯开源的GT,https://github.com/TencentOpen/GT
(1)什么是GT?
GT是一款同时支持安卓手机端和IOS手机端性能测试工具。
GT(随身调),即仅凭一部手机,无需连接电脑,就可以对APP进行快速的性能测试(CPU、内存、流量、电量、帧率/流畅度等)、 开发日志的查看、Crash日志查看、网络数据包的抓取、APP内部参数的调试、真机代码耗时统计等等;更重要的是,您可以在任意真实场所、 任何时候做如上的系列事情,这就是“随身调”。
如果您觉得GT提供的功能还不够满足您的需要, 您还可以利用GT提供的基础API自行开发有特殊功能的GT插件(仅iOS版支持), 帮助您解决更加复杂的APP调试、测试问题。
(2)如何使用?
GT支持iOS和Android两个手机平台,其中: Android版由一个可直接安装的GT控制台APP 和GT SDK插件扩展检测。GT控制台可以独立安装使用,SDK需嵌入被调测的应用、并利用GT控制台进行信息展示和参数修改。 iOS版是一个Framework包,必须嵌入APP工程,编译出带GT的APP才能使用;iPhone和iPad应用都能支持。
以下为Android版GT控制台APP 检测基础功能案例,使用步骤:
1、应用宝下载GT.APP,安装运行GT。
2、选择应用
3、选择关注的监控数据
(三)科大讯飞的iTest,http://www.liqucn.com/rj/410791.shtml
iTest官方说该工具由科大讯飞测试技术部开发,在多个项目中成功应用,具有较高的准确性和稳定性。它填补了手机端自动化测试的空白,以实用高效为宗旨,记录特定应用的性能消耗情况,包括cpu、内存、流量、电量等信息。我们知道科大讯飞在业界是很有名气的是讯飞语音。但是关于这款检测工具,遗憾本人寻遍全网,只能查找下载安装Apk的地址,查阅其他的相关介绍和资料太少。
(四)Google的开源Battery Historian,https://github.com/google/battery-historian
Battery Historian是一款用于检测与电池有关的信息和事件的工具。运行在Android5.0Lollipop(APIlevel21)及其之后。它会生成一张具有时间坐标的图纸,用户可以查看各种事件耗电时间。官方文档是英文版,网上可以搜到大量中文资料。使用需要先配置环境,包括go/java/python/git。最后git命令下载 Battery Historian,并使用命令操作 adb bugreport 得到 bugreport.txt(~16m)。使用 Battery Historian打开会生成图表,最终效果图如下。
纵坐标 | 解释 |
---|---|
CPU runing | cpu运行的状态,是否被唤醒 |
Kernel only uptime | 只有内核运行时间 |
Activity Manager Proc | 活跃的用户进程 |
Mobile network type | 网络类型 |
Mobile radio active | 移动蜂窝信号 BP侧耗电 |
Crashes(logcat) | 某个时间点出现crash的应用 |
Doze | 是否进入doze模式 |
Device active | 和Doze相反 |
JobScheduler | 异步作业调度 |
SyncManager | 同步操作 |
Temp White List | 电量优化白名单 |
Phone call | 是否打电话 |
GPS | 是否使用GPS |
Network connectivity | 网络连接状态(wifi、mobile是否连接) |
Mobile signal strength | 移动信号强度(great\good\moderate\poor) |
Wifi scan | 是否在扫描wifi信号 |
Wifi supplicant | 是否有wifi请求 |
Wifi radio | 是否正在通过wifi传输数据 |
Wifi signal strength | wifi信号强度(great\good\moderate\poor) |
Wifi running | wifi组件是否在工作(未传输数据) |
Wifi on | 同上 |
Audio | 音频是否开启 |
Camera | 相机是否在工作 |
Video | 是否在播放视频 |
Foreground process | 前台进程 |
Package install | 是否在进行包安装 |
Package active | 包管理在工作 |
Battery level | 电池当前电量 |
Temperature | 电池温度 |
Charging on | 在充电 |
Logcat misc | 是否在导出日志 |
(五)Android 自带 Lint 工具
Android Lint Tool 是 Android Sutido 集成的一个代码规范提示工具,使用[ Lint 检测代码、布局文件、去除多余资源](https://blog.csdn.net/u011240877/article/details/54141714)。
-
硬编码会提示以级别警告,例如:在布局文件中写了三层冗余的LinearLayout布局、直接在TextView中写要显示的文字、字体大小使用dp而不是sp为单位,就会在编辑器右边看到提示。
-
使用Android Studio的lint可以清除无用的资源文件(点击菜单栏的Analyze -> Run Inspection by Name, 输入unused resource)。
当然两个都是一个简单的举例,Lint的功能非常强大,大家应该养成写完代码查看Lint的习惯,这不仅让你及时发现代码种隐藏的一些问题,更能让你养成良好的代码风格,要知道,这些Lint提示可都是Google大牛们汗水合智慧的结晶。
二、性能优化
说完了性能检查工具,接下来该思考如何进行制定系统化的优化改进方案。本着人道主义,首先从用户体验的角度去思考,置身处地得把自己当做用户去玩一款应用时候会在意些什么呢?假如饭点将至,需要打开订餐APP进行点餐,首先一定不希望,在浏览商家和菜品列表内容很丰富的时候遇到卡顿现象,然后千挑万选后在期待美食将至的心情下准备下单,突然遇到闪退崩溃那简直想卸载APP的心都有了。其次就是配送员在配送过程中不希望耗电和耗流量太严重。最后就是用户和配送员都希望版本更新的时候安装包希望能小一点。
根据用户的四个方面需求,总结如下:
- 追求流畅,防止卡顿
- 追求稳定,防止闪退
- 追求续航,防止耗损
- 追求精简,防止臃肿
(最重要是稳定,app出现闪退崩溃是致命的。相比之下卡顿耗损安装包大都还好些。)
(一)追求稳定,防止闪退
一般造成Crash和ANR的都会出现应用崩溃的表现。造成ANR程序无响应的原因主要是在四大组件的耗时操作;而造成Crash的原因却有很多,比如运行时异常的空指针、数组越界、未实例化、强制类型、低内存机制等等,有些时候我们在开发测试阶段都没有出现异常崩溃现象,而发布上线后到了用户手机就会出现各种奇怪闪退。所以,我们要去努力实现一个永远打不死的小强 —— 不会出现Crash闪退的APP。
虽然我们无法保证开发完美至极不出现异常,但是可以努力做好异常的捕获。通常我们会使用 try - catch,在发现容易出现崩溃的代码块,主动加上try-catch 预防异常闪退。但是没加try-catch的代码块出现异常还是闪退该怎么办?我们可以通过设置 [UncaughtExceptionHandler](https://blog.csdn.net/csdn_aiyang/article/details/80677095) 捕获全局线程异常,而在主线程抛出异常时,APP依旧会闪退进程重启。那么可以使用 [Cockroach](https://blog.csdn.net/csdn_aiyang/article/details/80677095) (蟑螂),可以保证不管怎样抛异常都不会闪退,App进程也不会重启。
以上治标不治本,需要不断加强代码的严谨性去减少Crash率。由于Android应用的沙箱机制,每个应用程序都运行在一个进程中,拥有一个独立的Dalvik虚拟机实例,系统默认分配给虚拟机的内存是有限度的。当系统内存太低依然会触发LMK(Low Memory Killer)机制,即闪退、崩溃现象。不同厂商不同,如:华为mate7,192m ;小米4,128m ;红米,128m 。而在,Android4.0以后,可以通过在application节点中设置属性android:largeHeap=”true”来设置最大可分配多少内存空间就可以突破一定限制。
如果懂得内存如何分配和回收机制,对内存优化会有一定的认识和掌握的话,看下面这个例子,代码如下所示:
public class CommUtil { private static CommUtil instance; private Context context; private CommUtil(Context context) { this.context=context; } public static CommUtil getInstance(Context context){ if(null==instance){ instance=new CommUtil(context); } return instance; }}public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CommUtil instance = CommUtil.getInstance(this);// }}
一个工具类 CommUtil.class 使用创建型设计模式——单例模式,在MainActivity.class中引用,那么这个单例对象会持有Activity的实例。然后运行起来 ,再将手机屏幕旋转几下。这样的代码会造成什么错误?
(旋转屏幕的时候 Activity会被销毁重新创建,但是这里的单例会持有销毁Activity的实例就造成内存泄漏)
这时,我们可以借助内存分析工具,可以显示内存数据的可视化页面,方便定位内存泄漏的代码。使用分析工具如下:
(1)Memory Monitor 工具:
它是Android Studio自带的一个内存监视工具,它可以很好地帮助我们进行内存实时分析。通过点击Android Studio右下角的Memory Monitor标签,打开工具可以看见较浅蓝色代表free的内存,而深色的部分代表使用的内存从内存变换的走势图变换,可以判断关于内存的使用状态,例如当内存持续增高时,可能发生内存泄漏;当内存突然减少时,可能发生GC等,如下图所示。
*案例参考
(2)Memory Analyzer 工具:
MAT(Memory Analyzer Tool) 是一个快速,功能丰富的 Java Heap 分析工具,通过分析 Java 进程的内存快照 HPROF 分析,从众多的对象中分析,快速计算出在内存中对象占用的大小,查看哪些对象不能被垃圾收集器回收,并可以通过视图直观地查看可能造成这种结果的对象。
检测步骤如下:
(a)屏幕多次翻转,出现内存持续增高时。点击 Dump java Heap就会生成运行内存快照hprof文件。
(b)然后将APP完全退出,重新启动,打开Android Monitor 再次点击Dump java Heap 生成一份还没操作(旋转屏幕)前的内存快照hprof文件。现在就已经生成好了2份hprof文件, 一份是没有旋转过屏幕的 ,一份是旋转过屏幕多次的。
(c)然后选中Android Studio 最左边的Captures 进行将hprof文件导出。导出的时候需要选择保存的目录以及文件名。
(d)打开MAT ,导入我们的2个hprof文件 Open File-->选择文件-->Leak Suspects Report-->Finish:*案例参考
可以通过检索包名,查看某个类的实例个数和所在内存数据,还可以查看被引用的内存数据。如下:
Objects:实例个数
Shallow Heap:所占内存大小
Retained Heap:释放后能回收多少内存
(3)LeakCanary工具:
简单,傻瓜式操作,最重要的是LeakCanary 只在debug版本下检测,正版先上线后自动跳过检测这就方便开发者无需操作每次上线时注释检测代码。这个工具是Square公司在Github开源的。行业内不是有一句话嘛,Square出品必属精品,主流的库像okhttp、Picasso、retrofit、Dagger等都出自Square之手。说到这不得不让我联想到一位在Android开发领域神一般存在的人物,他就是大名鼎鼎的Jake Wharton(杰克.沃顿),ButterKnife的创造者,也参与贡献了Retrofit, okhttp等。
如何使用?GitHup官网https://github.com/square/leakcanary,首先在Gradle文件里添加依赖。
在Application中写方法:
private RefWatcher setupLeakCanary() { if (LeakCanary.isInAnalyzerProcess(this)) { return RefWatcher.DISABLED; } return LeakCanary.install(this); } public static RefWatcher getRefWatcher(Context context) { MyApplication leakApplication = (MyApplication) context.getApplicationContext(); return leakApplication.refWatcher; }
然后onCreate()中调用即可:refWatcher = setupLeakCanary();
在Activity中单独调用检测:
@Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = MyApplication.getRefWatcher(this);//1 refWatcher.watch(this); }
小结
影响稳定性的原因很多,比如内存使用不合理、代码异常场景考虑不周全、代码逻辑不合理等,都会对应用的稳定性造成影响。其中最常见的两个场景是:Crash 和 ANR,这两个错误将会使得程序无法使用。所以做好Crash全局监控,处理闪退同时把崩溃信息、异常信息收集记录起来,以便后续分析;合理使用主线程处理业务,不要在主线程中做耗时操作,防止ANR程序无响应发生。
(二)追求流畅,防止卡顿
卡顿的场景通常是发生在用户交互体验最直接的方面。大概分为四个方面,如下图所示。
影响卡顿的两大因素,分别是界面绘制和数据处理。
-
界面绘制:主要原因是绘制的层级深、页面复杂、刷新不合理,由于这些原因导致卡顿的场景更多出现在 UI 和启动后的初始界面以及跳转到页面的绘制上。
-
数据处理:导致这种卡顿场景的原因是数据处理量太大,一般分为三种情况,一是数据在处理 UI 线程,二是数据处理占用 CPU 高,导致主线程拿不到时间片,三是内存增加导致 GC 频繁,从而引起卡顿。
(1)布局优化
在Android种系统对View进行测量、布局和绘制时,都是通过对View数的遍历来进行操作的。如果一个View数的高度太高就会严重影响测量、布局和绘制的速度。Google也在其API文档中建议View高度不宜哦过10层。现在版本种Google[使用RelativeLayout替代LineraLayout](https://www.jianshu.com/p/8a7d059da746)作为默认根布局,目的就是降低LineraLayout嵌套产生布局树的高度,从而提高UI渲染的效率。
-
布局复用,使用<include>标签重用layout;
-
提高显示速度,使用<ViewStub>延迟View加载;
-
减少层级,使用<merge>标签替换父级布局;
-
注意使用wrap_content,会增加measure计算成本;
-
删除控件中无用属性;
(2)绘制优化
过度绘制是指在屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次重叠的 UI 结构中,如果不可见的 UI 也在做绘制的操作,就会导致某些像素区域被绘制了多次,从而浪费了多余的 CPU 以及 GPU 资源。如何避免过度绘制?
-
布局上的优化。移除 XML 中非必须的背景,移除 Window 默认的背景、按需显示占位背景图片
-
自定义View优化。使用 canvas.clipRect() 帮助系统识别那些可见的区域,只有在这个区域内才会被绘制。
(3)启动优化
启动优化工具。 应用一般都有闪屏页SplashActivity,优化闪屏页的 UI 布局,可以通过 Profile GPU Rendering 检测丢帧情况。
(另外,还可以IDE自带的一款UI绘制检测图形化数据分析工具 Systrace ,4.0版本以上版本可以使用。这里暂不介绍)
启动白屏或黑屏问题优化处理。当我们在启动一个应用时,系统会去检查是否已经存在这样一个进程,如果不存在,系统的服务会先检查startActivity中的intent的信息,然后在去创建进程,最后启动Acitivy,即冷启动。而启动出现白黑屏的问题,就是在这段时间内产生的。系统在绘制页面加载布局之前,首先会初始化窗口(Window),而在进行这一步操作时,系统会根据我们设置的Theme来指定它的Theme 主题颜色,我们在Style中的设置就决定了显示的是白屏还是黑屏。详情查看请点击
<style name="Theme.Splash" parent="AppTheme"> <item name="windowNoTitle">true</item> <item name="android:windowContentOverlay">@null</item> <item name="android:windowBackground">@drawable/splash_pic</item> <item name="android:windowFullscreen">true</item> </style>
启动加载逻辑优化处理。闪屏页的存在可以说就是启动优化,通常在闪屏页延迟2秒跳转到主界面,但是在进入首页的时候,首页复杂的View渲染以及必须在UI线程执行的业务逻辑,必然拖慢了启动速度。启动闪屏页虽然简单执行快,首页却复杂执行慢,应用启动前轻后重。可以采用分布加载、异步加载、延期加载策略来提高应用启动速。例如,把SplashActivity改成SplashFragment,应用程序的入口变成MainActivity,在MainActivity中先展示SplashFragment,显示完毕后再移除SplashFragment。这样,在SplashFragment的2S的友好时间内进行数据准备的同时,首页的View就能够被加载,首页的业务逻辑就能够被执行。在闪屏页窗口加载完毕后,我们加载activity_main的布局,考虑到这个布局有可能比较复杂,耽误View的解析时间,可以采用ViewStub的形式进行懒加载。
(4)刷新优化
- 减少刷新次数,灵活利用缓存及限时刷新;
- 缩小刷新区域,局部刷新,避免多余请求;
(5)动画优化
需要实现动画效果时,需要根据不同场景选择合适的动画框架来实现。
有些情况下,可以用硬件加速方式来提供流畅度降低动画卡顿。
(三)节省——耗电优化
在移动设备中,电池的重要性自然不言而喻,如果手机没电了应用功能技术实现再怎么牛逼,用户也什么都干不成。对于Android操作系统和设备各大开发商来说,对手机耗电的优化从没有停止过,不断地追求更长的待机时间。而对于开发一款应用来说,绝不可以忽略电量耗损的问题,被归为“电池杀手”的应用,最终的结果无疑是走向被用户卸载的道路。比如,有些应用为了保持应用进程长期在后台存活,使用各种不合理进程保活方案,破坏操作系统“生态平衡”,导致用户电量严重耗损,虽然这种流氓开发行为并不违法吧,但是也属于不道德的行为,也同样会被同行所被鄙视的行为。
在 Android5.0 以前,关于应用电量消耗的测试即麻烦又不准确,而5.0 之后Google专门引入了一个获取设备上电量消耗信息的API—— Battery Historian。Battery Historian 是一款由 Google 提供的 Android 系统电量分析工具,直观地展示出手机的电量消耗过程,通过输入电量分析文件,显示消耗情况。
最后提供一些可供参考耗电优化的方法:
(1)计算优化。算法、for循环优化、Switch..case替代if..else、避开浮点运算。
浮点运算:计算机里整数和小数形式就是按普通格式进行存储,例如1024、3.1415926等等,这个没什么特点,但是这样的数精度不高,表达也不够全面,为了能够有一种数的通用表示法,就发明了浮点数。浮点数的表示形式有点像科学计数法(*.*****×10^***),它的表示形式是0.*****×10^***,在计算机中的形式为 .***** e ±***),其中前面的星号代表定点小数,也就是整数部分为0的纯小数,后面的指数部分是定点整数。利用这样的形式就能表示出任意一个整数和小数,例如1024就能表示成0.1024×10^4,也就是 .1024e+004,3.1415926就能表示成0.31415926×10^1,也就是 .31415926e+001,这就是浮点数。浮点数进行的运算就是浮点运算。浮点运算比常规运算更复杂,因此计算机进行浮点运算速度要比进行常规运算慢得多。
(2)避免 Wake Lock 使用不当。
Wake Lock是一种锁的机制,主要是相对系统的休眠而言的,,只要有人拿着这个锁,系统就无法进入休眠意思就是我的程序给CPU加了这个锁那系统就不会休眠了,这样做的目的是为了全力配合我们程序的运行。有的情况如果不这么做就会出现一些问题,比如微信等及时通讯的心跳包会在熄屏不久后停止网络访问等问题。所以微信里面是有大量使用到了Wake_Lock锁。系统为了节省电量,CPU在没有任务忙的时候就会自动进入休眠。有任务需要唤醒CPU高效执行的时候,就会给CPU加Wake_Lock锁。大家经常犯的错误,我们很容易去唤醒CPU来工作,但是很容易忘记释放Wake_Lock。
(3)使用 Job Scheduler 管理后台任务。
在Android 5.0 API 21 中,google提供了一个叫做JobScheduler API的组件,来处理当某个时间点或者当满足某个特定的条件时执行一个任务的场景,例如当用户在夜间休息时或设备接通电源适配器连接WiFi启动下载更新的任务。这样可以在减少资源消耗的同时提升应用的效率。
(四)安装包——APK瘦身
其实APK大小对应用使用并没有影响,但应用的安装包越大,用户下载的门槛越高。例如,应用版本的迭代更新,特别是当用户在移动网络情况下,又不得不去下载安装包,才能使用产品满足自身需求。因此,开发应该减小安装包大小,使得让更多用户愿意下载产品和体验产品。
(1)安装包的组成结构
-
assets文件夹。存放一些配置文件、资源文件,assets不会自动生成对应的 ID,而是通过 AssetManager 类的接口获取。
-
res。res 是 resource 的缩写,这个目录存放资源文件,会自动生成对应的 ID 并映射到 .R 文件中,访问直接使用资源 ID。
-
META-INF。保存应用的签名信息,签名信息可以验证 APK 文件的完整性。
-
AndroidManifest.xml。这个文件用来描述 Android 应用的配置信息,一些组件的注册信息、可使用权限等。
-
classes.dex。Dalvik 字节码程序,让 Dalvik 虚拟机可执行,一般情况下,Android 应用在打包时通过 Android SDK 中的 dx 工具将 Java 字节码转换为 Dalvik 字节码。
-
resources.arsc。记录着资源文件和资源 ID 之间的映射关系,用来根据资源 ID 寻找资源。
(2)减少安装包大小
1.代码混淆。使用IDE 自带的 proGuard 代码混淆器工具 ,它包括压缩、优化、混淆等功能。
2.资源优化。比如使用 Android Lint 删除冗余资源,资源文件最少化等。(这个前面已经讲过了)
3.图片优化。比如利用 PNG优化工具 对图片做压缩处理。如果应用在4.0版本以上,推荐使用 WebP图片格式。
4.可以使用微信开源资源文件混淆工具——AndResGuard 。一般可以压缩apk的1M左右大。
5.插件化热修复开发。比如功能模块放在服务器上,按需下载,可以减少安装包大小。
6.避免重复或无用功能的第三方库。例如,百度地图接入基础地图即可、讯飞语音无需接入离线、图片库Glide\Picasso等
三、总结
综上所述,对应用进行性能优化已然成为当下开发工程师应该具备的基本技能之一,也是对开发工程师是否有能力维护高质量应用程序的重要考核之一。另外,性能优化是一个非常具有挑战性的工作,上面列出很多分析检查的工具和方法,但是真正优化工作远不止如此,想要进行完美的性能优化绝非一日之功,需要考验开发者长期研究的耐心和深厚的技术功底。
值得一看的博文推荐: