美文网首页
常见卡顿来源及其优化方法

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

作者: CrazyOrr | 来源:发表于2020-03-03 14:00 被阅读0次

    1. 可滚动列表

    1.1 RecyclerView: notifyDataSetChanged

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

    void onNewDataArrived(List<News> news) {
        List<News> 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 Layout2.3 Rendering

    2. Layout

    2.1 Layout: 耗时长

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

    例如:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal"
      tools:context=".jankoptim.ConstraintLayoutActivity">
    
      <LinearLayout
          android:layout_width="0dp"
          android:layout_height="100dp"
          android:layout_weight="1"
          android:orientation="horizontal">
    
          <com.example.playground.view.CustomView
              android:layout_width="20dp"
              android:layout_height="20dp"
              android:background="@color/colorAccent" />
    
          <View
              android:layout_width="30dp"
              android:layout_height="10dp"
              android:background="@color/colorPrimary" />
    
      </LinearLayout>
    
      <View
          android:layout_width="0dp"
          android:layout_height="10dp"
          android:layout_weight="1"
          android:background="@color/colorPrimaryDark" />
    
    </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的背景

      <item name="android:windowBackground">@null</item>
      
    • 移除控件中不需要的背景

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

      比如:在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资源的激烈竞争。

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

    参考文档

    相关文章

      网友评论

          本文标题:常见卡顿来源及其优化方法

          本文链接:https://www.haomeiwen.com/subject/ywmclhtx.html