UI卡顿
帧率60fps
准则:尽量保证每次在16ms内处理完所有的CPU与GPU计算、绘制、渲染等操作,否则会造成丢帧卡顿问题
常见原因:
- 人为在UI线程中做轻微耗时操作,导致UI线程卡顿;
- 布局Layout过于复杂,无法在16ms内完成渲染;
- 同一时间动画执行的次数过多,导致CPU或GPU负载过重;
- View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;
- View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;
- 内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;
- 冗余资源及逻辑等导致加载和执行缓慢;
- 臭名昭著的ANR;
解决方法
HierarchyViewer
1.可以看到view测量、布局、绘制的时间,红色和黄色的点代表速度渲染较慢的View
2.自定义View的性能调试时,HierarchyViewer上面的invalidate Layout和requestLayout按钮的功能更加强大,它可以帮助我们debug自定义View执行invalidate()和requestLayout()过程,我们只需要在代码的相关地方打上断点就行了,接下来通过它观察绘制即可。
GPU过度绘制
开发者选项中的GPU过度绘制工具来进行分析。设置->开发者选项->调试GPU过度绘制
由于过度绘制指在屏幕的一个像素上绘制多次(譬如一个设置了背景色的TextView就会被绘制两次,一次背景一次文本;这里需要强调的是Activity设置的Theme主题的背景不被算在过度绘制层级中),所以最理想的就是绘制一次,也就是蓝色(优化标准要求最极端界面下红色区域不能长期持续超过屏幕三分之一),因此我们需要依据此颜色分布进行代码优化,譬如优化布局层级、减少没必要的背景、暂时不显示的View设置为GONE而不是INVISIBLE、自定义View的onDraw方法设置canvas.clipRect()指定绘制区域或通过canvas.quickreject()减少绘制区域等。
GPU呈现模式图及FPS
GPU呈现模式图工具来进行流畅度考量的流程是在设置->开发者选项->GPU呈现模式
界面上会以实时柱状图来显示每帧的渲染时间,柱状图越高表示渲染时间越长,每个柱状图偏上都有一根代表16ms基准的绿色横线,只要我们每一帧的总时间低于基准线就不会发生UI卡顿问题。
虽然能够看出来有帧耗时超过基准线导致了丢帧卡顿,但却分析不到造成丢帧的具体原因。所以说为了配合解决分析UI丢帧卡顿问题我们还需要借助traceview和systrace来进行原因追踪
Lint资源及冗余UI布局等优化
Memory监测及GC打印与Allocation Tracker进行UI卡顿分析
GC导致的UI卡顿。。内存抖动:短时间内有大量频繁的对象创建与释放操作
Traceview
记录了应用程序中每个函数的执行时间
traces.txt文件进行ANR分析优化 adb pull /data/anr/traces.txt
开发中注意:
1.布局优化;尽量使用include、merge、ViewStub标签,尽量不存在冗余嵌套及过于复杂布局(譬如10层就会直接异常),尽量使用GONE替换INVISIBLE,使用weight后尽量将width和heigh设置为0dp减少运算,Item存在非常复杂的嵌套时考虑使用自定义Item View来取代,减少measure与layout次数等。
2.列表及Adapter优化;尽量复用getView方法中的相关View,不重复获取实例导致卡顿,列表尽量在滑动过程中不进行UI元素刷新等。
3.背景和图片等内存分配优化;尽量减少不必要的背景设置,图片尽量压缩处理显示,尽量避免频繁内存抖动等问题出现。
4.自定义View等绘图与布局优化;尽量避免在draw、measure、layout中做过于耗时及耗内存操作,尤其是draw方法中,尽量减少draw、measure、layout等执行次数。
5.避免ANR,不要在UI线程中做耗时操作,遵守ANR规避守则,譬如多次数据库操作等
内存优化
察觉方式 | 场景 |
AS的Memory窗口 | 平时用来直观了解自己应用的全局内存情况,大的泄露才能有感知。 |
dumpsys meminfo命令 | 常用方式,可以很直观的察觉一些泄露,但不全面且常规足够用。 |
leakcanary神器 | 比较强大,可以感知泄露且定位泄露;实质是MAT原理,只是更加自动化了,当现有代码量已经庞大成型,且无法很快察觉掌控全局代码时极力推荐;或者是偶现泄露的情况下极力推荐。 |
MAT | 专门分析Java堆数据内存引用的工具,找到GC ROOT |
1.Context使用不当造成内存泄露;不要对一个Activity Context保持长生命周期的引用。尽量在一切可以使用应用ApplicationContext代替Context的地方进行替换。
2.非静态内部类的静态实例容易造成内存泄漏;即一个类中如果你不能够控制它其中内部类的生命周期(譬如Activity中的一些特殊Handler等),则尽量使用静态类和弱引用来处理。
3.警惕线程未终止造成的内存泄露;譬如在Activity中关联了一个生命周期超过Activity的Thread,在退出Activity时切记结束线程。一个典型的例子就是HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了Activity生命周期,我们必须手动在Activity的销毁方法中中调运thread.getLooper().quit();才不会泄露。
4.对象的注册与反注册没有成对出现造成的内存泄露;譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。
5.创建与关闭没有成对出现造成的泄露;譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。
6.不要在执行频率很高的方法或者循环中创建对象,可以使用HashTable等创建一组对象容器从容器中取那些对象,而不用每次new与释放。
leakCanary原理:
主要是在 Activity 的onDestroy 方法中,手动调用 GC,然后利用 ReferenceQueue+WeakReference,来判断是否有释放不掉的引用,然后结合 dump memory 的 hpof 文件, 用 HaHa 分析出泄漏地方。