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

2020-03-03  本文已影响0人  CrazyOrr

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)。

优化要点:

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的渲染压力,导致渲染时间过长。

优化思路:

3.2 Rendering: RenderThread

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

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

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

参考文档

上一篇 下一篇

猜你喜欢

热点阅读