常见卡顿来源及其优化方法

1. 可滚动列表

1.1 RecyclerView: notifyDataSetChanged

非必要不要全量更新,可以考虑使用DiffUtil实现差量更新。

void onNewDataArrived(List news) {
    List oldNews = myAdapter.getItems();
    DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news));
    myAdapter.setNews(news);
    result.dispatchUpdatesTo(myAdapter);
}

MyCallback需要实现DiffUtil.Callback

1.2 RecyclerView: 嵌套RecyclerView

例如一个纵向的RecyclerView嵌套多个横向的RecyclerView时,如果这些被嵌套的RecyclerView的itemView都是相似的,
那么我们可以在这些被嵌套的RecyclerView之间共享RecyclerView.RecycledViewPool,
使得这些被嵌套的RecyclerView的itemView跨RecyclerView复用。

更进一步优化,你还可以对被嵌套的RecyclerViewLinearLayoutManager调用setInitialPrefetchItemCount(int)
来预加载itemView。

1.3 RecyclerView: Inflation太多

减少非必要的 view type(同一view type的itemView才能复用)

1.4 ListView: Inflation太多

getView里的convertView复用

1.5 RecyclerView 或者 ListView: layout / draw 太慢

见后文 2.2 Layout 和 2.3 Rendering

2. Layout

2.1 Layout: 耗时长

如果layout的耗时大于几毫秒,你有可能碰到了嵌套RelativeLayout或者带有weight的LinearLayout的最坏情况。

例如:




  

      

      

  

  


第1层 第2层 第3层
View元素 LinearLayout LinearLayout CustomView
measure次数 1 1 x 2 = 2 1 x 2 x 2 = 4

可以看到,在这种情况下,每增加1层嵌套,CustomView的measure次数就多乘1次2,
也就是说,被嵌套的View的measure次数m随着嵌套深度n的增长呈指数级增长(m=2^n)。

优化要点:

  • 尽量只在最低层级的叶子节点上使用RelativeLayout或者带有weight的LinearLayout,也就是只有1层嵌套
  • 尝试使用ConstraintLayout实现类似的效果

2.2 Layout: 过于频繁

Layout应当在屏幕上显示新的内容时发生,比如当RecyclerView的一个新的item滚动进入可见区域。
如果每一帧都发生大量的layout,那么有可能是你对layout做了动画处理。
一般来说,动画应该运行在view的drawing properties(例如setTranslationX/Y/Z(), setRotation(), setAlpha()等)上,
而不是改变起来代价更大的layout properties(例如padding, margin)。

3. Rendering

Android UI分两步完成rendering:

  1. Record View#draw,在UI线程执行,调用每个invalidated Viewdraw(Canvas)方法。
  2. DrawFrame,在RenderThread根据上一步的结果执行。

3.1 Rendering: UI线程

3.1.1 bitmap

避免在UI线程绘制bitmap。

3.1.2 避免过度绘制

过度绘制就是在同一帧情况下对同一块像素区域进行重复绘制。这样会加重GPU跟CPU的渲染压力,导致渲染时间过长。

优化思路:

  • 移除(不可见的)window的背景

    @null
    
  • 移除控件中不需要的背景

    • 对于子控件,如果其背景颜色跟父布局一致,那么就不用再给子控件添加背景了
    • 如果子控件背景五颜六色,且能够完全覆盖父布局,那么父布局就可以不用添加背景了
  • 减少透明度的使用

    比如:在TextView上设置带透明度alpha值的黑色文本可以实现灰色的效果。但是,直接通过设置灰色的话能够获得更好的性能

  • 减少布局的嵌套层级

  • 使用merge标签减少布局层级

  • 使用ViewStub标签懒加载

  • 减少自定义View的过度绘制,使用clipRect()只绘制可见区域

3.2 Rendering: RenderThread

  • 有些Canvas操作虽然在第一步record时很廉价,但会在第二步触发昂贵但计算。
  • Bitmap是作为OpenGL texture显示的,首次显示时,它会被上传到GPU。因此,如果Bitmap明显的大于显示所需,就会浪费上传时间和内存。

目前用的比较少,理解不深入,详情请见官方说明

4. 频繁GC

尽管在Android 5.0引入ART后此问题已经大大缓解,但是我们还是需要注意,
避免在View#onDrawRecyclerView.Adapter#onBindViewHolder等这类会被连续反复调用的方法中new局部对象。
因为这会在短时间内生成大量生命周期短暂的对象,导致频繁GC,引发UI卡顿。

5. 耗时操作

使用AsyncTaskThreadHandlerThreadThreadPoolExecutorIntentService等手段将耗时操作移出UI线程。

注意:如果你自己开启线程,你应该调用Process.setThreadPriority()
并传入THREAD_PRIORITY_BACKGROUND
设置线程的priority为"background"。如果不这样做,你开启的线程仍然有可能拖慢你的app,因为默认情况下它与UI线程的优先级相同。

详情请见Thread priority

优化思路:

  • 使用成员变量保存引用
  • 使用对象池进行复用

6. 线程过多

显然,这对于app的性能是有负面影响的。线程再多,CPU的资源是有限的,CPU在同一时间能够运行的线程数是不多的,
其他所有的线程都只能等待,同时,每个线程至少需要占用64K的内存。过多的线程只会带来对内存和CPU资源的激烈竞争。

优化思路:建立线程池统一管理

参考文档

  • App startup time
  • Slow rendering
  • Frozen frames
  • Keeping your app responsive
  • Better performance through threading

你可能感兴趣的:(常见卡顿来源及其优化方法)