近來對之前做優(yōu)化學習記錄的一些知識點進行了以下簡單的總結,主要集中在以下幾個方面:
1.Systrace
2.嚴格模式
3.非保護性廣播
4.Event Log 中的性能問題
5.幀率優(yōu)化
6.冷啟動流程
7.其他常見的優(yōu)化技巧
8.ANR
1、Systrace
- 截至目前為止账嚎,最實用酸舍、分析最精準、最專業(yè)的工具,但上手門檻高
- Systrace分析重經驗亦渗,平時養(yǎng)成經嘲壮铮看systrace分析運行時間智末、線程,CPU工作頻率徒河,CPU 搶占等習慣系馆。
- TraceView雖然也能分析函數執(zhí)行時間,但相比systrace有諸多劣勢顽照,且對于密集函數呼叫可能會高估執(zhí)行時間由蘑,對跨進程調用可能會低估執(zhí)行時間,無法分析出并發(fā)的影響代兵。故不能作為性能分析的主要工具尼酿,且常會誤導解決方向。適合在systrace分析后植影,想進一步找線索時使用
建議配置
- Trace duration:3 ~ 10 秒
- Trace Buffer Size (kb):16384
- Commonly Used Tags:用默認
一般情況除非需要看WebView內部運作細節(jié)裳擎,不然WebView也可以關掉
- Advanced Options
必選:CPU Frequency、CPU Idle
其他按需
2思币、嚴格模式
嚴格模式能查出跨進程通訊而產生的磁盤讀寫鹿响,優(yōu)于代碼評審
解決掉嚴格模式問題有什么好處
- 減緩手機越用越慢或是突然卡頓的問題
- 提升應用啟動時間及減少ANR發(fā)生概率羡微。
數據庫存取時間長有一大部分來自以下三種原因:
- 數據庫資料持續(xù)增加,造成查詢/新增/刪除/修改時間拉長
- 數據庫存取遭遇其他應用同時存取磁盤惶我,而拉長完成時間
- 等待被其他應用以同步鎖鎖住的數據庫
跨進程調用產生的嚴格模式問題的日志樣式
- duration: 嚴格模式造成的時間延遲
- 可由堆棧第一個項判斷嚴格模式問題種類
- 在堆棧中出現“# via Binder call with stack”字樣代表該嚴格模式問題來自為跨進程調用
StrictModepolicy violation; ~duration=30ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=22085639 violation=2
at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1296)
at android.database.sqlite.SQLiteConnection.applyBlockGuardPolicy(SQLiteConnection.java:1044)
…
at android.content.ContentResolver.query(ContentResolver.java:562)
at com.android.internal.telephony.ISub$Stub.onTransact(ISub.java:124)
at android.os.Binder.execTransact(Binder.java:582)
# via Binder call with stack:
android.os.StrictMode$LogStackTrace
at android.os.StrictMode.readAndHandleBinderCallViolations(StrictMode.java:1963)
at android.os.Parcel.readExceptionCode(Parcel.java:1665)
…
at android.app.ActivityThread.main(ActivityThread.java:6470)
…
將磁盤存取移到子線程執(zhí)行妈倔。但須注意以下子線程的設置:
- 建議以HanderThread或AsyncTask,讓子線程中的磁盤讀取以隊列方式執(zhí)行
- AsyncTask優(yōu)先級設置默認是Process.THREAD_PRIORITY_BACKGROUND指孤。假如以默認的優(yōu)先級執(zhí)行使用AsyncTask讀取重要性高的資料時启涯,其將被分配到較少比例的處理器資源,而影響處理速度恃轩。建議處理重要資料時结洼,先調整AsyncTask的優(yōu)先級為Process.THREAD_PRIORITY_DEFAULT。
- 數據庫存取可考慮利用AsyncQueryHandler簡化子線程存取數據庫實作叉跛。AsyncQueryHandler的默認優(yōu)先級設置已經是Process.THREAD_PRIORITY_DEFAULT松忍,不需更動
- 非必要的話,避免以直接以new Thread()來執(zhí)行磁盤存取筷厘。這方法容易在高并發(fā)運行時鸣峭,產生大量線程拖慢系統(tǒng)速度
常見該解決的問題:在主線程調用SharedPreferences的commit()
Process: com.android.browser:service
Duration-Millis: 59
android.os.StrictMode$StrictModeDiskReadViolation: policy=647 violation=2
at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1137)
at libcore.io.BlockGuardOs.access(BlockGuardOs.java:67)
at java.io.File.doAccess(File.java:283)
at java.io.File.exists(File.java:363)
at android.app.SharedPreferencesImpl.writeToFile(SharedPreferencesImpl.java:567)
at android.app.SharedPreferencesImpl.access$800(SharedPreferencesImpl.java:51)
at android.app.SharedPreferencesImpl$2.run(SharedPreferencesImpl.java:512)
at android.app.SharedPreferencesImpl.enqueueDiskWrite(SharedPreferencesImpl.java:533)
at android.app.SharedPreferencesImpl.access$100(SharedPreferencesImpl.java:51)
at android.app.SharedPreferencesImpl$EditorImpl.commit(SharedPreferencesImpl.java:455) //commit
at com.tencent.mtt.widget.androidwidget.QBAppWidgetProvider.b(RQDSRC:275)
at com.tencent.mtt.widget.androidwidget.QBAppWidgetProvider.a(RQDSRC:158)
at com.tencent.mtt.widget.androidwidget.QBAppWidgetProvider$1.handleMessage(RQDSRC:137)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:147)
at android.app.ActivityThread.main(ActivityThread.java:5451)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:970)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:765)
使用SharedPreferences.Editor.apply() 取代SharedPreferences.Editor.commit() 以避免在主線程上寫入磁盤
- commit()直接產生磁盤存取于執(zhí)行的線程中,會產生嚴格模式問題
- apply()則產生子線程來執(zhí)行磁盤存取
常見該解決的問題:主線程調用SQLite相關操作
Process: com.android.phone
Duration-Millis: 44
android.os.StrictMode$StrictModeDiskReadViolation: policy=647 violation=2
at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1137)
at android.database.sqlite.SQLiteConnection.applyBlockGuardPolicy(SQLiteConnection.java:1046)
at android.database.sqlite.SQLiteConnection.executeForCursorWindow(SQLiteConnection.java:847)
at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:836)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:144)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133)
at android.content.ContentResolver.query(ContentResolver.java:506)
at android.content.ContentResolver.query(ContentResolver.java:426) //query
at com.qti.qualcomm.datastatusnotification.DataStatusNotificationService.onApnChanged(DataStatusNotificationService.at com.qti.qualcomm.datastatusnotification.DataStatusNotificationService.access$300(DataStatusNotificationService.java:at com.qti.qualcomm.datastatusnotification.DataStatusNotificationService$DataSettingsObserver.onChange(DataStatusNotification
at com.qti.qualcomm.datastatusnotification.DataStatusNotificationService.enableContentObservers(DataStatusNotificationService
at com.qti.qualcomm.datastatusnotification.DataStatusNotificationService.access$000(DataStatusNotificationService.java:at com.qti.qualcomm.datastatusnotification.DataStatusNotificationService$1.onQcRilHookReady(DataStatusNotificationService.at com.qualcomm.qcrilhook.QcRilHook$6.onServiceConnected(QcRilHook.java:1513)
at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1241)
at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1258)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:147)
at android.app.ActivityThread.main(ActivityThread.java:5451)
3酥艳、非保護性廣播
Android 針對有一些廣播只能由系統(tǒng)發(fā)送的摊溶,并且提供了<protected-broadcast> 標記讓系統(tǒng)級應用在AndroidManifest.xml明確宣告。在系統(tǒng)運作起來之后充石,如果某個不具有系統(tǒng)權限的應用試圖發(fā)送“保護性廣播”莫换,AMS會拋出異常,提示"Permission Denial: not allowed to send broadcast"骤铃。
- 系統(tǒng)級應用指的是Persistent 應用或user id 為SYSTEM_UID拉岁、PHONE_UID、SHELL_UID惰爬、BLUETOOTH_UID喊暖、NFC_UID的應用馒稍。
- 反之兽愤,系統(tǒng)級應用發(fā)出的broadcast 必須宣告成”保護性廣播”,否則會觸發(fā)system server 記錄WTF 時間而產生寫入dropbox的磁盤存取肥缔,間接導致應用的主線程卡頓
- 例外:凡是由谷歌风范、高通源代碼定義發(fā)送的廣播咨跌,即使有非保護性廣播問題,也不處理
StrictModepolicy violation; ~duration=236 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=22085639 violation=1
at android.os.StrictMode$AndroidBlockGuardPolicy.onWriteToDisk(StrictMode.java:1256)
…
at com.android.server.am.ActivityManagerService.addErrorToDropBox(ActivityManagerService.java:14477)
…
at android.util.Log.wtf(Log.java:300)at android.util.Log.wtf(Log.java:290)
at com.android.server.am.ActivityManagerService.checkBroadcastFromSystem(ActivityManagerService.java:18473)
…
# via Binder call with stack:
…
at com.android.internal.telephony.imsphone.ImsPhone.sendImsNetworkStateBroadcast(ImsPhone.java:1568)
…
解決方法:在應用的AndroidManifest.xml明確宣告成保護性廣播
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv=http://schemas.android.com/apk/prv/res/android
package="com.android.server.telecom"
coreApp="true"
android:sharedUserId="android.uid.system”>
…
<protected-broadcast android:name="android.intent.action.SHOW_MISSED_CALLS_NOTIFICATION" />
…
</manifest>
4硼婿、Event Log 中的性能問題
Log 格式可查詢/system/etc/event-log-tags
20003 dvm_lock_sample
(process|3),(main|1|5),(thread|3),(time|1|3),(file|3),(line|1|5),(ownerfile|3),(ownerline|1|5),(sample_percent|1|6)
52002 content_query_sample
(uri|3),(projection|3),(selection|3),(sortorder|3),(time|1|3),(blocking_package|3),(sample_percent|1|6)
52003 content_update_sample
(uri|3),(operation|3),(selection|3),(time|1|3),(blocking_package|3),(sample_percent|1|6)
52004 binder_sample
(descriptor|3),(method_num|1|5),(time|1|3),(blocking_package|3),(sample_percent|1|6)
- 查詢是否有運行時間長的binder transaction
I/binder_sample( 5245): [com.android.internal.appwidget.IAppWidgetService,16,588,com.android.systemui,100]
- 查詢是否有運行時間長的content provider 操作(可能還有bug)
I/content_query_sample(16701): [content://call_log/calls?allow_voicemails=true,_id/number/voicemail_uri,new = 1
AND type = ?,date DESC,906,com.android.contacts,100]
- 查詢是否等待synchronized 太久
I/dvm_lock_sample( 922): [system_server,1,PowerManagerService,523,PowerManagerService.java,1787,-,858,100]
- 界面啟動時間:am_activity_launch_time
1551 1573 I am_activity_launch_time: [0,248545817,com.android.browser/com.android.browser.BrowserLauncher,4432,4432]
//倒數第二個參數就是框架觸發(fā)這個界面啟動直到應用畫完第一幀的時間
界面啟動時間過長锌半,不僅使用者會感到卡頓,嚴重會導致Input/Key dispatch timeout ANR
代碼優(yōu)化要求:在一般功能性測試中,需在800ms 內完成刊殉。以systrace分析問題殉摔,找出主要耗時點才進行優(yōu)化。如:
- 簡化布局
- 簡化在主線程的長執(zhí)行時間的代碼
- 將磁盤讀寫移到子線程
- 若需要做跨進程通訊记焊,盡量移到子線程
- 數據庫查詢時間:content_query_sample
- 當數據庫查詢時間> 500ms 會印出
- 當數據庫查詢時間< 500ms 則隨機印出
- 跨進程通信執(zhí)行時間:binder_sample
- 當執(zhí)行時間> 500ms 會d印出
- 當執(zhí)行時間< 500ms 則隨機印出
如何找到接口編號對應的函數逸月?(以ITelephony的接口編號40為例)
- ROM 編譯完后,所有AIDL 編譯出的Java 代碼遍膜,放在out/target/common/obj下碗硬。直接在該文件夾以及子文件夾查找ITelephony.java 文件:
1、打開ITelephony.java文件
2瓢颅、找到FIRST_CALL_TRANSATION位置
3恩尾、所有的接口編號都由IBinder的第一個接口編號1號起算。在本例中挽懦,要找android.os.IBinder.FIRST_CALL_TRANSATION+39
- 某些模塊可能自行撰寫B(tài)inder 調用翰意,不使用AIDL。此種情況需要從模塊代碼尋找信柿。例如frameworks/base/core/java/android/app/IActivityManager.java
- 同步鎖等待時間:dvm_lock_sample冀偶,主線程不要被同步鎖卡住超過50ms。
- 等待時間> 500ms 會印出
- 等待時間< 500ms 則隨機印出
5渔嚷、幀率優(yōu)化
- 屏幕刷新率(Refresh):屏幕內在一秒刷新屏幕的速度
- 手機一般要求60Hz
- 幀率(Frame Rate):軟件系統(tǒng)一秒繪制的幀數
- 不同步問題
- 幀率 > 刷新率:導致畫面撕裂(screen tearing) 問題进鸠,有兩個或以上幀顯示在同一個frame buffer上
- 刷新率 > 幀率:如果畫面銜接不夠連續(xù),就會感覺卡頓形病。如果幀率= ? 刷新率堤如,穩(wěn)定輸出,感覺就沒那么明顯
- V-sync (Vertical Synchronization)
- 保證屏幕刷新過程窒朋,內容不會變更
- 屏幕刷新時,只從frame buffer 取
- Vsync信號來時蝗岖,才觸發(fā)上層軟件刷新back buffer
- 不管MDP/GPU 多快侥猩,都應適應屏幕刷新的速度
- Back buffer 可以超過一個
1000ms/60 frames = 16.666 ms / frame
//在Vsync機制下,想保持流暢的體驗抵赢,每幀必須在16.6ms 內完成
-
SurfaceFlinger
Choreographer
- 工作流程:請求Vsync→ 收到Vsync→ 請求Vsync→ 收到Vsync
- 如果沒有再次請求Vsync欺劳,則無法收到
- Rasterization(光柵化)
- Display List
- 記錄(Record) 一個View 的繪制需求
- 在HWUI內部處理,還需要進行其他的預處理铅鲤,例如判別顯示區(qū)域划提、產生HWlayer 等,才能轉換變成OpenGL 指令
- 只要View的屬性/內容不變邢享,就不需要重新產生Display List
改變的時機鹏往、頻率至關重要,多余的view invalidate骇塘、重新measure/layout 等伊履,都會觸發(fā)display list改變(CPU運算)韩容。有些對應的OpenGL會相當耗時,例如重新upload texture唐瀑、重新產生HW layer 等群凶。
- GPU 呈現模式分析
雖然工具叫GPU 呈現模式分析,但其實顯示的內容不只是GPU 工作時間
GPU 呈現模式分析用在什么場合
- 對解決問題而言哄辣,基本沒用请梢。信息太概略,無法知道問題在哪里力穗,Systrace才能分析根因
- 以發(fā)現問題的角度而言毅弧,容易造成測試與研發(fā)誤解,花時間優(yōu)化不重要的事
- 有些幀處理的時間稍長睛廊,未必導致卡頓形真。例如第一幀一般準備工作比較多。中間偶爾掉一幀超全,其實用戶看不出來
- 應用只要有界面刷新咆霜,工具就會顯示長條。不容易看出滑動嘶朱、動畫播放精準的開始與結束點
在Triple Buffer 架構下蛾坯,應用即使掉幀,也可能還有l(wèi)ayer buffer 能畫
上面的流程仔細體會疏遏。
- 硬件加速與渲染線程
UI 線程
- 每個View 透過draw()脉课,將繪制的需求,透過DisplayListCanvas财异,記錄在RenderNode里的DisplayList
渲染線程:兼顧性能優(yōu)化倘零、抽象層次
- Defer: 對RenderNode里DisplayList的渲染需求,進行前處理戳寸,主要是進行Batch與Merge呈驶,產生一群BatchBase,對應一群渲染操作疫鹊,減少后續(xù)GL draw call
- FrameBuilder:管理每幀的繪制任務袖瞻,調用各個LayerBuiler進行
- LayerBuilder:管理每幀中,某一層的繪制任務拆吆×可能對應目前的Surface 或一個FBO。將BatchBase透過BakedOpDispatcher調用到BakedOpRender枣耀、RenderState霉晕,再透過Glop真正執(zhí)行OpenGL 指令
以上的流程可以通過在systrace上加深體會。
- 避免過度繪制
- 避免大面積的過度繪制
- 去掉WindowBackground
- 若自行實現復雜的View,可在onDraw內娄昆,還可透過下列幾種方法佩微,減少過度繪制
使用Canvas.clipRect(float left, float top, float right, float buttom) :設置顯示范圍
- 使用Canvas.quickReject(float left, float top, float right, float bottom, EdgeTypetype) :若回傳true,代表參數所指定的矩形區(qū)域萌焰,完全在目前的canvas clip 外哺眯。此時就可忽略那些對應的渲染需求。
- 使用Hardware Layer
適用時機:布局復雜ViewGroup的大面積移動扒俯、移動過程中不會改變內容
- 觀念:將每幀做的復雜繪制運算奶卓,提前在動畫啟動前做一次
- 避免誤用:產生hardware layer是耗時操作,不應在動畫過程中改變或產生hardware layer撼玄。例如不應使用在ListViewitem
// 啟動動畫時設為hardware layer
viewGroup.setLayerType(View.LAYER_TYPE_HARDWARE, null);
// 動畫結束時夺姑,記得關掉hardware layer,釋放內存
viewGroup.setLayerType(View.LAYER_TYPE_NONE, null);
// View animation 過程中開啟hardware layer
ViewPropertyAnimator.alpha(0.5f).withLayer();
- 使用hardware layer必須保證動畫過程沒有內容更新掌猛,否則會造成掉幀
- 開發(fā)者選項 > 顯示硬件層更新
只要hardware layer 產生或變更內容盏浙,就會有綠色的閃爍
- Alpha 效果優(yōu)化
- View alpha 屬性的影響
必須先知道下層的元素是什么,再結合這個View進行混色處理- 盡可能避免大面積的alpha動畫
Alpha 效果常伴隨過度繪制的問題- 減少alpha效果的影響
setAlpha()會造成硬件加速呼叫saveLayer()進行復雜操作
呼叫setAlpha()的View優(yōu)化方向
- 不做其他繪制荔茬,例如設定background废膘、復寫onDraw()
- 復寫hasOverlappingRendering()并回傳false
- 產生hardware layer
- ListView優(yōu)化
- 較大圖片的加載,不適合在每個item的getView()中讀取慕蔚,會導致掉幀丐黄。一般解決做法是:
在子線程加載圖片。
若滑出這頁孔飒,則取消對應的圖片加載灌闺,停止滑動時,才將圖片設進ImageView坏瞄。
6桂对、冷啟動流程
- 前一個應用的pause 算在啟動時間內。如果是跨進程啟動鸠匀,pause 超過500ms 就會觸發(fā)pause timeout接校,強迫畫面切換。
- am_activity_launch_time只計算到IdleHandler觸發(fā)時的時間狮崩,并非完整的界面加載完時間。
- 從startActivity開始鹿寻,歷經前一個activity的pause睦柴,到下一個activity畫出第一個畫面的時間。故又稱display time毡熏。
- 若第一個activity用于跳轉坦敌,也就是onCreate執(zhí)行完就startActivity并finish自己,則會計算到下一個activity 畫出第一個畫面的時間。
- am_activity_fully_drawn_time
- 應用可自行在數據加載完狱窘,調用Activity.reportFullyDrawn()杜顺,告訴AMS 真正執(zhí)行完的時間,供自己調試用
- 這個跨進程調用可能會增加不必要的卡頓蘸炸,建議在子線程調用躬络。
- 提升啟動速度的方法:減少插入主線程執(zhí)行的handler、減少跨進程通信搭儒、減少下面幾個重點函數的執(zhí)行時間穷当。
- 在Application.onCreate() 初始化太多模塊,拖慢進程啟動時間淹禾。
- 在onCreate()馁菜、onResume()中post Message、Runnable到主線程铃岔,導致畫面繪制的Handler 被推遲執(zhí)行
- 不同模塊重復調用同樣的耗時接口汪疮,如AMS、WMS 接口毁习。
- 在主線程調用數據庫的讀智嚷、寫或執(zhí)行bindService、provider 等需要等待其他線程的操作蜓洪。
- 在主線程調用registerReceiver等容易被AMS耽誤執(zhí)行時間的接口纤勒。
- 在主線程初始化第三方SDK,而第三方SDK又有耗時操作隆檀。
7摇天、其他常見的優(yōu)化技巧
- 用System.arraycopy() 取代循環(huán)語句賦值
final int size = 1000000;
int[] array1 = new int[size];
int[] array2 = new int[size];
// 注意:size 大時,才有明顯效果
System.arraycopy(array1, 0, array2, 0, size);
//for (inti= 0 ; i< size ; ++i)
// array2[i] = array1[i];
- ArrayList恐仑、Vector泉坐、HashMap、HashSet等裳仆,可以在初始化時腕让,根據程序可能面對的數據量,指定初始容量歧斟,避免過程中的內存重新分配與內容復制纯丸。
ArrayList<Foo> a = new ArrayList<Foo>(20);
- 安卓不適合使用SoftReference。對象幾乎不會被GC静袖,反而導致內存問題
因為 SoftReference 無法提供足夠的信息可以讓 runtime 很輕松地決定 clear 它還是 keep 它觉鼻。 Android推薦使用android.util.LruCache做Cache管理。
- 減少GC
- 避免在關鍵路徑中队橙,頻繁分配內存坠陈,以減少GC 可能帶來的卡頓萨惑。例如:View.onDraw()中新建新對象。
過度采用這個策略仇矾,將對象宣告為static庸蔼,導致兩個問題:
- 內存無法釋放:有些Java對象乍看雖小,但native 分配的內存卻不小贮匕,例如將正則表達式Pattern宣告為static姐仅。
- static對象在類加載時就會初始化,即使最后代碼沒走到粗合,也會多花執(zhí)行時間萍嬉。
- 針對需要頻繁分配、釋放的小對象隙疚,采取Object Pool 方式優(yōu)化壤追,減少虛擬機分配內存時間、減少GC
- AsyncTask陷阱
- AsyncTask與Activity供屉、Fragment 的生命周期不同步行冰,不隨著onDestroy而消滅
- 若執(zhí)行任務長,可能會導致連續(xù)進出Activity伶丐、Fragment 的情況悼做,有太多線程運行,導致卡頓哗魂。更壞情況肛走,例如用AsyncTask執(zhí)行非常耗時的ContentProviderquery,可能連續(xù)進出幾次录别,就把provider 進程的Binder 線程全占滿
- 可能onPostExecute() 執(zhí)行時朽色,前一個Activity/Fragment 已經銷毀,現在運行的组题,可能需要的數據不同葫男。處理不好容易導致功能異常
- 自己撰寫的AsyncTask,若不是static 類崔列,隱性持有外部的Activity梢褐、Fragment,會導致他們無法被GC
- AsyncTask產生的子線程赵讯,默認的優(yōu)先級是THREAD_PRIORITY_BACKGROUND盈咳。若此任務需要盡快執(zhí)行,則這個優(yōu)先級會導致執(zhí)行完的時間不確定
- AsyncTask以ThreadPoolExecutor實現边翼,每個實例會產生線程池。應用中過多的AsyncTask實例讯私,會導致線程過多
- 避免應用在后臺執(zhí)行不必要的任務
- 在后臺斤寇,仍然觸發(fā)畫面刷新
- 同一個應用桶癣,多個Receiver 注冊同一個intent,如SCREEN_ON
- ContentObserver監(jiān)聽到Uri 變化后牙寞,就在后臺馬上query
- 舉例:遇到聯(lián)系人同步的情況莫秆,就是系統(tǒng)性災難
- 應該等到應用回到前臺间雀,才query
- 使用getInstalledApplications()、getInstalledPackages() 等類似的函數镊屎,讀取各應用的字符串缝驳,觸發(fā)大量I/O、CPU運算
- 線程優(yōu)先級不能亂設定
public class Process {
public static final intTHREAD_PRIORITY_DEFAULT = 0;
public static final intTHREAD_PRIORITY_LOWEST = 19;
public static final intTHREAD_PRIORITY_BACKGROUND = 10;
public static final intTHREAD_PRIORITY_FOREGROUND = -2;
public static final intTHREAD_PRIORITY_DISPLAY = -4;
public static final intTHREAD_PRIORITY_URGENT_DISPLAY = -8;
public static final intTHREAD_PRIORITY_AUDIO = -16;
public static final intTHREAD_PRIORITY_URGENT_AUDIO = -19;
public static final intTHREAD_PRIORITY_MORE_FAVORABLE = -1;
public static final intTHREAD_PRIORITY_LESS_FAVORABLE = +1;
}
- 設成THREAD_PRIORITY_BACKGROUND运怖、LOWEST 等夏伊,必須是不緊急的任務,不要求在UI立即顯示咏连。
- 應用不允許設成比THREAD_PRIORITY_DEFAULT 更高的優(yōu)先級砸狞,會影響自身與系統(tǒng)性能刀森、搶占CPU、導致許多莫名的時序問題出現埠偿。
- View 層級簡化
- ViewStub
- 分頁加載(處理Tab榜晦、ViewPager的情況)
原始做法:一次性產生所有Fragment 與其下所有的View乾胶。啟動性能差朽寞、且剛啟動時由于大量UI 在初始化化斩郎,主線程過多任務缩宜,應用容易卡頓
- 改進方法1:看不到的Fragment,推遲到開始滑動到那個Fragment 才產生
- 改進方法2:Fragment 先產生妓布,但View 推遲到開始滑動過去那個Fragment 才inflate宋梧、measure乃秀、layout
- 改進方法3:Fragment 先產生,而View 在子線程inflate枢贿。若有多個Fragment刀脏,則依照與目前可視的Fragment,由近向遠依次產生耀态、或滑動到相鄰頁才產生暂雹。但inflate 后杭跪,不能馬上加入View階層,否則馬上會觸發(fā)在UI線程的measure與layout系奉,導致卡頓姑廉。等到要開始滑動時桥言,才加入View 階層葵礼。
- Constraint Layout
- Bitmap
常是內存問題的來源
- 全屏ARGB 圖片:1920 x 1080 x 4 = 8294400 = 8 MB
- 1600萬像素照片:4608 x 3408 x 4 = 62816256 = 60MB
常是性能問題的來源
- 在UI線程加載大圖片
- 加載比實際顯示區(qū)域大的圖片
- 顯示區(qū)域與圖片大小不符
使用LRU (Least Recently Used) Cache
- 數據庫優(yōu)化
- 常運行章咧、或短時間內高頻度運行的類似語句能真,可以使用prepared statement扰柠,減少每次都要重新編譯SQL 語句再執(zhí)行的時間
- 減少transaction 次數
- bulkInsert() 的實現卤档,依賴自行控制transaction 提升性能
- 使用applyBatch(),能一次將不同種類的操作汤踏,打包處理
安卓默認的Provider.applyBatch() 實現舔腾,僅是一個個執(zhí)行稳诚,并未使用單次transaction 提升性能。
- applyBatch() 或bulkInsert() 單次調用才避,需要有筆數上限氨距,筆數太多俏让,需要拆分多個批次執(zhí)行。
- 單次binder transaction 允許傳遞的內存橱健,避免應用發(fā)生transaction fail
- 鎖住數據庫的時間太長沙廉,會導致其他應用無法訪問
- 建議:
- 傳遞數據量:不超過128 KB
- 執(zhí)行時間:不超過100ms
- 經驗:一般應用撬陵,一次小于50筆數據
- SQLite的index
- CPU調度
cpuctl (Android N 及之前默認開啟)
控制不同線程之間搶占 CPU 的分配
- Android以 cpuctl 機制,設計了 foreground scheduling 與 background scheduling网缝,當兩種線程同時競爭 CPU 資源時粉臊,采95:5 的方式驶兜,優(yōu)先分配給 foreground scheduling
- OOM_ADJ <= 1 的安卓應用:Foreground cpuset
- 其他安卓應用:Background cpuset
8抄淑、ANR
- 三種ANR
- Input (key) dispatching timeout:System_server 送出輸入事件到某窗口后,應用無法在 5秒內完成矗愧,讓框架通知 System_server 此次輸入事件已處理完成郑原。若在應用啟動過程犯犁,在畫出第一幀(也就是 am_activity_launch_time 出現之前)窗口是無法響應輸入的。
- Service timeout:System_server 送出 intent 到應用啟動 service春塌,過程中可能包含進程啟動簇捍、應用執(zhí)行 onCreate() + onStartCommand(),總時間無法在 X 秒內完成并通知System_server
- 應用在前臺時: X = 20 秒
- 應用在后臺時: X = 200 秒
- Broadcast timeout: System_server 送出 intent 到應用吼句,過程中可能包含進程啟動事格、應用
執(zhí)行 onReceive()驹愚,總時間無法在 Y 秒內完成并通知 System_server
- Foreground broadcast:Y = 10 秒
- 其他 broadcast:Y = 60 秒
假設在某段時間執(zhí)行如下:
從中可以看到導致ANR發(fā)生的原因可能并不是因為某個特定的服務或廣播時間超過限制了,而是由于主線程賽車導致的整體的時間的延長谁鳍。
所以有一個基本觀念就是:
- 主線程運行任務多倘潜、執(zhí)行時間長,導致 ANR 的可能性就越大
- ANR 解決的是主線程塞車的問題
由上可知,ANR是比較難解且無法從 log 得知為何執(zhí)行突然變慢废睦。到底是數據量大养泡、還是被其他進程卡住了澜掩、還是CPU 被其他應用搶占了、還是系統(tǒng)關核限頻了,目前的解法就是:
- 抓 systrace
- 錯誤的線程優(yōu)先級設定
就是優(yōu)先級倒轉問題点把,使得主線程等待子線程執(zhí)行
- 若子線程設成 THREAD_PRIORITY_BACKGROUND屿附,則更有可能導致 CPU 資源被搶走挺份。故無法避免情況,至少要將子線程設成 THREAD_PRIORITY_DEFAULT
- AsyncTask 默認的優(yōu)先級是 THREAD_PRIORITY_BACKGROUND优训,需要特別留意
下面看一個優(yōu)先級倒轉的例子
上圖中T1最先開始運行并獲得了R鎖揣非,之后T2躲因、T3大脉、T4也都運行,之后T3琐驴、T4等待R鎖,此時由于T1優(yōu)先級比較低安疗,按照上面我們講的95:5的CPU分配比例荐类,不需要R鎖的T2,就開始搶占T1運行的時間茁帽,導致T3、T4的等待時間將完全取決于低優(yōu)先級的T2吊输,如果此時還有更多的低優(yōu)先的線程在搶占資源那么T3季蚂、T4的執(zhí)行將會很糟糕琅束。
看段Log從中可以看到一些線程的設置信息
"OnLineMonitor" prio=5 tid=17 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x12e41dc0 self=0xe61fd200
| sysTid=14587 nice=0 cgrp=default sched=0/0 handle=0xcf3fb970
| state=S schedstat=( 303078149 1485237159 1965 ) utm=23 stm=7 core=2 HZ=100
| stack=0xcf2f9000-0xcf2fb000 stackSize=1038KB
| held mutexes=
kernel: (couldn't read /proc/self/task/14587/stack)
native: #00 pc 00018de0 /system/lib/libc.so (syscall+28)
native: #01 pc 000b76a1 /system/lib/libart.so
(art::ConditionVariable::WaitHoldingLocks(art::Thread*)+88)
native: #02 pc 003e6c89 /system/lib/libart.so (art::GoToRunnable(art::Thread*)+308)
native: #03 pc 003e6b21 /system/lib/libart.so (art::JniMethodEnd(unsigned int, art::Thread*)+8)
native: #04 pc 0075d969 /system/framework/arm/boot-framework.oat
(Java_android_os_BinderProxy_transactNative__ILandroid_os_Parcel_2Landroid_os_Parcel_2I+144)
at android.os.BinderProxy.transactNative(Native method)
at android.os.BinderProxy.transact(Binder.java:793)
at android.app.IActivityManager$Stub$Proxy.getProcessMemoryInfo(IActivityManager.java:6324)
at java.lang.reflect.Method.invoke(Native method)
at c8.ky._1invoke(Interception.java:-1)
- nice=0 代表 THREAD_PRIORITY_DEFAULT, nice = 10 代表 THREAD_PRIORITY_BACKGROUND
- cgrp=defult 代表 foreground CPU scheduling料滥,cgrp=bg_non_interactive 代表 background CPU scheduling
- 在主線程調用系統(tǒng)服務艾船,例如 AMS、WMS践宴、PMS 等浴井,可能在系統(tǒng)高負載霉撵、高并發(fā)運行下遇到ANR徒坡,原因在于這些接口一般是阻塞式的,而安卓框架內有太多同步鎖伦泥、彼此影響。故主線程應盡量減少這些接口的調用
常見可能會變得很慢的調用:registerReceiver府怯、sendBroadcast