衆所周知,移動開發已經來到了後半場,爲了可以在衆多開發者中脫穎而出,咱們須要對某一個領域有深刻地研究與心得,對於Android開發者來講,目前,有幾個好的細分領域值得咱們去創建本身的技術壁壘,以下所示:html
在上述幾個細分領域中,最難也最具技術壁壘的莫過於性能優化,要想成爲一個頂尖的性能優化專家,須要對許多領域的深度知識及廣度知識有深刻的瞭解與研究,其中不乏須要掌握架構師、NDK、Flutter所涉及的衆多技能。從這篇文章開始,筆者將會帶領你們一步一步深刻探索Android的性能優化。前端
爲了可以全面地瞭解Android的性能優化知識體系,咱們先看看我總結的下面這張圖,以下所示:linux
要作好應用的性能優化,咱們須要創建一套成體系的性能優化方案,這套方案被業界稱爲 APM(Application Performance Manange),爲了讓你們快速瞭解APM涉及的相關知識,筆者已經將其總結成圖,以下所示:android
在建設APM和對App進行性能優化的過程當中,咱們必須首先解決的是App的穩定性問題,如今,讓咱們搭乘航班,來深刻探索Android穩定性優化的疆域。git
首先,咱們必須對App的穩定性有正確的認識,它是App質量構建體系中最基本和最關鍵的一環。若是咱們的App不穩定,而且常常不能正常地提供服務,那麼用戶大機率會卸載掉它。因此穩定性很重要,而且Crash是P0優先級,須要優先解決。 並且,穩定性可優化的面很廣,它不只僅只包含Crash這一部分,也包括卡頓、耗電等優化範疇。github
應用的穩定性能夠分爲三個緯度,以下所示:面試
咱們在作應用的穩定性優化的時候,須要注意三個要點,以下所示:算法
對於穩定性來講,若是App已經到了線上才發現異常,那其實已經形成了損失,因此,對於穩定性的優化,其重點在於預防。從開發同窗的編碼環節,到測試同窗的測試環節,以及到上線前的發佈環節、上線後的運維環節,這些環節都須要來預防異常狀況的發生。若是異常真的發生了,也須要將千方百計將損失降到最低,爭取用最小的代價來暴露儘量多的問題。shell
此外,監控也是必不可少的一步,預防作的再好,到了線上,總會有各類各樣的異常發生。因此,不管如何,咱們都須要有全面的監控手段來更加靈敏地發現問題。json
當咱們看到了一個Crash的時候,不能簡單地只處理這一個Crash,而是須要思考更深一層,要考慮會不會在其它地方會有同樣的Crash類型發生。若是有這樣的狀況,咱們必須對其統一處理和預防。
此外,咱們還要關注Crash相關的隱含信息,好比,在面試過程中,面試官問你,大家應用的Crash率是多少,這個問題代表上問的是Crash率,可是實際上它是問你一些隱含信息的,太高的Crash率就表明開發人員的水平不行,leader的架構能力不行,項目的各個階段中優化的空間很是大,這樣一來,面試官對你的印象和評價也不會好。
應用穩定性的建設過程是一個細活,因此很容易出現這個版本優化好了,可是在接下來的版本中若是咱們無論它,它就會發生持續惡化的狀況,所以,咱們必須從項目研發的每個流程入手,創建科學完善的相關規範,才能保證長效的優化效果。
要對應用的穩定性進行優化,咱們就必須先了解與Crash相關的一些指標。
那麼,咱們App的Crash率下降多少才能算是一個正常水平或優秀的水平呢?
這裏咱們還須要關注Crash相關的關鍵問題,若是應用發生了Crash,咱們應該儘量還原Crash現場。所以,咱們須要全面地採集應用發生Crash時的相關信息,以下所示:
接着,採集完上述信息並上報到後臺後,咱們會在APM後臺進行聚合展現,具體的展現信息以下所示:
最後,咱們能夠根據以上信息決定Crash是否須要立馬解決以及在哪一個版本進行解決,關於APM聚合展現這塊能夠參考 Bugly平臺 的APM後臺聚合展現。
而後,咱們再來看看與Crash相關的總體架構。
APM Crash部分的總體架構從上之下分爲採集層、處理層、展現層、報警層。下面,咱們來詳細講解一下每一層所作的處理。
首先,咱們須要在採集層這一層去獲取足夠多的Crash相關信息,以確保可以精肯定位到問題。須要採集的信息主要爲以下幾種:
而後,在處理層,咱們會對App採集到的數據進行處理。
通過處理層以後,就會來到展現層,展現的信息爲以下幾類:
最後,就會來到報警層,當發生嚴重異常的時候,會通知相關的同窗進行緊急處理。報警的規則咱們能夠自定義,例如總體的Crash率,其環比(與上一期進行對比)或同比(如本月10號與上月10號)抖動超過5%,或者是單個Crash忽然間激增。報警的方式能夠經過 郵件、IM、電話、短信 等等方式。
最後,咱們來看下Crash相關的非技術問題,須要注意的是,咱們要解決的是如何長期保持較低的Crash率這個問題。咱們須要保證可以迅速找到相關bug的相關責任人並讓開發同窗可以及時地處理線上的bug。具體的解決方法爲以下幾種:
對與單個Crash的處理方案咱們能夠按以下三個步驟來進行解決處理。
要對應用的Crash率進行治理,通常須要對如下三種類型的Crash進行對應的處理,以下所示:
出現未捕獲異常,致使出現異常退出
Thread.setDefaultUncaughtExceptionHandler();
複製代碼
咱們經過設置自定義的UncaughtExceptionHandler,就能夠在崩潰發生的時候獲取到現場信息。注意,這個鉤子是針對單個進程而言的,在多進程的APP中,監控哪一個進程,就須要在哪一個進程中設置一遍ExceptionHandler。
獲取主線程的堆棧信息:
Looper.getMainLooper().getThread().getStackTrace();
複製代碼
獲取當前線程的堆棧信息:
Thread.currentThread().getStackTrace();
複製代碼
獲取所有線程的堆棧信息:
Thread.getAllStackTraces();
複製代碼
第三方Crash監控工具如 Fabric、騰訊Bugly,都是以字符串拼接的方式將數組StackTraceElement[]轉換成字符串形式,進行保存、上報或者展現。
對此,咱們通常有兩種可選的處理方案,以下所示:
logcat日誌流程是這樣的,應用層 --> liblog.so --> logd,底層使用 ring buffer 來存儲數據。獲取的方式有如下三種:
經過hook liblog.so 中的 __android_log_buf_write 方法,將內容重定向到本身的buffer中。
當發生native崩潰時,咱們經過unwind只能拿到Native堆棧。咱們但願能夠拿到當時各個線程的Java堆棧。對於這個問題,目前有兩種處理方式,分別以下所示:
簡單,兼容性好。
經過hook ThreadList和Thread 的函數,得到跟ANR同樣的堆棧。爲了穩定性,須要在fork的子進程中執行。
講解了Java Crash相關的知識後,咱們就能夠去了解下Java Crash的處理流程,這裏借用Gityuan流程圖進行講解,以下圖所示:
這裏咱們還須要瞭解下binder 死亡通知的原理,其流程圖以下所示:
因爲Crash進程中擁有一個Binder服務端ApplicationThread,而應用進程在建立過程調用attachApplicationLocked(),從而attach到system_server進程,在system_server進程內有一個ApplicationThreadProxy,這是相對應的Binder客戶端。當Binder服務端ApplicationThread所在進程(即Crash進程)掛掉後,則Binder客戶端能收到相應的死亡通知,從而進入binderDied流程。
特色:
上述都會產生相應的signal信號,致使程序異常退出。
一個合格的異常捕獲組件須要包含如下功能:
Native崩潰捕獲的過程涉及到三端,這裏咱們分別來了解下其對應的處理。
編譯C/C++需將帶符號信息的文件保留下來。
捕獲到崩潰時,將收集到儘量多的有用信息寫入日誌文件,而後選擇合適的時機上傳到服務器。
讀取客戶端上報的日誌文件,尋找合適的符號文件,生成可讀的C/C++調用棧。
核心:如何確保客戶端在各類極端狀況下依然能夠生成崩潰日誌。
提早申請文件句柄fd預留。
參考Breakpad從新封裝Linux Syscall Support的作法以免直接調用libc去分配堆內存。
Breakpad使用了fork子進程甚至孫進程的方式去收集崩潰現場,即使出現二次崩潰,也只是這部分信息丟失。
這裏說下Breakpad缺點:
須要瞭解的是,將來Chromium會使用Crashpad替代Breakpad。
改造Breakpad,增長Logcat信息,Java調用棧信息、其它有用信息。
一個Native Crash log信息以下:
堆棧信息中 pc 後面跟的內存地址,就是當前函數的棧地址,咱們能夠經過下面的命令行得出出錯的代碼行數
arm-linux-androideabi-addr2line -e 內存地址
複製代碼
下面列出所有的信號量以及所表明的含義:
#define SIGHUP 1 // 終端鏈接結束時發出(無論正常或非正常)
#define SIGINT 2 // 程序終止(例如Ctrl-C)
#define SIGQUIT 3 // 程序退出(Ctrl-\)
#define SIGILL 4 // 執行了非法指令,或者試圖執行數據段,堆棧溢出
#define SIGTRAP 5 // 斷點時產生,由debugger使用
#define SIGABRT 6 // 調用abort函數生成的信號,表示程序異常
#define SIGIOT 6 // 同上,更全,IO異常也會發出
#define SIGBUS 7 // 非法地址,包括內存地址對齊出錯,好比訪問一個4字節的整數, 但其地址不是4的倍數
#define SIGFPE 8 // 計算錯誤,好比除0、溢出
#define SIGKILL 9 // 強制結束程序,具備最高優先級,本信號不能被阻塞、處理和忽略
#define SIGUSR1 10 // 未使用,保留
#define SIGSEGV 11 // 非法內存操做,與 SIGBUS不一樣,他是對合法地址的非法訪問, 好比訪問沒有讀權限的內存,向沒有寫權限的地址寫數據
#define SIGUSR2 12 // 未使用,保留
#define SIGPIPE 13 // 管道破裂,一般在進程間通訊產生
#define SIGALRM 14 // 定時信號,
#define SIGTERM 15 // 結束程序,相似溫和的 SIGKILL,可被阻塞和處理。一般程序如 果終止不了,纔會嘗試SIGKILL
#define SIGSTKFLT 16 // 協處理器堆棧錯誤
#define SIGCHLD 17 // 子進程結束時, 父進程會收到這個信號。
#define SIGCONT 18 // 讓一箇中止的進程繼續執行
#define SIGSTOP 19 // 中止進程,本信號不能被阻塞,處理或忽略
#define SIGTSTP 20 // 中止進程,但該信號能夠被處理和忽略
#define SIGTTIN 21 // 當後臺做業要從用戶終端讀數據時, 該做業中的全部進程會收到SIGTTIN信號
#define SIGTTOU 22 // 相似於SIGTTIN, 但在寫終端時收到
#define SIGURG 23 // 有緊急數據或out-of-band數據到達socket時產生
#define SIGXCPU 24 // 超過CPU時間資源限制時發出
#define SIGXFSZ 25 // 當進程企圖擴大文件以致於超過文件大小資源限制
#define SIGVTALRM 26 // 虛擬時鐘信號. 相似於SIGALRM, 可是計算的是該進程佔用的CPU時間.
#define SIGPROF 27 // 相似於SIGALRM/SIGVTALRM, 但包括該進程用的CPU時間以及系統調用的時間
#define SIGWINCH 28 // 窗口大小改變時發出
#define SIGIO 29 // 文件描述符準備就緒, 能夠開始進行輸入/輸出操做
#define SIGPOLL SIGIO // 同上,別稱
#define SIGPWR 30 // 電源異常
#define SIGSYS 31 // 非法的系統調用
複製代碼
通常關注SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV, SIGSTKFLT, SIGSYS便可。
要訂閱異常發生的信號,最簡單的作法就是直接用一個循環遍歷全部要訂閱的信號,對每一個信號調用sigaction()。
首先,應收集崩潰現場的一些相關信息,以下:
系統運行日誌
/system/etc/event-log-tags
機型、系統、廠商、CPU、ABI、Linux版本等
注意,咱們能夠去尋找共性問題,以下:
/proc/meminfo
複製代碼
當系統可用內存小於MemTotal的10%時,OOM、大量GC、系統頻繁自殺拉起等問題很是容易出現。
包括Java內存、RSS、PSS
PSS和RSS經過/proc/self/smap計算,能夠獲得apk、dex、so等更詳細的分類統計。
獲取大小:
/proc/self/status
複製代碼
獲取其具體的分佈狀況:
/proc/self/maps
複製代碼
須要注意的是,對於32位進程,32位CPU,虛擬內存達到3GB就可能會引發內存失敗的問題。若是是64位的CPU,虛擬內存通常在3~4GB。若是支持64位進程,虛擬內存就不會成爲問題。
若是應用堆內存和設備內存比較充足,但還出現內存分配失敗,則可能跟資源泄漏有關。
獲取fd的限制數量:
/proc/self/limits
複製代碼
通常單個進程容許打開的最大句柄個數爲1024,若是超過800需將全部fd和文件名輸出日誌進行排查。
獲取線程數大小:
/proc/self/status
複製代碼
一個線程通常佔2MB的虛擬內存,線程數超過400個比較危險,須要將全部tid和線程名輸出到日誌進行排查。
容易出現引用失效、引用爆表等崩潰。
經過DumpReferenceTables統計JNI的引用表,進一步分析是否出現JNI泄漏等問題。
補充加油站:dumpReferenceTables的出處
在dalvik.system.VMDebug類中,是一個native方法,亦是static方法;在JNI中能夠這麼調用
jclass vm_class = env->FindClass("dalvik/system/VMDebug");
jmethodID dump_mid = env->GetStaticMethodID( vm_class, "dumpReferenceTables", "()V" );
env->CallStaticVoidMethod( vm_class, dump_mid );
複製代碼
接下來進行崩潰分析:
常見的崩潰類型有:
若是是ANR,先看主線程堆棧、是否由於鎖等待致使,而後看ANR日誌中的iowait、CPU、GC、systemserver等信息,肯定是I/O問題或CPU競爭問題仍是大量GC致使的ANR。
注意,當從一條崩潰日誌中沒法看出問題緣由時,須要查看相同崩潰點下的更多崩潰日誌,或者也能夠查看內存信息、資源信息等進行異常排查。
機型、系統、ROM、廠商、ABI這些信息均可以做爲共性參考,對於下一步復現問題有明確指引。
復現以後再增長日誌或使用Debugger、GDB進行調試。如不能復現,能夠採用一些高級手段,如xlog日誌、遠程診斷、動態分析等等。
補充加油站:系統崩潰解決方式
首先,這裏給出《Android開發高手課》張紹文老師寫的crash捕獲示例工程,工程裏面已經集成了Breakpad 來獲取發生 native crash 時候的系統信息和線程堆棧信息。下面來詳細介紹下使用Breakpad來分析native崩潰的流程:
adb pull /sdcard/crashDump/***.dmp > ~/Documents/crash_log.dmp
複製代碼
// 在項目目錄下clone Breakpad倉庫
git clone https://github.com/google/breakpad.git
// 切換到Breakpad根目錄進行配置、編譯
cd breakpad
./configure && make
// 使用src/processor目錄下的minidump_stackwalk工具將dmp文件轉換爲txt文件
./src/processor/minidump_stackwalk ~/Documents/crashDump/crash_log.dmp >crash_log.txt
複製代碼
Operating system: Android
0.0.0 Linux 4.4.78-perf-g539ee70 #1 SMP PREEMPT Mon Jan 14 17:08:14 CST 2019 aarch64
CPU: arm64
8 CPUs
GPU: UNKNOWN
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
0 libcrash-lib.so + 0x650
複製代碼
其中咱們須要的關鍵信息爲CPU是arm64的,而且crash的地址爲0x650。接下來咱們須要將這個地址轉換爲代碼中對應的行。
若是是arm64的so使用 $NDKHOME/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line。
若是是arm的so使用 $NDKHOME/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line。
由crash_log.txt的信息可知,咱們機器的cpu架構是arm64的,所以須要使用aarch64-linux-android-addr2line這個命令行工具。該命令的通常使用格式以下: // 注意:在mac下 ./ 表明執行文件 ./aarch64-linux-android-addr2line -e 對應的.so 須要解析的地址
上述中對應的.so文件在項目編譯以後,會出如今Chapter01-master/sample/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/libcrash-lib.so這個位置,因爲個人手機CPU架構是arm64的,因此這裏選擇的是arm64-v8a中的libcrash-lib.so。接下來咱們使用aarch64-linux-android-addr2line這個命令:
./aarch64-linux-android-addr2line -f -C -e ~/Documents/open-project/Chapter01-master/sample/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/libcrash-lib.so 0x650
參數含義:
-e --exe=<executable>:指定須要轉換地址的可執行文件名。
-f --functions:在顯示文件名、行號輸出信息的同時顯示函數名信息。
-C --demangle[=style]:將低級別的符號名解碼爲用戶級別的名字。
複製代碼
結果輸出爲:
Crash()
/Users/quchao/Documents/open-project/Chapter01-master/sample/src/main/cpp/crash.cpp:10
複製代碼
由此,咱們得出crash的代碼行爲crahs.cpp文件中的第10行,接下來根據項目具體狀況進行相應的修改便可。
Tips:這是從事NDK開發(音視頻、圖像處理、OpenCv、熱修復框架開發)同窗調試native層錯誤時常常要使用的技巧,強烈建議熟練掌握。
最後,筆者這裏再講解下一些疑難Crash的解決方案。
參考Android 8.0 try catch的作法,代理Toast裏的mTN(handler)就能夠實現捕獲異常。
SP 調用 apply 方法,會建立一個等待鎖放到 QueuedWork 中,並將真正數據持久化封裝成一個任務放到異步隊列中執行,任務執行結束會釋放鎖。Activity onStop 以及 Service 處理 onStop,onStartCommand 時,執行 QueuedWork.waitToFinish() 等待全部的等待鎖釋放。
全部此類 ANR 都是經由 QueuedWork.waitToFinish() 觸發的,只要在調用此函數以前,將其中保存的隊列手動清空便可。
具體是Hook ActivityThrad的Handler變量,拿到此變量後給其設置一個Callback,Handler 的 dispatchMessage 中會先處理 callback。最後在 Callback 中調用隊列的清理工做,注意隊列清理須要反射調用 QueuedWork。
apply 機制自己的失敗率就比較高(1.8%左右),清理等待鎖隊列對持久化形成的影響不大。
它是由系統的FinalizerWatchdogDaemon拋出來的。
這裏首先介紹下看門狗 WatchDog,它 的做用是監控重要服務的運行狀態,當重要服務中止時,發生 Timeout 異常崩潰,WatchDog 負責將應用重啓。而當關閉 WatchDog(執行stop()方法)後,當重要服務中止時,也不會發生 Timeout 異常,是一種經過非正常手段防止異常發生的方法。
stop方法,在Android 6.0以前會有線程同步問題。 由於6.0以前調用threadToStop的interrupt方法是沒有加鎖的,因此可能會有線程同步的問題。
注意:Stop的時候有必定機率致使即便沒有超時也會報timeoutexception。
只是爲了不上報異常採起的一種hack方案,並無真正解決引發finialize超時的問題。
經過反射將輸入法的兩個View置空。
咱們能夠利用SyncAdapter提升進程優先級,它是Android系統提供一個帳號同步機制,它屬於核心進程級別,而使用了SyncAdapter的進程優先級自己也會提升,使用方式請Google,關聯SyncAdapter後,進程的優先級變爲1,僅低於前臺正在運行的進程,所以能夠下降應用被系統殺掉的機率。
對於App的Crash優化,總的來講,咱們須要考慮如下四個要點:
高版本ROM須要root權限。
海外Google Play服務、國內Hardcoder。
利用主線程的消息隊列處理機制,應用發生卡頓,必定是在dispatchMessage中執行了耗時操做。咱們經過給主線程的Looper設置一個Printer,打點統計dispatchMessage方法執行的時間,若是超出閥值,表示發生卡頓,則dump出各類信息,提供開發者分析性能瓶頸。
爲卡頓監控代碼增長ANR的線程監控,在發送消息時,在ANR線程中保存一個狀態,主線程消息執行完後再Reset標誌位。若是在ANR線程中收到發送消息後,超過必定時間沒有復位,就能夠任務發生了ANR。
因爲traces.txt上傳比較耗時,因此通常線下采用,線上建議綜合ProcessErrorStateInfo和出現ANR時的堆棧信息來實現ANR的實時上傳。
ANR發生緣由:沒有在規定的時間內完成要完成的事情。
從進程角度看發生緣由有:
Andorid系統監測ANR的核心原理是消息調度和超時處理。
一、抓取bugreport
adb shell bugreport > bugreport.txt
複製代碼
二、直接導出/data/anr/traces.txt文件
adb pull /data/anr/traces.txt trace.txt
複製代碼
發生時間(可能會延時10-20s)
pid:當pid=0,說明在ANR以前,進程就被LMK殺死或出現了Crash,因此沒法接受到系統的廣播或者按鍵消息,所以會出現ANR
cpu負載Load: 7.58 / 6.21 / 4.83
表明此時一分鐘有平均有7.58個進程在等待 一、五、15分鐘內系統的平均負荷 當系統負荷持續大於1.0,必須將值降下來 當系統負荷達到5.0,表面系統有很嚴重的問題
cpu使用率
CPU usage from 18101ms to 0ms ago 28% 2085/system_server: 18% user + 10% kernel / faults: 8689 minor 24 major 11% 752/android.hardware.sensors@1.0-service: 4% user + 6.9% kernel / faults: 2 minor 9.8% 780/surfaceflinger: 6.2% user + 3.5% kernel / faults: 143 minor 4 major
上述表示Top進程的cpu佔用狀況。
若是CPU使用量不多,說明主線程可能阻塞。
----- pid 10494 at 2019-11-18 15:28:29 -----
複製代碼
"main" prio=5 tid=1 Sleeping
| group="main" sCount=1 dsCount=0 flags=1 obj=0x746bf7f0 self=0xe7c8f000
| sysTid=10494 nice=-4 cgrp=default sched=0/0 handle=0xeb6784a4
| state=S schedstat=( 5119636327 325064933 4204 ) utm=460 stm=51 core=4 HZ=100
| stack=0xff575000-0xff577000 stackSize=8MB
| held mutexes=
複製代碼
上述關鍵字段的含義以下所示:
須要注意的是,這裏的各類線程狀態指的是Native層的線程狀態,關於Java線程狀態與Native線程狀態的對應關係以下所示:
enum ThreadState {
// Thread.State JDWP state
kTerminated = 66, // TERMINATED TS_ZOMBIE Thread.run has returned, but Thread* still around
kRunnable, // RUNNABLE TS_RUNNING runnable
kTimedWaiting, // TIMED_WAITING TS_WAIT in Object.wait() with a timeout
kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep()
kBlocked, // BLOCKED TS_MONITOR blocked on a monitor
kWaiting, // WAITING TS_WAIT in Object.wait()
kWaitingForLockInflation, // WAITING TS_WAIT blocked inflating a thin-lock
kWaitingForTaskProcessor, // WAITING TS_WAIT blocked waiting for taskProcessor
kWaitingForGcToComplete, // WAITING TS_WAIT blocked waiting for GC
kWaitingForCheckPointsToRun, // WAITING TS_WAIT GC waiting for checkpoints to run
kWaitingPerformingGc, // WAITING TS_WAIT performing GC
kWaitingForDebuggerSend, // WAITING TS_WAIT blocked waiting for events to be sent
kWaitingForDebuggerToAttach, // WAITING TS_WAIT blocked waiting for debugger to attach
kWaitingInMainDebuggerLoop, // WAITING TS_WAIT blocking/reading/processing debugger events
kWaitingForDebuggerSuspension, // WAITING TS_WAIT waiting for debugger suspend all
kWaitingForJniOnLoad, // WAITING TS_WAIT waiting for execution of dlopen and JNI on load code
kWaitingForSignalCatcherOutput, // WAITING TS_WAIT waiting for signal catcher IO to complete
kWaitingInMainSignalCatcherLoop, // WAITING TS_WAIT blocking/reading/processing signals
kWaitingForDeoptimization, // WAITING TS_WAIT waiting for deoptimization suspend all
kWaitingForMethodTracingStart, // WAITING TS_WAIT waiting for method tracing to start
kWaitingForVisitObjects, // WAITING TS_WAIT waiting for visiting objects
kWaitingForGetObjectsAllocated, // WAITING TS_WAIT waiting for getting the number of allocated objects
kWaitingWeakGcRootRead, // WAITING TS_WAIT waiting on the GC to read a weak root
kWaitingForGcThreadFlip, // WAITING TS_WAIT waiting on the GC thread flip (CC collector) to finish
kStarting, // NEW TS_WAIT native thread started, not yet ready to run managed code
kNative, // RUNNABLE TS_RUNNING running in a JNI native method
kSuspended, // RUNNABLE TS_RUNNING suspended by GC or debugger
};
複製代碼
雖然apply並不會阻塞主線程,可是會將等待時間轉嫁到主線程。
在應用啓動時設定一個標誌,在主動自殺或崩潰後更新標誌 ,下次啓動時檢測此標誌便可判斷。
broadcast跟service超時機制大抵相同,但有一個很是隱蔽的技能點,那就是經過靜態註冊的廣播超時會受SharedPreferences(簡稱SP)的影響。
當SP有未同步到磁盤的工做,則需等待其完成,才告知系統已完成該廣播。而且只有XML靜態註冊的廣播超時檢測過程會考慮是否有SP還沒有完成,動態廣播並不受其影響。
上述導出每一個進程trace時,進程之間會休眠200ms。
關於業務高可用重要性有以下五點:
業務高可用方案建設須要注意的點比較繁雜,可是整體能夠歸結爲以下幾點:
用戶反饋、從新打包、渠道更新、不可接受。
關於容災方案的建設主要能夠細分爲如下七點,下面,咱們分別來了解下。
配置中心,服務端下發配置控制
熱修復能力,可監控、灰度、回滾、清除。
微信讀書、蘑菇街、淘寶、天貓等「重運營」的APP都使用了安全模式保障客戶端啓動流程,啓動失敗後給用戶自救機會。先介紹一下它的核心特色:
配置後臺:統一的配置後臺,具有灰度發佈機制
一、客戶端能力:
二、數據統計及告警
三、快速測試
一、如何判斷異常退出?
APP啓動時記錄一個flag值,知足如下條件時,將flag值清空
若是在啓動階段發生異常,則flag值不會清空,經過flag值就能夠判斷客戶端是否異常退出,每次異常退出,flag值都+1。
二、安全模式的分級執行策略
分爲兩級安全模式,連續Crash 2次爲一級安全模式,連續Crash 2次及以上爲二級安全模式。
業務線能夠在一級安全模式中註冊行爲,好比清空緩存數據,再進入該模式時,會使用註冊行爲嘗試修復客戶端 若是一級安全模式沒法修復APP,則進入二級安全模式將APP恢復到初次安裝狀態,並將Document、Library、Cache三個根目錄清空。
三、熱修復執行策略
只要發現配置中須要熱修復,APP就會同步阻塞進行熱修復,保證修復的及時性
四、灰度方案
灰度時,配置中會包含灰度、正式兩份配置及其灰度機率 APP根據特定算法算出本身是否知足灰度條件,則使用灰度配置
一、接入成本
完善文檔、接口簡潔
二、統一配置後臺
可按照APP、版本配置
三、定製性
支持定製功能,讓接入方來決定具體行爲
四、灰度機制
五、數據分析
採用統一數據平臺,爲安全模式改進提供依據
六、快速測試
建立更多的針對性測試案例,如模擬連續Crash
當屢次請求失敗則可以讓網絡庫主動拒絕請求。
功能開關 -> 統跳中心 -> 動態修復 -> 安全模式
要實現App穩定性的長效治理,咱們須要從 開發階段 => 測試階段 => 合碼階段 => 發佈階段 => 運維階段 這五個階段來作針對性地處理。
隨着項目的逐漸成熟,用戶基數逐漸增多,DAU持續升高,咱們遇到了不少穩定性方面的問題,對於咱們技術同窗遇到了不少的挑戰,用戶常用咱們的App卡頓或者是功能不可用,所以咱們就針對穩定性開啓了專項的優化,咱們主要優化了三項:
經過這三方面的優化咱們搭建了移動端的高可用平臺。同時,也作了不少的措施來讓App真正地實現了高可用。
咱們針對啓動速度,內存、佈局加載、卡頓、瘦身、流量、電量等多個方面作了多維的優化。
咱們的優化主要分爲了兩個層次,即線上和線下,針對於線下呢,咱們側重於發現問題,直接解決,將問題儘量在上線以前解決爲目的。而真正到了線上呢,咱們最主要的目的就是爲了監控,對於各個性能緯度的監控呢,可讓咱們儘量早地獲取到異常狀況的報警。
同時呢,對於線上最嚴重的性能問題性問題:Crash,咱們作了專項的優化,不只優化了Crash的具體指標,並且也儘量地獲取了Crash發生時的詳細信息,結合後端的聚合、報警等功能,便於咱們快速地定位問題。
移動端業務高可用它側重於用戶功能完整可用,主要是爲了解決一些線上一些異常狀況致使用戶他雖然沒有崩潰,也沒有性能問題,可是呢,只是單純的功能不可用的狀況,咱們須要對項目的主流程、核心路徑進行埋點監控,來計算每一步它真實的轉換率是多少,同時呢,還須要知道在每一步到底發生了多少異常。這樣咱們就知道了全部業務流程的轉換率以及相應界面的轉換率,有了大盤的數據呢,咱們就知道了,若是轉換率或者是某些監控的成功率低於某個值,那頗有可能就是出現了線上異常,結合了相應的報警功能,咱們就不須要等用戶來反饋了,這個就是業務穩定性保障的基礎。
同時呢,對於一些特殊狀況,好比說,開發過程中或代碼中出現了一些catch代碼塊,捕獲住了異常,讓程序不崩潰,這實際上是不合理的,程序雖然沒有崩潰,當時程序的功能已經變得不可用,因此呢,這些被catch的異常咱們也須要上報上來,這樣咱們才能知道用戶到底出現了什麼問題而致使的異常。此外,線上還有一些單點問題,好比說用戶點擊登陸一直進不去,這種就屬於單點問題,其實咱們是沒法找出其和其它問題的共性之處的,因此呢,咱們就必需要找到它對應的詳細信息。
最後,若是發生了異常狀況,咱們還採起了一系列措施進行快速止損。(=>4)
首先,須要讓App具有一些高級的能力,咱們對於任何要上線的新功能,要加上一個功能的開關,經過配置中心下發的開關呢,來決定是否要顯示新功能的入口。若是有異常狀況,能夠緊急關閉新功能的入口,那就可讓這個App處於可控的狀態了。
而後,咱們須要給App設立路由跳轉,全部的界面跳轉都須要經過路由來分發,若是咱們匹配到須要跳轉到有bug的這樣一個新功能時,那咱們就不跳轉了,或者是跳轉到統一的異常正處理中的界面。若是這兩種方式都不能夠,那就能夠考慮經過熱修復的方式來動態修復,目前熱修復的方案其實已經比較成熟了,咱們徹底能夠低成本地在咱們的項目中添加熱修復的能力,固然,若是有些功能是由RN或WeeX來實現就更好了,那就能夠經過更新資源包的方式來實現動態更新。而這些若是都不能夠的話呢,那就能夠考慮本身去給應用加上一個自主修復的能力,若是App啓動屢次的話,那就能夠考慮清空全部的緩存數據,將App重置到安裝的狀態,到了最嚴重的等級呢,能夠阻塞主線程,此時必定要等App熱修復成功以後才容許用戶進入。
Android穩定性優化是一個須要 長期投入,持續運營和維護 的一個過程,上文中咱們不只深刻探討了Java Crash、Native Crash和ANR的解決流程及方案,還分析了其內部實現原理和監控流程。到這裏,能夠看到,要想作好穩定性優化,咱們 必須對虛擬機運行、Linux信號處理和內存分配 有必定程度的瞭解,只有深刻了解這些底層知識,咱們才能比別人設計出更好的穩定性優化方案。
一、《Android性能優化最佳實踐》第五章 穩定性優化
二、慕課網之國內Top團隊大牛帶你玩轉Android性能分析與優化 第十一章 App穩定性優化
四、Android 平臺 Native 代碼的崩潰捕獲機制及實現
六、美團外賣Android Crash治理之路 (進階)
七、海神平臺Crash監控SDK(Android)開發經驗總結
1三、ANR監測機制
1七、巧妙定位ANR問題
1八、剖析 SharedPreference apply 引發的 ANR 問題
1九、Linux錯誤信號
歡迎關注個人微信:
bcce5360
微信羣若是不能掃碼加入,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。
2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~