Android性能优化
1.启动优化
概述:启动速度往往是由一个input事件(以Message的形式)触发(比如点击、长按、电源键),由一个或者多个Message的执行结束为结尾,而这些Message中一般都有关键的界面绘制相关的Message,应用启动时间介绍见官网:https://developer.android.com/topic/performance/vitals/launch-time?hl=zh-cn
冷启动:
在冷启动开始时,系统有三个任务,它们是:
1、加载并启动应用
2、在启动后立即显示应用的空白启动窗口
3、创建应用进程
系统一创建应用进程,应用进程就负责后续阶段:
1、创建应用对象。
2、启动主线程。
3、创建主 Activity。
4、扩充视图。
5、布局屏幕。
6、执行初始绘制。
-->Launcher--(启动应用)
-->SystemServer--(加载并启动应用-->创建空白启动窗口-->创建应用进程-->)
-->APP--(创建ActivityThread-->执行main函数--创建主Activity--测量、布局、绘制)
界面切换
Activity切换主要是弄清activity的生命周期过程,A跳转到B Activity的过程:
A.onPause-->B.onCreate-->B.onStart-->B.onResume-->A.onStop
Fragment切换主要是弄清fragment的生命周期过程,Fragment概述见官网:https://developer.android.com/guide/components/fragments?hl=zh-cn,生命周期可参考这篇文章:https://juejin.cn/post/6844903752114126855
1.1 测试启动速度:
A.使用高速摄像机或者iphone测试时间;
B.通过打印ActivityManager中Displayed的log查看启动时间(Activity第一帧显示的时间)
C.adb shell am start -S -W (Displayed 对应TotalTime,即Activity第一帧显示的时间)
1.2 启动速度分析工具:
A.HierarchyView分析布局层次
B.systrace定位问题点 和traceView定位耗时函数分析性能
C.Run->Edit Configurations-->Profiling-->check start recording CPU activity on startup
https://developer.android.com/studio/profile/cpu-profiler
1.3 startingWindow机制
通过startActivity启动应用时,把Options等信息传给AMS,AMS创建相应的ActivityRecord,改变其状态、可见性,WMS创建相应的窗口,改变其可见性、大小、位置等,等到APP页面绘制完成,动画加载完成,开始执行动画,这个时候用户才看到应用起来了。
当应用界面绘制比较慢时,系统一直处于等待状态,直到应用告诉系统绘制完成,启动动画才开始执行,这也是启动慢的直接原因。
原生为了解决上面的问题,引入了startingWindow的机制,即应用启动不依赖于应用界面绘制,只要startingWindow绘制完成并且动画设置完毕,就展开页面。
流程如下:
***startActivity ---> 创建ActivityRecord/改变其可见性--->添加StartingWindow---> StartingWindow 绘制完成--->页面展开
***startActivity--->解析theme属性--->加载预览图--->窗口绘制完成(校验theme属性、更新缓存图)--->应用退出(校验theme属性、更新缓存图)
1.3 检查初始化代码
1.4 减少布局复杂度,使用viewstub,预加载
1.5 view耗时:
A.使用overdraw分析界面是否存在过绘制;
B.使用profiling GPU render分析view绘制逻辑是否过于复杂;
C.使用HierachyView分析布局层次;
D.lint找出ui线程中耗时操作
https://developer.android.com/topic/performance/vitals/launch-time
启动高级性能剖析
https://developer.android.com/studio/profile/android-profiler?hl=zh-cn#advanced-profiling
1.6 启动速度优化分析思路
1、打开binder调试,方便在trace中显示binder信息
adb shell am trace-ipc start
抓取结束后,可以执行下面的命令关闭
adb shell am trace-ipc stop
2、trace命令加入irq tag,默认的命令不包含irq,需要自己加irq的TAG,这样打开Trace之后,就可以看到irq相关的内容,最后的抓trace命令如下:
python /mnt/d/Android/platform-tools/systrace/systrace.py gfx input view webview wm am sm rs bionic power pm ss database network adb idle pdx sched irq freq idle disk workq binder_driver binder_lock -a com.xxx.xxx
3、在APP代码的每个函数中插入trace,在分析的时候可以看到更详细的信息
2.内存优化
Log分析,命令分析,工具分析
2.1 adb shell dumpsys meminfo查看内存情况
2.2 DDMS heap查看
2.3 APT工具查看是否有内存泄露,配合重复操作某个路径
2.4 MAT工具查看Leak Suspects, Histogram分析内存最大类的内部引用
2.5 基于MAT分析,屏蔽代码,反复测试
2.6 内存抖动导致卡顿:
1.查看虚拟机GC的log
2.使用systrace查看绘制过程线程的执行情况
补充说明:不设置背景>使用颜色值>.9图片>大像素图片
https://cs.android.com/android/platform/superproject/+/master:development/scripts/native_heapdump_viewer.py?q=native_heapdump_viewer.py
https://cs.android.com/android/platform/superproject/+/master:bionic/libc/malloc_debug/README.md?q=native_heapdump_viewer.py
2.7 使用LeakCanary检测内存泄露:
(1)build.gradle添加依赖
(2)application中初始化 LeakCanary.install(this); 在应用内点击并退出,如果有内存泄露,一小段时间后会在通知栏生成一条通知,点击可查看内存泄露的引用路径。
3.卡顿优化
3.1 启动卡顿,运行卡顿,滑动卡顿;
3.2 log分析:
A.log是否打印频繁;
B.虚拟机是否有执行GC操作;
3.3 命令分析:
A.adb shell top -m 10 -t -n 5 top命令查看是否有进程占用大量CPU时间,导致主线程无法执行;
B.dumpsys SurfaceFlinger查看刷新率是否正常;
C.dumpsys SurfaceFlinger查看叠加层信息,是否有多余层;
D.dumpsys gfxinfo packagename 查看硬件加速是否开启;
3.4 工具分析:
A.TraceView使用DDMS采集数据
3.4.1 打开debug属性,application配置debuggable,系统应用还需配置ro.debuggable=1,persist.service.adb.enable=1;
3.4.2 打开eclipse->DDMS,选择应用;点击start method profiling,执行相应过程,点击stop method profiling
B.TraceView 加代码采集数据
3.4.3 manifest加入写入SD卡的权限
3.4.4 在性能关注起始位置,调用方法启动追踪,开始创建trace log文件;调用stopMethodTracing方法停止trace过程
3.4.5 执行trace文件进行分析traceview ***demo.trace
3.5 分析时间线面板(选择线程)和分析面板(所选线程各函数的调用情况)
3.6选择Incl Cpu Time进行降序排列,得到最耗时的方法
3.7 Systrace
https://developer.android.com/topic/performance/tracing/navigate-report?hl=zh-cn
Systrace会生成包含多个部分的输出HTML文件。该报告列出了每个进程的线程。如果给定线程会渲染界面帧,该报告还会沿时间轴指明所渲染的帧,当您在报告中从左向右移动时,时间会向前推移。
报告从上到下包含以下几个部分:
用户互动
第一部分包含表示应用或游戏中的具体用户活动的条形图。这些互动可用作有用的事件标记。
CPU活动
下一部分显示了表示每个CPU中的线程活动的条形图。这些条形图会显示所有应用中的CPU活动。CPU活动部分可以展开,展开后您就可以查看每个CPU的时钟频率。【点击kernel展开】
系统事件
此部分中的直方图会显示特定的系统级事件,例如特定对象的纹理计数和总大小。
值得仔细检查的直方图是标记为 SurfaceView 的直方图。计数表示已传递到显示管道并等待显示在设备屏幕上的组合帧缓冲区的数量。由于大多数设备都会进行双重或三重缓冲,因此该计数几乎总为 0、1 或 2。
显示帧
这一部分通常是报告中最顶部的部分,描绘了一条多色线条,后面是成堆的条形。这些形状表示已创建的特定线程的状态和帧堆栈。堆栈的每个层级代表对 beginSection()
的一次调用,或您为应用或游戏定义的[自定义跟踪事件]的开头。
注意:界面线程(即通常运行您的应用或游戏的主线程)始终会显示为第一个线程。
每个条形堆上方的多色线条表示特定线程随时间变化的一组状态。每段线条可以包含以下颜色之一:
绿色:正在运行
线程正在完成与某个进程相关的工作或正在响应中断。
蓝色:可运行
线程可以运行但目前未进行调度。
白色:休眠
线程没有可执行的任务,可能是因为线程在遇到斥锁定时被阻止。
橙色:不可中断的休眠
线程在遇到 I/O 操作时被阻止或正在等待磁盘操作完成。
紫色:可中断的休眠
线程在遇到另一项内核操作(通常是内存管理)时被阻止。
键盘快捷键
键 | 说明 |
---|---|
W | 放大跟踪时间轴 |
A | 在跟踪时间轴上向左平移 |
S | 缩小跟踪时间轴。 |
D | 在跟踪时间轴上向右平移。 |
E | 以当前鼠标位置为中定位跟踪时间轴 |
M | 高亮当前选区 |
1 | 将当前正在使用中的选择模型更改为“选择”模式。对应于鼠标选择器工具栏中显示的第 1 个按钮(请参见右图)。 |
2 | 将当前正在使用中的选择模型更改为“平移”模式。对应于鼠标选择器工具栏中显示的第 2 个按钮(请参见右图)。 |
3 | 将当前正在使用中的选择模型更改为“缩放”模式。对应于鼠标选择器工具栏中显示的第 3 个按钮(请参见右图)。 |
4 | 将当前正在使用中的选择模型更改为“计时”模式。对应于鼠标选择器工具栏中显示的第 4 个按钮(请参见右图)。 |
G | 在当前所选任务的开头显示网格。 |
Shift + G | 在当前所选任务的末尾显示网格 |
向左箭头 | 在当前选定的时间轴上选择上一个事件 |
向右箭头 | 在当前选定的时间轴上选择下一个事件。 |
识别性能问题
浏览 Systrace 报告时,您可以通过执行以下一项或多项操作来更轻松地识别性能问题:
1.通过在时间间隔周围绘制一个矩形来选择所需的时间间隔。
2.使用标尺工具标记或突出显示问题区域。
3.依次点击 View Options > Highlight VSync,以显示每项显示屏刷新操作。
检查界面帧和提醒
1.Systrace 报告列出了渲染界面帧的每个进程,并指明了沿时间轴渲染的每个帧。在 16.6 毫秒内渲染的必须保持每秒 60 帧稳定帧速率的帧会以绿色圆圈表示。渲染时间超过 16.6 毫秒的帧会以黄色或红色帧圆圈表示。
2.点击某个帧圆圈可将其突出显示,并提供有关系统为渲染该帧所做工作的其他信息,包括提醒。此报告还会显示系统在渲染该帧时执行的方法。您可以调查这些方法以确定界面卡顿的可能原因。
选择运行速度慢的帧后,您可能会在报告的底部窗格中看到一条提醒。图 5 中显示的提醒指明帧的主要问题是在 [ListView
]回收和重新绑定上花费了太多时间。指向跟踪记录中相关事件的链接可详细说明系统在此期间执行的操作。
如需查看此工具在您的跟踪记录中发现的每条提醒以及设备触发每条提醒的次数,请点击窗口最右侧的 Alerts 标签页,如图 6 所示。Alerts 面板可帮助您了解跟踪记录中出现的问题以及这些问题导致出现卡顿的频率。您可以将此面板视为要修正的错误列表。通常情况下,只需对一个区域进行细微改动或改进即可移除整组提醒。
如果您发现在界面线程上执行的工作太多,请使用以下方法之一来帮助确定哪些方法占用了过多的 CPU 时间:
- 如果您想了解哪些方法可能会导致瓶颈,请在这些方法中添加跟踪标记。如需了解详情,请参阅有关如何在代码中定义自定义事件的指南。
- 如果您不确定界面瓶颈的来源,请使用 Android Studio 中提供的 CPU 分析器。您可以生成跟踪日志,然后使用 CPU 分析器导入和检查这些日志。
4.数据库优化
4.1 使用SQLiteSpy工具进行数据库优化:
A.抽取SQL语句,在此工具上进行调试,分析SQL效率,查看执行速度。
4.2 常用优化方向:
A.数据库读取优化(索引读取,按需索取,查询语句优化,JOIN优化);
B.写入优化(批量处理,使用触发器)
4.3 数据库调试:
A.adb shell content query;
B.contentprovider的query/update/insert方法中打印出调用者进程id(分析死锁,分析ANR,分析性能;UserId:android.os.Binder.getCallingUid(),Pid:android.os.Binder.getCallingPid());
C.编写测试apk进行单元测试
4.4 数据库性能分析:
SQLite benchmarking测试了下数据库性能
5.广播优化
5.1 cpu开销分析,查看cpu使用情况
5.2 借助trace堆栈和log分析
获取系统服务的堆栈(adb shell bugreport -t > allTrace.txt),查看系统服务16个Binder线程占用情况
5.3 代码排查
5.4 获取当前广播信息
adb shell dumpsys activity broadcasts
5.5 应用内部使用广播通信,使用LocalBraodcastManager
5.6 执行效率要求高的消息传递不使用广播
6.ANR问题分析
6.1 ANR分类
6.1.1 应用ANR:
(1)按键响应超时事件5s
Input dispatching timed out
主线程对输入事件在5秒内没有处理完毕发生ANR:当应用程序的Window处于Active状态并且能够接收输入事件(例如按键更新、触摸事件)时,系统底层上报的事件就会被InputDispatcher分发给这个应用程序,应用程序的主线程通过InputChannel读取输入事件并交给界面视图处理。通常会注册一个监听器来接收并处理事件,或者创建自定义的试图控件来处理事件。InputDispatcher运行在系统进程system_server的一个单独线程中,应用程序的主线程在处理事件的过程中,InputDispatcher会不断的检测处理过程是否超时,一旦超时,会通过一系列的回调通知WMS的notifyANR函数,最终会调用到AMS中mHandler对象里的SHOW_NOT_RESPONDING_MSG这个case
(2)广播处理超时10s
Timeout of broadcast /Receiver during timeout:
**可能原因:1、binder;2、消息队列;3、onReceive;4、静态接收者写入磁盘未完成**
主线程在执行BroadcastReceiver的onReceive函数时10秒内没有执行完毕发生ANR:ContextImpl发送广播(sendBroadcast\sendOrderBroadcast\sendStickyBroadcast)APP进程---->broadcastIntent--(binder_1)-->AMS----->processNextBroadcast--->BroadcastQueue(BroadcastHandler埋定时炸弹)----->scheduleReceiver(binder_3)---->handleReceive--->ActivityThread---->BroadcastReceiver(onReceive)--->finishReceiver(binder_2)--->AMS
静态注册的接收者超时检测过程会考虑QueuedWork是否工作尚未完成(SharedPreferences写入磁盘操作)
(3)服务处理超时20s
Timeout executing service:
**可能原因:1、binder;2、消息队列;3、onCreate、onStartCommand**
主线程在执行Service的各个生命周期函数时20秒内没有执行完毕发生ANR:ContextImpl(startService)APP进程---->startService(binder_1)---->AMS---->realStartServiceLocked----->ActiveServices(AMS.MainHandler)----->scheduleCreateService----->binder_3---->handleCreateService---->ActivityThread--->new/attach/onCreate(Service进程)---->serviceDoneExecuting(binder_2)
6.1.2系统ANR:
(1)Watchdog超时60s
6.2 ANR的原因
1、 应用本身:耗时操作,死循环,线程阻塞,线程挂起;
2\ 其他进程:cpu被抢占
6.3 ANR问题分析思路:
1、 event log看具体的ANR时间(关键字:am_anr),看看是否跟ANR log能够对上,以确定ANR Log是否有效
2、 如果应用ANR log有效,则分析应用ANR现场:
(1)根据apps/android.txt获取出错进程、出错原因、CPU占用率、tid、死锁等信息;
(2)根据anr/traces.txt查看堆栈信息、分析代码
3、 trace中字段的含义
字段 | 含义 |
---|---|
tid | 线程号 |
sysTid | 线程号 |
Native | 线程状态,其中 state 也是线程状态,state=S 代表 Sleeping |
nice | nice 值越小,则优先级越高。因为是主线程此处 nice=-10, 可以看到优先级很高了 |
schedstat | 括号中的 3 个数字,依次是 Running, Runnable, Switch Running 时间:CPU 运行的时间,单位 ns Runable 时间:RQ 队列的等待时间,单位 ns Switch 次数:CPU 调度切换次数 |
utm | 该线程在用户态所执行的时间,单位是 jiffies |
stm | 该线程在内核态所执行的时间,单位是 jiffies |
sCount | 此线程被挂起的次数 |
dsCount | 线程被调试器挂起的次数,当一个进程被调试后,sCount 会重置为 0,调试完毕后 sCount 会根据是否被正常挂起增长,但是 dsCount 不会被重置为 0,所以 dsCount 也可以用来判断这个线程是否被调试过 |
self | 线程本身的地址 |
字段 | 含义 |
---|---|
TERMINATED | Thread.run has returned, but Thread* still around |
RUNNABLE | runnable/running in a JNI native method/suspended by GC or debugger |
TIMED_WAITING | in Object.wait() Thread.sleep() with a timeout |
BLOCKED | blocked on a monitor |
WAITING | in Object.wait()/checking other threads are not run on abort |
NEW | native thread started, not yet ready to run managed code |
4、 如果没有log,分析代码(zygote的堆栈dump kill -3 <pid>,输出的trace会保存在data/anr/trace.txt文件中,分析堆栈log,结合堆栈log确认是当前报错进程的异常还是其他进程的异常。如果是其他进程的异常,则跟踪其他进程处理,如果是当前进程的异常,则查看当前进程的主线程状态是Native,Block或者Waiting。
1)如果是Native,则查看主线程的堆栈信息,分析线程耗时的原因
2)如果是Block,则查看
3)如果是Waiting(挂起状态),
6.4 系统ANR分析方法
1、查找WATCHDOG信息
2、 查看调用堆栈、分析代码
3、 分析思路:
(1)分析关键操作:比如解锁、安装应用、亮灭屏、应用启动
(2)分析系统关键log:A:系统变慢:Slow operation、Slow dispatch、Slow delivery、dvm_lock_sample;B:进程变化:am_kill、lowmemorykiller、ANR;C:cpu info、binder info(是否满了)、iowait(是否过高)
(3)分析关键线程的运行情况
7.兼容性问题分析
7.1 常用分析策略:
A.无法安装(log分析,apktool);
B.定屏分析(dumpsys input,getevent);
C.界面异常(dumpsys SurfaceFlinger,dumpsys window);
8.apk大小优化
8.1 assets目录筛查,lint工具清除本地冗余资源(命令行执行)
8.2 findbugs查找冗余代码,系统使用资源反色排查,使用工具排查应用中多余图片和未使用资源
8.3 tinypng压缩图片
减少程序图片资源的大小
1 确保在build.gradle文件中开启了minifEnabled与shrinkResources的属性,这两个属性可以帮助移除那些在程序中使用不到的代码与资源,帮助减少APP的安装包大小。
2 有选择性的提供对应分辨率的图片资源,系统会自动匹配最合适分辨率的图片并执行拉伸或者压缩的处理。
3 在符合条件的情况下,使用Vertor Drawable替代传统的PNG/JPEG图片,能够极大的减少图片资源的大小。传统模式下,针对不同dpi的手机都需要提供一套PNG/JPEG的图片,而如果使用Vector Drawable的话,只需要一个XML文件即可。
使用VectorDrawable还可以避免因为使用帧动画导致的图片资源过多的情况,如下图所示
4 尽量复用已经存在的资源图片,使用代码的方式对已有的资源进行复用,如下图所示:
5、针对启动速度不影响的应用,不编译odex文件
6、互联网的应用放老的版本
7、不重要的应用从system分区摞到data分区,并确保不可卸载
8、OS版本裁剪功能
9.功耗优化
9.1 常见耗电原因:
1.通过wakelock锁定;
2.alarm频繁唤醒异常;
3.频繁通过网络设备唤醒;
4.异常持有外设耗电,(LCD、MODEM、AP、GPU对功耗影响)界面颜色偏白,持有亮屏锁不释放;
5.后台消耗cpu资源
9.2 常见应用原因:
1.log过多,频繁启动,文件解析,大数据量传输,复杂算法运算;
2.写存储,申请连续内存时等待;
3.多核调度,调频策略变化;
4.运营商网络影响,NV参数设置不合理;
5.刷新频率,过度绘制,图层成熟,图片形状,裁剪图片,动画曲线,字体阴影
9.3 优化方向:
界面颜色风格选择:
9.4 分析思路
9.4.1. 确认现象:1.确认问题点 ,2.熄屏/亮屏查看 events log查看 screen_toggled,3.电量查看 events log中查看battery_level,电量下降是否平滑
9.4.2 分析cpu耗电:1.wakelock是否常时间持有,kernel log中搜索UTC,确认熄屏后kernel是否很快待机,cpu时间是否有跳变,是否有多个watchdog打印;2.定位wakelock,查看应用持锁和系统持锁,分析长时间未休眠原因,kernel log中搜索aborted,分析是否进程冻结失败,kernel log中搜索failed看是否设备suspend失败;3.统计唤醒源
9.4.3.分析网络数据,4.分析外设耗电,5.电量检测是否有误差
9.4.4.OLED屏,不同颜色界面对功耗影响非常大
9.5 应用电流分析思路:
9.5.1 top命令查看CPU占有率(adb shell top -t -m 10)
9.5.2 ftrace查看线程的运行情况
9.6 应用界面对功耗的影响
9.6.1 1.图片资源(图片色值明暗程度)2.view数量,图层数量,view区域重叠,运算复杂度,字体阴影
9.6.2 联系人头像方形变圆形,裁剪算法更复杂;item行高变低,单页view数量更多。
9.6.3 减少不必要的刷新,通过开发者选项查看是否仍在刷新
打开systrace 网站
1.chrome://tracing
systrace工作原理
https://www.jianshu.com/p/6f528e862d31
10.网络优化
思路:缓存
1.让应用可离线使用——应用开发者行为(如:电子邮件客户端撰写、发送、阅读、移动、删除邮件)
2.使用GcmNetworkManager和ContentProvider——网络数据缓存,应用开发者行为
3.网络请求去重——应用开发者行为(如:不会变化的数据只网络请求一次,缓存起来)
思路:根据网络状态决定行为
4.网络请求划分优先级——应用开发者行为(如:文本请求高于富媒体请求)
5.低速网络下减少网络请求——应用开发者行为(如:网速慢,只下载低分辨率媒体)
6.检测网络变化,然后更改应用行为——应用开发者行为(如:新闻阅读器应用2G网络时一次获取3篇文章,使用WLAN时一次获取20篇文章)