1. 可滾動(dòng)列表
1.1 RecyclerView: notifyDataSetChanged
非必要不要全量更新,可以考慮使用DiffUtil實(shí)現(xiàn)差量更新。
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需要實(shí)現(xiàn)DiffUtil.Callback
1.2 RecyclerView: 嵌套R(shí)ecyclerView
例如一個(gè)縱向的RecyclerView嵌套多個(gè)橫向的RecyclerView時(shí),如果這些被嵌套的RecyclerView的itemView都是相似的,
那么我們可以在這些被嵌套的RecyclerView之間共享RecyclerView.RecycledViewPool,
使得這些被嵌套的RecyclerView的itemView跨RecyclerView復(fù)用。
更進(jìn)一步優(yōu)化,你還可以對(duì)被嵌套的RecyclerView的LinearLayoutManager調(diào)用setInitialPrefetchItemCount(int)
來預(yù)加載itemView。
1.3 RecyclerView: Inflation太多
減少非必要的 view type(同一view type的itemView才能復(fù)用)
1.4 ListView: Inflation太多
getView里的convertView復(fù)用
1.5 RecyclerView 或者 ListView: layout / draw 太慢
見后文 2.2 Layout 和 2.3 Rendering
2. Layout
2.1 Layout: 耗時(shí)長(zhǎng)
如果layout的耗時(shí)大于幾毫秒,你有可能碰到了嵌套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次數(shù) | 1 | 1 x 2 = 2 | 1 x 2 x 2 = 4 |
可以看到,在這種情況下,每增加1層嵌套,CustomView的measure次數(shù)就多乘1次2,
也就是說,被嵌套的View的measure次數(shù)m隨著嵌套深度n的增長(zhǎng)呈指數(shù)級(jí)增長(zhǎng)(m=2^n)。
優(yōu)化要點(diǎn):
- 盡量只在最低層級(jí)的葉子節(jié)點(diǎn)上使用
RelativeLayout或者帶有weight的LinearLayout,也就是只有1層嵌套 - 嘗試使用
ConstraintLayout實(shí)現(xiàn)類似的效果
2.2 Layout: 過于頻繁
Layout應(yīng)當(dāng)在屏幕上顯示新的內(nèi)容時(shí)發(fā)生,比如當(dāng)RecyclerView的一個(gè)新的item滾動(dòng)進(jìn)入可見區(qū)域。
如果每一幀都發(fā)生大量的layout,那么有可能是你對(duì)layout做了動(dòng)畫處理。
一般來說,動(dòng)畫應(yīng)該運(yùn)行在view的drawing properties(例如setTranslationX/Y/Z(), setRotation(), setAlpha()等)上,
而不是改變起來代價(jià)更大的layout properties(例如padding, margin)。
3. Rendering
Android UI分兩步完成rendering:
-
Record View#draw,在UI線程執(zhí)行,調(diào)用每個(gè)invalidatedView的draw(Canvas)方法。 -
DrawFrame,在RenderThread根據(jù)上一步的結(jié)果執(zhí)行。
3.1 Rendering: UI線程
3.1.1 bitmap
避免在UI線程繪制bitmap。
3.1.2 避免過度繪制
過度繪制就是在同一幀情況下對(duì)同一塊像素區(qū)域進(jìn)行重復(fù)繪制。這樣會(huì)加重GPU跟CPU的渲染壓力,導(dǎo)致渲染時(shí)間過長(zhǎng)。
優(yōu)化思路:
-
移除(不可見的)window的背景
<item name="android:windowBackground">@null</item> -
移除控件中不需要的背景
- 對(duì)于子控件,如果其背景顏色跟父布局一致,那么就不用再給子控件添加背景了
- 如果子控件背景五顏六色,且能夠完全覆蓋父布局,那么父布局就可以不用添加背景了
-
減少透明度的使用
比如:在TextView上設(shè)置帶透明度alpha值的黑色文本可以實(shí)現(xiàn)灰色的效果。但是,直接通過設(shè)置灰色的話能夠獲得更好的性能
減少布局的嵌套層級(jí)
使用merge標(biāo)簽減少布局層級(jí)
使用
ViewStub標(biāo)簽懶加載減少自定義View的過度繪制,使用clipRect()只繪制可見區(qū)域
3.2 Rendering: RenderThread
- 有些Canvas操作雖然在第一步record時(shí)很廉價(jià),但會(huì)在第二步觸發(fā)昂貴但計(jì)算。
- Bitmap是作為OpenGL texture顯示的,首次顯示時(shí),它會(huì)被上傳到GPU。因此,如果Bitmap明顯的大于顯示所需,就會(huì)浪費(fèi)上傳時(shí)間和內(nèi)存。
目前用的比較少,理解不深入,詳情請(qǐng)見官方說明
4. 頻繁GC
盡管在Android 5.0引入ART后此問題已經(jīng)大大緩解,但是我們還是需要注意,
避免在View#onDraw、RecyclerView.Adapter#onBindViewHolder等這類會(huì)被連續(xù)反復(fù)調(diào)用的方法中new局部對(duì)象。
因?yàn)檫@會(huì)在短時(shí)間內(nèi)生成大量生命周期短暫的對(duì)象,導(dǎo)致頻繁GC,引發(fā)UI卡頓。
5. 耗時(shí)操作
使用AsyncTask、Thread、HandlerThread、ThreadPoolExecutor、IntentService等手段將耗時(shí)操作移出UI線程。
注意:如果你自己開啟線程,你應(yīng)該調(diào)用Process.setThreadPriority()
并傳入THREAD_PRIORITY_BACKGROUND
設(shè)置線程的priority為"background"。如果不這樣做,你開啟的線程仍然有可能拖慢你的app,因?yàn)槟J(rèn)情況下它與UI線程的優(yōu)先級(jí)相同。
詳情請(qǐng)見Thread priority
優(yōu)化思路:
- 使用成員變量保存引用
- 使用對(duì)象池進(jìn)行復(fù)用
6. 線程過多
顯然,這對(duì)于app的性能是有負(fù)面影響的。線程再多,CPU的資源是有限的,CPU在同一時(shí)間能夠運(yùn)行的線程數(shù)是不多的,
其他所有的線程都只能等待,同時(shí),每個(gè)線程至少需要占用64K的內(nèi)存。過多的線程只會(huì)帶來對(duì)內(nèi)存和CPU資源的激烈競(jìng)爭(zhēng)。
優(yōu)化思路:建立線程池統(tǒng)一管理