UI 定位卡顿 原因及解决方法,UI 界面性能优化

> 检测应用在UI线程的卡顿方式:
-- 如何检测应用在UI线程的卡顿,目前已经有两种比较典型方式来检测了:
1.利用UI线程Looper打印的日志
  https://github.com/markzhai/AndroidPerformanceMonitor
2.利用Choreographer
  https://github.com/wasabeef/Takt 
  https://github.com/friendlyrobotnyc/TinyDancer  

> UI卡顿的原因:
Android只有主线程才能更新UI。如果界面1秒钟刷新少于60次,即FPS小于60,用户就会产生卡顿感觉。
Android使用消息机制进行UI更新的,如果在主线程handler的dispatchMessage方法进行了耗时操作,就会发生UI卡顿。
CPU负责UI布局元素的Measure, Layout, Draw等相关运算执行. GPU负责栅格化(rasterization), 将UI元素绘制到屏幕上.
public class LogMonitor {
    private static LogMonitor sInstance = new LogMonitor();
    private HandlerThread mHandlerThread = new HandlerThread("log");
    private Handler mHandler;
 
    private LogMonitor() {
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
    }
 
    private static Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            StringBuilder sb = new StringBuilder();
            StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
            for (StackTraceElement s : stackTrace) {
                sb.append(s.toString() + "\n");
            }
            Log.e("TAG", sb.toString());
        }
    };
 
    public static LogMonitor getInstance() {
        return sInstance;
    }
 
    public void startMonitor() {
        mHandler.postDelayed(mRunnable, 1000);
    }
 
    public void removeMonitor() {
        mHandler.removeCallbacks(mRunnable);
    }
 
}

-- ANR Loop trace;trace文件
Android面试:主线程中的Looper.loop()一直无限循环为什么不会造成ANR?- https://blog.csdn.net/m0_37402140/article/details/78567991
通过debuggerd导出native进程trace信息- https://www.jianshu.com/p/871211c0a7f7
通过Android trace文件分析死锁ANR- https://www.cnblogs.com/0616--ataozhijia/p/4140998.html
  Android系统每次发生ANR后,都会在/data/anr/目录下面输出一个traces.txt文件,这个文件记录了发生问题进程的虚拟机相关信息和线程的堆栈信息,通过这个文件我们就能分析出当前线程正在做什么操作,继而可以分析出ANR的原因,它的生成与Signal Catcher线程是息息相关的,每一个从zygote派生出来的子进程都会有一个Signal Catcher线程,可以在终端的Shell环境下执行”ps -t &pid” 命令得到对应pid进程所有的子线程列表


  信号是一种Linux系统中进程间通信手段,Linux默认已经给进程的信号有处理,如果你不关心信号的话,默认系统行为就好了,但是如果你关心某些信号,例如段错误SIGSEGV(一般是空指针、内存访问越界的时候由系统发送给当事进程),那么你就得重新编写信号处理函数来覆盖系统默认的行为,这种机制对于程序调试来说是很重要的一种手段,因为像这种段错误是不可预知的,它可以发生在任何地方,也就是说在应用程序的代码里面是不能处理这种异常的,这个时候要定位问题的话,就只能依靠信号这种机制,虽然应用程序不知道什么时候发生了段错误,但是系统底层(Kernel)是知道的,Kernel发现应用程序访问了非法地址的时候,就会发送一个SIGSEGV信号给该进程,在该进程从内核空间返回到用户空间时会检测是否有信号等待处理,如果用户自定义了信号处理函数,那么这个时候就会调用用户编写的函数,这个时候就可以做很多事情了:例如dump当前进程的堆栈、获取系统的全局信息(内存、IO、CPU)等,而这些信息对分析问题是非常重要的。

-- ANR,生成TraceView日志的两种方式
1.第一种是通过代码生成
 开始的地方写代码 Debug.startMethodTracing();
 结束的地方写代码 Debug.stopMethodTracing();
 注意需要写入sdcard权限。
 日志会保存到/sdcard/dmtrace.trace,然后adb pull /sdcard/dmtrace.trace D:\dir拉下来。用Android Device Monitor打开就行。

2.第二种是通过操作Android Device Monitor的按钮生成
 打开Android Device Monitor,选中想观察的进程。点击Start Method Profiling,一通操作后,再点击Stop method Profiling。就是弹出这段时间操作的trace文件。
 强烈建议用这种方式,因为第一种方式有bug。

> Android App 优化之消除卡顿- https://blog.csdn.net/maxtung14/article/details/53260075

Android应用UI卡顿,性能优化完全分析:http://blog.csdn.net/yanbober/article/details/48394201
 打开GPU overDraw检测;频繁的GC,因此这里的优化实质上是内存优化,CPU使用率在Monitor中占比比较高。
