以前在项目中作过一些Android卡顿以及性能优化的工做,可是一直没时间总结,趁着这段时间把这部分总结一下。html
在应用开发中若是留意到log的话有时候可能会发下下面的log信息:java
I/Choreographer(1200): Skipped 60 frames! The application may be doing too much work on its main thread.
复制代码
在大部分Android平台的设备上,Android系统是16ms刷新一次,也就是一秒钟60帧。要达到这种刷新速度就要求在ui线程中处理的任务时间必需要小于16ms,若是ui线程中处理时间长,就会致使跳过帧的渲染,也就是致使界面看起来不流畅,卡顿。若是用户点击事件5s中没反应就会致使ANR。python
即 Frame Rate,单位 fps,是指 gpu 生成帧的速率,60fps,Android中更帧率相关的类是SurfaceFlinger。android
SurfaceFlinger surfaceflinger做用是接受多个来源的图形显示数据,将他们合成,而后发送到显示设备。好比打开应用,常见的有三层显示,顶部的statusbar底部或者侧面的导航栏以及应用的界面,每一个层是单独更新和渲染,这些界面都是有surfaceflinger合成一个刷新到硬件显示。 web
Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,VSync是Vertical Synchronization(垂直同步)的缩写,是一种在PC上很早就普遍使用的技术,能够简单的把它认为是一种定时中断。而在Android 4.1(JB)中已经开始引入VSync机制,用来同步渲染,让UI和SurfaceFlinger能够按硬件产生的VSync节奏进行工做。shell
安卓系统中有 2 种 VSync 信号: 一、屏幕产生的硬件 VSync: 硬件 VSync 是一个脉冲信号,起到开关或触发某种操做的做用。 二、由 SurfaceFlinger 将其转成的软件 Vsync 信号:经由 Binder 传递给 Choreographer。数据库
除了Vsync的机制,Android还使用了多级缓冲的手段以优化UI流程度,例如双缓冲(A+B),在显示buffer A的数据时,CPU/GPU就开始在buffer B中准备下一帧数据:可是不能保证每一帧CPU、GPU都运行状态良好,可能因为资源抢占等性能问题致使某一帧GPU掉链子,vsync信号到来时buffer B的数据还没准备好,而此时Display又在显示buffer A的数据,致使后面CPU/GPU没有新的buffer着手准备数据,致使卡顿(jank)。 浏览器
从系统层面上看主要如下几个方面的缘由会致使卡顿: 1. SurfaceFlinger 主线程耗时性能优化
SurfaceFlinger 负责 Surface 的合成 , 一旦 SurfaceFlinger 主线程调用超时 , 就会产生掉帧 . SurfaceFlinger 主线程耗时会也会致使 hwc service 和 crtc 不能及时完成, 也会阻塞应用的 binder 调用, 如 dequeueBuffer \ queueBuffer 等.网络
2. 后台活动进程太多致使系统繁忙
后台进程活动太多,会致使系统很是繁忙, cpu \ io \ memory 等资源都会被占用, 这时候很容易出现卡顿问题 , 这也是系统这边常常会碰到的问题。 dumpsys cpuinfo 能够查看一段时间内 cpu 的使用状况:
当线程为 Runnable 状态的时候 , 调度器若是迟迟不能对齐进行调度 , 那么就会产生长时间的 Runnable 线程状态 , 致使错过 Vsync 而产生流畅性问题。
四、System 锁
system_server 的 AMS 锁和 WMS 锁 , 在系统异常的状况下 , 会变得很是严重 , 以下图所示 , 许多系统的关键任务都被阻塞 , 等待锁的释放 , 这时候若是有 App 发来的 Binder 请求带锁 , 那么也会进入等待状态 , 这时候 App 就会产生性能问题 ; 若是此时作 Window 动画 , 那么 system_server 的这些锁也会致使窗口动画卡顿
Android P 修改了 Layer 的计算方法 , 把这部分放到了 SurfaceFlinger 主线程去执行, 若是后台 Layer 过多, 就会致使 SurfaceFlinger 在执行 rebuildLayerStacks 的时候耗时 , 致使 SurfaceFlinger 主线程执行时间过长。
一、主线程执行时间长 主线程执行 Input \ Animation \ Measure \ Layout \ Draw \ decodeBitmap 等操做超时都会致使卡顿 。
二、主线程 Binder 耗时
Activity resume 的时候, 与 AMS 通讯要持有 AMS 锁, 这时候若是碰到后台比较繁忙的时候, 等锁操做就会比较耗时, 致使部分场景由于这个卡顿, 好比多任务手势操做。
三、WebView 性能不足
应用里面涉及到 WebView 的时候, 若是页面比较复杂, WebView 的性能就会比较差, 从而形成卡顿
四、帧率与刷新率不匹配
若是屏幕帧率和系统的 fps 不相符 , 那么有可能会致使画面不是那么顺畅. 好比使用 90 Hz 的屏幕搭配 60 fps 的动画。
卡顿检测可使用如下多种方法同时进行: 一、使用dumpsys gfxinfo 二、使用Systrace获取相关信息 三、使用LayoutInspect 检测布局层次 四、使用BlockCanary 五、利用Choreographer。 六、使用严格模式(StrictMode )。
在开发过程当中发现有卡顿发生时可使用下面的命令来获取卡顿相关的信息:
adb shell dumpsys gfxinfo [PACKAGE_NAME]
复制代码
输入这个命令后可能会打印下面的信息:
Applications Graphics Acceleration Info:
Uptime: 102809662 Realtime: 196891968
** Graphics info for pid 31148 [com.android.settings] **
Stats since: 524615985046231ns
Total frames rendered: 8325
Janky frames: 729 (8.76%)
90th percentile: 13ms
95th percentile: 20ms
99th percentile: 73ms
Number Missed Vsync: 294
Number High input latency: 47
Number Slow UI thread: 502
Number Slow bitmap uploads: 44
Number Slow issue draw commands: 135
复制代码
上面参数说明:
Graphics info for pid 31148 [com.android.settings]: 代表当前dump的为设置界面的帧信息,pid为31148 Total frames rendered: 8325 本次dump搜集了8325帧的信息
Janky frames :729 (8.76%)出现卡顿的帧数有729帧,占8.76%
Number Missed Vsync: 294 垂直同步失败的帧
Number Slow UI thread: 502 因UI线程上的工做致使超时的帧数
Number Slow bitmap uploads: 44 因bitmap的加载耗时的帧数
Number Slow issue draw commands: 135 因绘制致使耗时的帧数
上面使用的dumpsys是能发现问题或者判断问题的严重性,但没法定位真正的缘由。若是要定位缘由,应当配合systrace工具使用。
systrace使用
Systrace能够帮助分析应用是如何设备上运行起来的,它将系统和应用程序线程集中在一个共同的时间轴上,分析systrace的第一步须要在程序运行的时间段中抓取trace log,在抓取到的trace文件中,包含了这段时间中想要的关键信息,交互状况。
Android studio中使用systrace
一、在android设备的 设置 -- 开发者选项 -- 监控 -- 开启traces。 二、选择要追中的类别,而且点击肯定。
完成以上配置后,开始抓trace文件
$ python systrace.py --cpu-freq --cpu-load --time=10 -o mytracefile.html
复制代码
分析trace文件 抓到trace.html文件后,经过web浏览器打开
检查Frames 每一个应用程序都有一排表明渲染帧的圆圈,一般为绿色,若是绘制的时间超过16.6毫秒则显示黄色或红色。经过“W”键查看帧。
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
...
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Trace.beginSection("MyAdapter.onCreateViewHolder");
MyViewHolder myViewHolder;
try {
myViewHolder = MyViewHolder.newInstance(parent);
} finally {
Trace.endSection();
}
return myViewHolder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
Trace.beginSection("MyAdapter.onBindViewHolder");
try {
try {
Trace.beginSection("MyAdapter.queryDatabase");
RowItem rowItem = queryDatabase(position);
mDataset.add(rowItem);
} finally {
Trace.endSection();
}
holder.bind(mDataset.get(position));
} finally {
Trace.endSection();
}
}
…
}
复制代码
BlockCanary是国内开发者MarkZhai开发的一套性能监控组件,它对主线程操做进行了彻底透明的监控,并能输出有效的信息,帮助开发分析、定位到问题所在,迅速优化应用。 其特色有: 一、非侵入式,简单的两行就打开监控,不须要处处打点,破坏代码优雅性。 二、精准,输出的信息能够帮助定位到问题所在(精确到行),不须要像Logcat同样,慢慢去找。 三、目前包括了核心监控输出文件,以及UI显示卡顿信息功能
BlockCanary基本原理
android应用程序只有一个主线程ActivityThread,这个主线程会建立一个Looper(Looper.prepare),而Looper又会关联一个MessageQueue,主线程Looper会在应用的生命周期内不断轮询(Looper.loop),从MessageQueue取出Message 更新UI。
public static void loop() {
...
for (;;) {
...
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
}
复制代码
BlockCanary主要是检测msg.target.dispatchMessage(msg);
以前的>>>>> Dispatching to
和以后的<<<<< Finished to
的间隔时间。 应用发生卡顿,必定是在dispatchMessage中执行了耗时操做。经过给主线程的Looper设置一个Printer,打点统计dispatchMessage方法执行的时间,若是超出阀值,表示发生卡顿,则dump出各类信息,提供开发者分析性能瓶颈。
Android 主线程运行的本质,其实就是 Message 的处理过程,咱们的各类操做,包括每一帧的渲染操做 ,都是经过 Message 的形式发给主线程的 MessageQueue ,MessageQueue 处理完消息继续等下一个消息。
Choreographer 两个主要做用
一、承上:负责接收和处理 App 的各类更新消息和回调,等到 Vsync 到来的时候统一处理。好比集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操做) ,判断卡顿掉帧状况,记录 CallBack 耗时等。
二、启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(经过 FrameDisplayEventReceiver.onVsync );请求 Vsync(FrameDisplayEventReceiver.scheduleVsync) .
使用Choreographer 计算帧率
Choreographer 处理绘制的逻辑核心在 Choreographer.doFrame 函数中,从下图能够看到,FrameDisplayEventReceiver.onVsync post 了本身,其 run 方法直接调用了 doFrame 开始一帧的逻辑处理:
public class MyFrameCallback implements Choreographer.FrameCallback {
private String TAG = "性能检测";
private long lastTime = 0;
@Override
public void doFrame(long frameTimeNanos) {
if (lastTime == 0) {
//代码第一次初始化。不作检测统计。
lastTime = frameTimeNanos;
} else {
long times = (frameTimeNanos - lastTime) / 1000000;
int frames = (int) (times / 16);
if (times > 16) {
Log.w(TAG, "UI线程超时(超过16ms):" + times + "ms" + " , 丢帧:" + frames);
}
lastTime = frameTimeNanos;
}
Choreographer.getInstance().postFrameCallback(mFrameCallback);
}
}
复制代码
由上面的分析可知对象分配、垃圾回收(GC)、线程调度以及Binder调用 是Android系统中常见的卡顿缘由,所以卡顿优化主要如下几种方法,更多的要结合具体的应用来进行:
一、布局优化
二、减小主线程耗时操做
三、减小过分绘制 过分绘制是同一个像素点上被屡次绘制,减小过分绘制通常减小布局背景叠加等方式,以下图所示右边是过分绘制的图片。
五、对象分配和回收优化
自从Android引入 ART 而且在Android 5.0上成为默认的运行时以后,对象分配和垃圾回收(GC)形成的卡顿已经显著下降了,可是因为对象分配和GC有额外的开销,它依然又可能使线程负载太重。 在一个调用不频繁的地方(好比按钮点击)分配对象是没有问题的,但若是在在一个被频繁调用的紧密的循环里,就须要避免对象分配来下降GC的压力。
一、source.android.google.cn/devices/gra… 二、www.bradcypert.com/what-is-and… 三、ashishb.net/tech/demyst… 四、devblogs.microsoft.com/xamarin/tip… 五、developer.android.com/training/te… 六、www.androidperformance.com/2019/09/05/… 七、developer.android.com/tools/help/… 八、zhuanlan.zhihu.com/p/87954949