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
复用。
更进一步优化,你还可以对被嵌套的RecyclerView
的LinearLayoutManager
调用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
的最坏情况。
例如:
<?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:
-
Record View#draw
,在UI线程执行,调用每个invalidatedView
的draw(Canvas)
方法。 -
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#onDraw
、RecyclerView.Adapter#onBindViewHolder
等这类会被连续反复调用的方法中new
局部对象。
因为这会在短时间内生成大量生命周期短暂的对象,导致频繁GC,引发UI卡顿。
5. 耗时操作
使用AsyncTask
、Thread
、HandlerThread
、ThreadPoolExecutor
、IntentService
等手段将耗时操作移出UI线程。
注意:如果你自己开启线程,你应该调用Process.setThreadPriority()
并传入THREAD_PRIORITY_BACKGROUND
设置线程的priority为"background"。如果不这样做,你开启的线程仍然有可能拖慢你的app,因为默认情况下它与UI线程的优先级相同。
详情请见Thread priority
优化思路:
- 使用成员变量保存引用
- 使用对象池进行复用
6. 线程过多
显然,这对于app的性能是有负面影响的。线程再多,CPU的资源是有限的,CPU在同一时间能够运行的线程数是不多的,
其他所有的线程都只能等待,同时,每个线程至少需要占用64K的内存。过多的线程只会带来对内存和CPU资源的激烈竞争。
优化思路:建立线程池统一管理
网友评论