StrictMode用来基于线程或VM设置一些策略, 一旦检测到策略违例, 控制台将输出一些警告,包含一个trace信息展示你的应用在何处出现问题.通常用来检测主线程中的磁盘读写或网络访问等耗时操作.
在Application或是Activity的onCreate中开启StrictMode:
 public void onCreate() {
     if (BuildConfig.DEBUG) {
         // 针对线程的相关策略
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()   // or .detectAll() for all detectable problems
                 .penaltyLog()
                 .build());
 
         // 针对VM的相关策略
         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                 .detectLeakedSqlLiteObjects()
                 .detectLeakedClosableObjects()
                 .penaltyLog()
                 .penaltyDeath()
                 .build());
     }
     super.onCreate();
 }

> ANR检测工具BlockCanary- https://github.com/moduth/blockcanary
BlockCanary对主线程操作进行了完全透明的监控,并能输出有效的信息,帮助开发分析、定位到问题所在,迅速优化应用。其特点有:
1.非侵入式,简单的两行就打开监控,不需要到处打点,破坏代码优雅性。 
2.精准,输出的信息可以帮助定位到问题所在(精确到行),不需要像Logcat一样,慢慢去找。 
3.目前包括了核心监控输出文件,以及UI显示卡顿信息功能。仅支持Android端。
 查找ANR的方式: 1. 导出/data/data/anr/traces.txt,找出函数和调用过程,分析代码 2. 通过性能LOG人肉查找

-- 定位卡顿问题的方法:
1.直观分析
2.Systrace
3.定制性能监控工具
用于捕获UI线程的crash- https://github.com/android-notes/Cockroach

-- Android UI卡顿原因及解决办法-https://blog.csdn.net/lf_hycz/article/details/76034457
 1.CPU优化建议,如何找出里面没用的view呢?或者减少不必要的view嵌套。
 我们利用工具:Hierarchy Viewer进行检测,优化思想是:查看自己的布局,层次是否很深以及渲染比较耗时,然后想办法能否减少层级以及优化每一个View的渲染时间。
 2.GPU优化建议就是一句话:尽量避免过度绘制(overdraw)
背景经常容易造成过度绘制。手机开发者选项里面找到工具:Debug GPU overdraw。
GPU太忙,说明DrawList太多,CPU太忙,说明要么主线程性能有问题,要么GPU太忙,来不及通知主线程。

-- Traceview是一个性能分析工具, 主要是分析当前线程情况, 各个方法执行时间等. 
 在Application的onCreate开始和结尾打上trace.
Debug.startMethodTracing("GithubApp");
...
Debug.stopMethodTracing();
运行程序, 会在sdcard上生成一个"GithubApp.trace"的文件.打开DDMS分析trace文件;分析trace文件

 -- 导致频繁GC有两个原因:
 1.内存抖动(Memory Churn), 即大量的对象被创建又在短时间内马上被释放.
 2.瞬间产生大量的对象会严重占用Young Generation的内存区域, 当达到阀值, 剩余空间不够的时候, 也会触发GC. 即使每次分配的对象需要占用很少的内存,但是他们叠加在一起会增加Heap的压力, 从而触发更多的GC.

  -- 引起 UI 卡顿的常见原因有如下几种:
1.主线程做了阻塞 UI 的耗时操作;
2.同一时刻动画执行多次导致 GPU 和 CPU 过度绘制;
3.View 过度绘制导致 GPU 和 CPU 过度绘制;
4.频繁地进行布局绘制、文本计算等操作导致 View 需要重新渲染;
5.频繁的对象创建和销毁;
6.过度复杂的业务逻辑,耗时函数。

-- 携程移动端 UI 界面性能优化实践-http://geek.csdn.net/news/detail/115422

-- 从 UI 优化、网络优化、通信数据格式传输优化、内存优化、启动时间优化、Hybrid 框架优化、React Native 优化等角度.
  进行 App 性能优化时,要遵循如下准则:
尽量保证每帧在 16ms 内处理完所有的 CPU 与 GPU 计算、绘制、渲染等操作,否则会造成丢帧卡顿问题。
基于上面的卡顿原理,我们知道所谓的卡顿其实是可以量化的,每次是否能够成功渲染是非常重要的问题,即 16ms 能否完整的做完一次操作直接决定了卡顿性能问题。
  
 UI卡顿的原因围绕着提高帧率、减少嵌套布局层次、减少对象创建等角度去解决问题的。
  Android 平台主要通过优化 Layout 布局层次角度:减少层级和 Overdraw、防止不必要的重新 Layout 和 Measure、加快界面显示速度、减少系统 GC 次数等措施去进行 UI 优化。
  由于过度绘制指在屏幕的一个像素上绘制多次(譬如一个设置了背景色的TextView就会被绘制两次,一次背景一次文本;这里需要强调的是Activity设置的Theme主题的背景不被算在过度绘制层级中),所以最理想的就是绘制一次,也就是蓝色(当然这在很多绚丽的界面是不现实的,所以大家有个度即可,我们开发性能优化标准要求:红色区域不能长期持续超过屏幕三分之一),因此我们需要依据此颜色分布进行代码优化,譬如优化布局层级、减少没必要的背景、暂时不显示的View设置为GONE而不是INVISIBLE、自定义View的onDraw方法设置canvas.clipRect()指定绘制区域或通过canvas.quickreject()减少绘制区域等措施去优化。
  通过删除无用的层级,或者对整个布局进行改造使用RealtiveLayout替换LinerLayout减少布局层级;此外,使用Merge标签或ViewStub标签来优化整个布局性能,比如一些显示错误界面、加载提示框界面等,不是必须显示的这些布局可以使用ViewStub标签来提升性能。

