Android性能問題分析與優(yōu)化

近來對之前做優(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 則隨機印出
image.png
  • 跨進程通信執(zhí)行時間:binder_sample
  • 當執(zhí)行時間> 500ms 會d印出
  • 當執(zhí)行時間< 500ms 則隨機印出
image.png

如何找到接口編號對應的函數逸月?(以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
image.png
image.png
image.png
  • 同步鎖等待時間:dvm_lock_sample冀偶,主線程不要被同步鎖卡住超過50ms。
  • 等待時間> 500ms 會印出
  • 等待時間< 500ms 則隨機印出
image.png

5渔嚷、幀率優(yōu)化

  • 屏幕刷新率(Refresh):屏幕內在一秒刷新屏幕的速度
  • 手機一般要求60Hz
  • 幀率(Frame Rate):軟件系統(tǒng)一秒繪制的幀數
  • 不同步問題
  • 幀率 > 刷新率:導致畫面撕裂(screen tearing) 問題进鸠,有兩個或以上幀顯示在同一個frame buffer上
  • 刷新率 > 幀率:如果畫面銜接不夠連續(xù),就會感覺卡頓形病。如果幀率= ? 刷新率堤如,穩(wěn)定輸出,感覺就沒那么明顯
image.png
  • 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


    image.png
  • Choreographer

  • 工作流程:請求Vsync→ 收到Vsync→ 請求Vsync→ 收到Vsync
  • 如果沒有再次請求Vsync欺劳,則無法收到
image.png
  • Rasterization(光柵化)
image.png
  • Display List
  • 記錄(Record) 一個View 的繪制需求
  • 在HWUI內部處理,還需要進行其他的預處理铅鲤,例如判別顯示區(qū)域划提、產生HWlayer 等,才能轉換變成OpenGL 指令
  • 只要View的屬性/內容不變邢享,就不需要重新產生Display List

改變的時機鹏往、頻率至關重要,多余的view invalidate骇塘、重新measure/layout 等伊履,都會觸發(fā)display list改變(CPU運算)韩容。有些對應的OpenGL會相當耗時,例如重新upload texture唐瀑、重新產生HW layer 等群凶。

image.png
  • GPU 呈現模式分析

雖然工具叫GPU 呈現模式分析,但其實顯示的內容不只是GPU 工作時間

image.png

GPU 呈現模式分析用在什么場合

  • 對解決問題而言哄辣,基本沒用请梢。信息太概略,無法知道問題在哪里力穗,Systrace才能分析根因
  • 以發(fā)現問題的角度而言毅弧,容易造成測試與研發(fā)誤解,花時間優(yōu)化不重要的事
  • 有些幀處理的時間稍長睛廊,未必導致卡頓形真。例如第一幀一般準備工作比較多。中間偶爾掉一幀超全,其實用戶看不出來
  • 應用只要有界面刷新咆霜,工具就會顯示長條。不容易看出滑動嘶朱、動畫播放精準的開始與結束點

在Triple Buffer 架構下蛾坯,應用即使掉幀,也可能還有l(wèi)ayer buffer 能畫


image.png

上面的流程仔細體會疏遏。

  • 硬件加速與渲染線程

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 指令
image.png
image.png

以上的流程可以通過在systrace上加深體會。

  • 避免過度繪制
  • 避免大面積的過度繪制
  • 去掉WindowBackground
  • 若自行實現復雜的View,可在onDraw內娄昆,還可透過下列幾種方法佩微,減少過度繪制
  • 使用Canvas.clipRect(float left, float top, float right, float buttom) :設置顯示范圍


    image.png
  • 使用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又有耗時操作隆檀。
image.png

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
image.png
  • 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í)行如下:


image.png

從中可以看到導致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)先級倒轉的例子

image.png

上圖中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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末牺丙,一起剝皮案震驚了整個濱河市冲簿,隨后出現的幾起案子亿昏,更是在濱河造成了極大的恐慌角钩,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惨险,死亡現場離奇詭異,居然都是意外死亡睹欲,警方通過查閱死者的電腦和手機窘疮,發(fā)現死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門闸衫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弟翘,你說我怎么就攤上這事稀余∏鞣” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵历等,是天一觀的道長辟癌。 經常有香客問我愿待,道長,這世上最難降的妖魔是什么要出? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任患蹂,我火速辦了婚禮砸紊,結果婚禮上醉顽,老公的妹妹穿的比我還像新娘。我一直安慰自己系草,他們只是感情好找都,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布能耻。 她就那樣靜靜地躺著亡驰,像睡著了一般凡辱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帕涌,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天蚓曼,我揣著相機與錄音,去河邊找鬼床绪。 笑死其弊,一個胖子當著我的面吹牛梭伐,可吹牛的內容都是我干的。 我是一名探鬼主播绩社,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼愉耙,長吁一口氣:“原來是場噩夢啊……” “哼朴沿!你這毒婦竟也來了败砂?” 一聲冷哼從身側響起吠卷,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤祭隔,失蹤者是張志新(化名)和其女友劉穎路操,沒想到半個月后屯仗,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡桩撮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年店量,在試婚紗的時候發(fā)現自己被綠了融师。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡舀射,死狀恐怖脆烟,靈堂內的尸體忽然破棺而出浩淘,到底是詐尸還是另有隱情吴攒,我是刑警寧澤洼怔,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站极谊,受9級特大地震影響轻猖,放射性物質發(fā)生泄漏域那。R本人自食惡果不足惜次员,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一淑蔚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧醋寝,春花似錦、人聲如沸柿究。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽民镜。三九已至制圈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慧库,已是汗流浹背馋嗜。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工甘磨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留眯停,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像九府,于是被迫代替她去往敵國和親侄旬。 傳聞我的和親對象是個殘疾皇子煌妈,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容