-- 加快界面加载:
  1.除了从 XML Layout 文件里面角度减少布局层级,还通过提前加载布局,即在线程中做一些必要的 inflate 等来提前初始化布局,减少实际显示时的耗时。对于一些复杂的布局,我们还会自己做View对象复用池,减少 inflate 带来的性能损耗,特别是在列表控件中。
  2.可以通过 TraceView 工具找出主线程的耗时操作和其他耗时的线程并作优化,另外减少主线程的 GC 停顿。因为即使并行 GC,也会对 heap 加锁,如果主线程请求分配内存的话,也会被挂起,所以尽量避免在主线程分配较多对象和较大的对象,特别是在onDraw等函数中,以减少被挂起的时间。另外可以通过去掉 ListView、ScrollView 等控件的 EdgeEffect 效果,来减少内存分配和加快控件的创建时间。
  3.利用本地缓存,主要界面缓存上次的数据,并配合增量的更新和删除,能做到数据和服务端同步,这样可以直接展示本地数据,不用等到网络返回数据。
  4.减少不必要的数据协议字段,减少名字长度等,并作压缩。还可以通过分页加载数据来加快传输解析时间。因为数据越大,传输和解析时间也会越久,引发的内存对象分配也会越多。
  5.注意线程的优先级,对于占用 CPU 较多时间的函数,也要判断线程的优先级。

-- 自定义控件防止重新布局
  在 ListView 滑动、广告动画变化等过程中,图片和文字有变化,经常会发现整个界面被重新布局,影响了性能。尤其布局复杂时,测量过程很费时,导致明显卡顿。比如对于大小基本固定的控件和布局例如 TextView、ImageView 来说,这是多余的损耗。采取优化措施,我们使用自定义控件来阻断,重写方法requestLayout、onSizeChanged,如果大小没有变化就阻断这次请求。对于 ViewPager 等广告条,可以设置缓存子 View 的数量为广告的数量。

-- UI优化方案:
1.Overdraw的检测
手机里面就能打开测试,设置 -> 开发者选项 -> 调试GPU过度绘制 -> 显示GPU过度绘制,从字面意思就可以看到,它是查看绘制过程中过度绘制严重程度。
(1)布局程度的调整,如果在布局中重复设置 background,就会出现过度绘制,例如,在设置列表listview背景为白色,background=“white”,但是想让条目也为白色,设置background为白色,这样就造成了过度绘制
(2)使用中设置background,例如在设置imageview的时候先设置一个预显示background,然后再设置
(3)自定义View中有些图片要叠加使用,就会造成过度绘制,使用clipRect能解决这类型问题。
2.Hierarchy Viewer 减少不必要的嵌套
3.Android标签的优化
 a.
如果在一个项目中需要用到相同的布局设计,可以通过 标签来重用layout代码, 
这样可以多次引用一个布局片段而不用重复的复制、粘贴。通过include标签也可以覆写一些属性的值,
 b.
标签在UI的结构优化中起着非常重要的作用,它可以删减多余的层级,优化UI。多用于替换FrameLayout或者当一个布局包含另一个时,标签消除视图层次结构中多余的视图组。例如你的主布局文件是垂直布局,引入了一个垂直布局的include,这是如果include布局使用的LinearLayout就没意义了,使用的话反而减慢你的UI表现。这时可以使用标签优化。
 c.
标签最大的优点是当你需要时才会加载,使用他并不会影响UI初始化时的性能。各种不常用的布局想进度条、显示错误消息等可以使用标签,以减少内存使用量,加快渲染速度。是一个不可见的,大小为0的View。

-- Android动画的FPS,24帧每秒;
 Android UI的FPS:使用shell 命令可以保存fps数据到本地文件,但是文件内容太简单,没多考参考意义;
shell命令格式如下:adb shell dumpsys gfxinfo "com.hlkt123.uplus" > d:\app_fps_log\ysms_fps.txt
备注:com.hltk123.uplus可以替换为你自己的报名;">"后面的是你要保存的log文件在PC 文件系统下的路径;

你可能感兴趣的:(性能优化与测试)