深刻探索Android穩定性優化

前言

成爲一名優秀的Android開發,須要一份完備的知識體系,在這裏,讓咱們一塊兒成長爲本身所想的那樣~。

衆所周知,移動開發已經來到了後半場,爲了可以在衆多開發者中脫穎而出,咱們須要對某一個領域有深刻地研究與心得,對於Android開發者來講,目前,有幾個好的細分領域值得咱們去創建本身的技術壁壘,以下所示:html

  • 一、性能優化專家:具有深度性能優化與體系化APM建設的能力
  • 二、架構師:具備豐富的應用架構設計經驗與心得,對Android Framework層與熱門三方庫的實現原理與架構設計瞭如指掌。
  • 三、音視頻/圖像處理專家:毫無疑問,掌握NDK,深刻音視頻與圖像處理領域能讓咱們在將來幾年大放異彩。
  • 四、大前端專家:深刻掌握Flutter及其設計原理與思想,可讓咱們具備快速學習前端知識的能力。

在上述幾個細分領域中,最難也最具技術壁壘的莫過於性能優化,要想成爲一個頂尖的性能優化專家,須要對許多領域的深度知識及廣度知識有深刻的瞭解與研究,其中不乏須要掌握架構師、NDK、Flutter所涉及的衆多技能。從這篇文章開始,筆者將會帶領你們一步一步深刻探索Android的性能優化。前端

爲了可以全面地瞭解Android的性能優化知識體系,咱們先看看我總結的下面這張圖,以下所示:linux

image

要作好應用的性能優化,咱們須要創建一套成體系的性能優化方案,這套方案被業界稱爲 APM(Application Performance Manange),爲了讓你們快速瞭解APM涉及的相關知識,筆者已經將其總結成圖,以下所示:android

image

在建設APM和對App進行性能優化的過程當中,咱們必須首先解決的是App的穩定性問題,如今,讓咱們搭乘航班,來深刻探索Android穩定性優化的疆域git

思惟導圖大綱

目錄

  • 1、正確認識
    • 一、穩定性緯度
    • 二、穩定性優化注意事項
    • 三、Crash 相關指標
    • 四、Crash 率評價
    • 五、Crash 關鍵問題
    • 六、APM Crash 部分總體架構
    • 七、責任歸屬
  • 2、Crash 優化
    • 一、單個 Crash 處理方案
    • 二、Crash 率治理方案
    • 三、Java Crash
    • 四、Java Crash 處理流程
    • 五、Native Crash
    • 六、疑難 Crash 解決方案
    • 七、進程保活
    • 八、總結
  • 3、ANR 優化
    • 一、ANR 監控實現方式
    • 二、ANR 優化
    • 三、關於 ANR 的一些常見問題
    • 四、理解 ANR 的觸發流程
  • 4、移動端業務高可用方案建設
    • 一、業務高可用重要性
    • 二、業務高可用方案建設
    • 三、移動端容災方案
  • 5、穩定性長效治理
    • 一、開發階段
    • 二、測試階段
    • 三、合碼階段
    • 四、發佈階段
    • 五、運維階段
  • 6、穩定性優化問題
    • 一、大家作了哪些穩定性方面的優化?
    • 二、性能穩定性是怎麼作的?
    • 三、業務穩定性如何保障?
    • 四、若是發生了異常狀況,怎麼快速止損?
  • 7、總結

1、正確認識

首先,咱們必須對App的穩定性有正確的認識,它是App質量構建體系中最基本和最關鍵的一環。若是咱們的App不穩定,而且常常不能正常地提供服務,那麼用戶大機率會卸載掉它。因此穩定性很重要,而且Crash是P0優先級,須要優先解決。 並且,穩定性可優化的面很廣,它不只僅只包含Crash這一部分,也包括卡頓、耗電等優化範疇。github

一、穩定性緯度

應用的穩定性能夠分爲三個緯度,以下所示:面試

  • 一、Crash緯度:最重要的指標就是應用的Crash率
  • 二、性能緯度:包括啓動速度、內存、繪製等等優化方向,相對於Crash來講是次要的,在作應用性能體系化建設以前,咱們必需要確保應用的功能穩定可用。
  • 三、業務高可用緯度:它是很是關鍵的一步,咱們須要採用多種手段來保證咱們App的主流程以及核心路徑的穩定性,只有用戶常用咱們的App,它纔有可能發現別的方面的問題。

二、穩定性優化注意事項

咱們在作應用的穩定性優化的時候,須要注意三個要點,以下所示:算法

一、重在預防、監控必不可少

對於穩定性來講,若是App已經到了線上才發現異常,那其實已經形成了損失,因此,對於穩定性的優化,其重點在於預防。從開發同窗的編碼環節,到測試同窗的測試環節,以及到上線前的發佈環節、上線後的運維環節,這些環節都須要來預防異常狀況的發生。若是異常真的發生了,也須要將千方百計將損失降到最低,爭取用最小的代價來暴露儘量多的問題。shell

此外,監控也是必不可少的一步,預防作的再好,到了線上,總會有各類各樣的異常發生。因此,不管如何,咱們都須要有全面的監控手段來更加靈敏地發現問題json

二、思考更深一層、重視隱含信息:如解決Crash問題時思考是否會引起同一類問題

當咱們看到了一個Crash的時候,不能簡單地只處理這一個Crash,而是須要思考更深一層,要考慮會不會在其它地方會有同樣的Crash類型發生。若是有這樣的狀況,咱們必須對其統一處理和預防

此外,咱們還要關注Crash相關的隱含信息,好比,在面試過程中,面試官問你,大家應用的Crash率是多少,這個問題代表上問的是Crash率,可是實際上它是問你一些隱含信息的,太高的Crash率就表明開發人員的水平不行,leader的架構能力不行,項目的各個階段中優化的空間很是大,這樣一來,面試官對你的印象和評價也不會好。

三、長效保持須要科學流程

應用穩定性的建設過程是一個細活,因此很容易出現這個版本優化好了,可是在接下來的版本中若是咱們無論它,它就會發生持續惡化的狀況,所以,咱們必須從項目研發的每個流程入手,創建科學完善的相關規範,才能保證長效的優化效果

三、Crash相關指標

要對應用的穩定性進行優化,咱們就必須先了解與Crash相關的一些指標。

一、UV、PV

  • PV(Page View):訪問量
  • UV(Unique Visitor):獨立訪客,0 - 24小時內的同一終端只計算一次

二、UV、PV、啓動、增量、存量 Crash率

  • UV Crash率(Crash UV / DAU):針對用戶使用量的統計,統計一段時間內全部用戶發生崩潰的佔比,用於評估Crash率的影響範圍,結合PV。須要注意的是,須要確保一直使用同一種衡量方式。
  • PV Crash率:評估相關Crash影響的嚴重程度
  • 啓動Crash率:啓動階段,用戶尚未徹底打開App而發生的Crash,它是影響最嚴重的Crash,對用戶傷害最大,沒法經過熱修復拯救,需結合客戶端容災,以進行App的自主修復。(這塊後面會講)
  • 增量、存量Crash率:增量Crash是指的新增的Crash,而存量Crash則表示一些歷史遺留bug。增量Crash是新版本重點,存量Crash是須要持續啃的硬骨頭,咱們須要優先解決增量、持續跟進存量問題

四、Crash率評價

那麼,咱們App的Crash率下降多少才能算是一個正常水平或優秀的水平呢?

  • Java與Native的總崩潰率必須在千分之二如下。
  • Crash率萬分位爲優秀:須要注意90%的Crash都是比較容易解決的,可是要解決最後的10%須要付出巨大的努力。

五、Crash關鍵問題

這裏咱們還須要關注Crash相關的關鍵問題,若是應用發生了Crash,咱們應該儘量還原Crash現場。所以,咱們須要全面地採集應用發生Crash時的相關信息,以下所示:

  • 堆棧、設備、OS版本、進程、線程名、Logcat
  • 先後臺、使用時長、App版本、小版本、渠道
  • CPU架構、內存信息、線程數、資源包信息、用戶行爲日誌

接着,採集完上述信息並上報到後臺後,咱們會在APM後臺進行聚合展現,具體的展現信息以下所示:

  • Crash現場信息
  • Crash Top機型、OS版本、分佈版本、區域
  • Crash起始版本、上報趨勢、是否新增、持續、量級

最後,咱們能夠根據以上信息決定Crash是否須要立馬解決以及在哪一個版本進行解決,關於APM聚合展現這塊能夠參考 Bugly平臺 的APM後臺聚合展現。

而後,咱們再來看看與Crash相關的總體架構。

六、APM Crash部分總體架構

APM Crash部分的總體架構從上之下分爲採集層、處理層、展現層、報警層。下面,咱們來詳細講解一下每一層所作的處理。

採集層

首先,咱們須要在採集層這一層去獲取足夠多的Crash相關信息,以確保可以精肯定位到問題。須要採集的信息主要爲以下幾種:

  • 錯誤堆棧
  • 設備信息
  • 行爲日誌
  • 其它信息
處理層

而後,在處理層,咱們會對App採集到的數據進行處理。

  • 數據清洗:將一些不符合條件的數據過濾掉,好比說,由於一些特殊狀況,一些App採集到的數據不完整,或者因爲上傳數據失敗而致使的數據不完整,這些數據在APM平臺上確定是沒法全面地展現的,因此,首先咱們須要把這些信息進行過濾。
  • 數據聚合:在這一層,咱們會把Crash相關的數據進行聚合。
  • 緯度分類:如Top機型下的Crash、用戶Crash率的前10%等等維度。
  • 趨勢對比
展現層

通過處理層以後,就會來到展現層,展現的信息爲以下幾類:

  • 數據還原
  • 緯度信息
  • 起始版本
  • 其它信息
報警層

最後,就會來到報警層,當發生嚴重異常的時候,會通知相關的同窗進行緊急處理。報警的規則咱們能夠自定義,例如總體的Crash率,其環比(與上一期進行對比)或同比(如本月10號與上月10號)抖動超過5%,或者是單個Crash忽然間激增。報警的方式能夠經過 郵件、IM、電話、短信 等等方式。

七、責任歸屬

最後,咱們來看下Crash相關的非技術問題,須要注意的是,咱們要解決的是如何長期保持較低的Crash率這個問題。咱們須要保證可以迅速找到相關bug的相關責任人並讓開發同窗可以及時地處理線上的bug。具體的解決方法爲以下幾種:

  • 設立專項小組輪值:成立一個虛擬的專項小組,來專門跟蹤每一個版本線上的Crash率,組內的成員能夠輪流跟蹤線上的Crash,這樣,就能夠從源頭來保證全部Crash必定會有人跟進。
  • 自動匹配責任人將APM平臺與bug單系統打通,這樣APM後臺一旦發現緊急bug就能第一時間下發到bug單系統給相關責任人發提醒
  • 處理流程全紀錄:咱們須要記錄Crash處理流程的每一步,確保緊急Crash的處理不會被延誤

2、Crash優化

一、單個Crash處理方案

對與單個Crash的處理方案咱們能夠按以下三個步驟來進行解決處理。

一、根據堆棧及現場信息找答案

  • 解決90%問題
  • 解決完後需考慮產生Crash深層次的緣由

二、找共性:機型、OS、實驗開關、資源包,考慮影響範圍

三、線下復現、遠程調試

二、Crash率治理方案

要對應用的Crash率進行治理,通常須要對如下三種類型的Crash進行對應的處理,以下所示:

  • 一、解決線上常規Crash
  • 二、系統級Crash嘗試Hook繞過
  • 三、疑難Crash重點突破或更換方案

三、Java Crash

出現未捕獲異常,致使出現異常退出

Thread.setDefaultUncaughtExceptionHandler();
複製代碼

咱們經過設置自定義的UncaughtExceptionHandler,就能夠在崩潰發生的時候獲取到現場信息。注意,這個鉤子是針對單個進程而言的,在多進程的APP中,監控哪一個進程,就須要在哪一個進程中設置一遍ExceptionHandler

獲取主線程的堆棧信息:

Looper.getMainLooper().getThread().getStackTrace();
複製代碼

獲取當前線程的堆棧信息:

Thread.currentThread().getStackTrace();
複製代碼

獲取所有線程的堆棧信息:

Thread.getAllStackTraces();
複製代碼

第三方Crash監控工具如 Fabric、騰訊Bugly,都是以字符串拼接的方式將數組StackTraceElement[]轉換成字符串形式,進行保存、上報或者展現。

那麼,咱們如何反混淆上傳的堆棧信息?

對此,咱們通常有兩種可選的處理方案,以下所示:

  • 一、每次打包生成混淆APK的時候,須要把Mapping文件保存並上傳到監控後臺。
  • 二、Android原生的反混淆的工具包是retrace.jar,在監控後臺用來實時解析每一個上報的崩潰時。它會將Mapping文件進行文本解析和對象實例化,這個過程比較耗時。所以能夠將Mapping對象實例進行內存緩存,但爲了防止內存泄露和內存過多佔用,須要增長按期自動回收的邏輯。

如何獲取logcat方法?

logcat日誌流程是這樣的,應用層 --> liblog.so --> logd,底層使用 ring buffer 來存儲數據。獲取的方式有如下三種:

一、經過logcat命令獲取。

  • 優勢:很是簡單,兼容性好。
  • 缺點:整個鏈路比較長,可控性差,失敗率高,特別是堆破壞或者堆內存不足時,基本會失敗。

二、hook liblog.so實現

經過hook liblog.so 中的 __android_log_buf_write 方法,將內容重定向到本身的buffer中

  • 優勢:簡單,兼容性相對還好。
  • 缺點:要一直打開。

三、自定義獲取代碼。經過移植底層獲取logcat的實現,經過socket直接跟logd交互。

  • 優勢:比較靈活,預先分配好資源,成功率也比較高。
  • 缺點:實現很是複雜

如何獲取Java 堆棧?

當發生native崩潰時,咱們經過unwind只能拿到Native堆棧。咱們但願能夠拿到當時各個線程的Java堆棧。對於這個問題,目前有兩種處理方式,分別以下所示:

一、Thread.getAllStackTraces()。

優勢

簡單,兼容性好。

缺點
  • 成功率不高,依靠系統接口在極端狀況也會失敗。
  • 7.0以後這個接口是沒有主線程堆棧。
  • 使用Java層的接口須要暫停線程。

二、hook libart.so。

經過hook ThreadList和Thread 的函數,得到跟ANR同樣的堆棧。爲了穩定性,須要在fork的子進程中執行

  • 優勢:信息很全,基本跟ANR的日誌同樣,有native線程狀態,鎖信息等等。
  • 缺點:黑科技的兼容性問題,失敗時咱們能夠使用Thread.getAllStackTraces()兜底。

四、Java Crash處理流程

講解了Java Crash相關的知識後,咱們就能夠去了解下Java Crash的處理流程,這裏借用Gityuan流程圖進行講解,以下圖所示:

image

一、首先發生crash所在進程,在建立之初便準備好了defaultUncaughtHandler,用來來處理Uncaught Exception,並輸出當前crash基本信息;

二、調用當前進程中的AMP.handleApplicationCrash;通過binder ipc機制,傳遞到system_server進程;

三、接下來,進入system_server進程,調用binder服務端執行AMS.handleApplicationCrash;

四、從mProcessNames查找到目標進程的ProcessRecord對象;並將進程crash信息輸出到目錄/data/system/dropbox;

五、執行makeAppCrashingLocked:

  • 建立當前用戶下的crash應用的error receiver,並忽略當前應用的廣播;
  • 中止當前進程中全部activity中的WMS的凍結屏幕消息,並執行相關一些屏幕相關操做;

六、再執行handleAppCrashLocked方法:

  • 當1分鐘內同一進程連續crash兩次時,且非persistent進程,則直接結束該應用全部activity,並殺死該進程以及同一個進程組下的全部進程。而後再恢復棧頂第一個非finishing狀態的activity;
  • 當1分鐘內同一進程連續crash兩次時,且persistent進程,,則只執行恢復棧頂第一個非finishing狀態的activity;
  • 當1分鐘內同一進程未發生連續crash兩次時,則執行結束棧頂正在運行activity的流程。

七、經過mUiHandler發送消息SHOW_ERROR_MSG,彈出crash對話框;

八、到此,system_server進程執行完成。回到crash進程開始執行殺掉當前進程的操做;

九、當crash進程被殺,經過binder死亡通知,告知system_server進程來執行appDiedLocked();

十、最後,執行清理應用相關的activity/service/ContentProvider/receiver組件信息。

補充加油站:binder 死亡通知原理

這裏咱們還須要瞭解下binder 死亡通知的原理,其流程圖以下所示:

image

因爲Crash進程中擁有一個Binder服務端ApplicationThread,而應用進程在建立過程調用attachApplicationLocked(),從而attach到system_server進程,在system_server進程內有一個ApplicationThreadProxy,這是相對應的Binder客戶端。當Binder服務端ApplicationThread所在進程(即Crash進程)掛掉後,則Binder客戶端能收到相應的死亡通知,從而進入binderDied流程。

五、Native Crash

特色:

  • 訪問非法地址
  • 地址對齊出錯
  • 發送程序主動abort

上述都會產生相應的signal信號,致使程序異常退出。

一、合格的異常捕獲組件

一個合格的異常捕獲組件須要包含如下功能:

  • 支持在crash時進行更多擴張操做
  • 打印logcat和日誌
  • 上報crash次數
  • 對不一樣crash作不一樣恢復措施
  • 能夠針對業務不斷改進的適應

二、現有方案

一、Google Breakpad

  • 優勢:權威、跨平臺
  • 缺點:代碼體量較大

二、Logcat

  • 優勢:利用安卓系統實現
  • 缺點:須要在crash時啓動新進程過濾logcat日誌,不可靠

三、coffeecatch

  • 優勢:實現簡潔、改動容易
  • 缺點:有兼容性問題

三、Native崩潰捕獲流程

Native崩潰捕獲的過程涉及到三端,這裏咱們分別來了解下其對應的處理。

一、編譯端

編譯C/C++需將帶符號信息的文件保留下來。

二、客戶端

捕獲到崩潰時,將收集到儘量多的有用信息寫入日誌文件,而後選擇合適的時機上傳到服務器。

三、服務端

讀取客戶端上報的日誌文件,尋找合適的符號文件,生成可讀的C/C++調用棧。

四、Native崩潰捕獲的難點

核心:如何確保客戶端在各類極端狀況下依然能夠生成崩潰日誌。

一、文件句柄泄漏,致使建立日誌文件失敗?

提早申請文件句柄fd預留。

二、棧溢出致使日誌生成失敗?

  • 使用額外的棧空間signalstack,避免棧溢出致使進程沒有空間建立調用棧執行處理函數。(signalstack:系統會在危險狀況下把棧指針指向這個地方,使得能夠在一個新的棧上運行信號處理函數)
  • 特殊請求需直接替換當前棧,因此應在堆中預留部分空間。

三、堆內存耗盡致使日誌生產失敗?

參考Breakpad從新封裝Linux Syscall Support的作法以免直接調用libc去分配堆內存。

四、堆破壞或二次崩潰致使日誌生成失敗?

Breakpad使用了fork子進程甚至孫進程的方式去收集崩潰現場,即使出現二次崩潰,也只是這部分信息丟失。

這裏說下Breakpad缺點:

  • 生成的minidump文件時二進制的,包含過多不重要的信息,致使文件數MB。但minidump能夠使用gdb調試、看到傳入參數。

須要瞭解的是,將來Chromium會使用Crashpad替代Breakpad。

五、想要遵循Android的文本格式並添加更多重要的信息?

改造Breakpad,增長Logcat信息,Java調用棧信息、其它有用信息。

五、Native崩潰捕獲註冊

一個Native Crash log信息以下:

image

堆棧信息中 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()。

注意

  • JNI_OnLoad是最適合安裝信號初識函數的地方。
  • 建議在上報時調用Java層的方法統一上報。Native崩潰捕獲註冊。

六、崩潰分析流程

首先,應收集崩潰現場的一些相關信息,以下:

一、崩潰信息

  • 進程名、線程名
  • 崩潰堆棧和類型
  • 有時候也須要知道主線程的調用棧

二、系統信息

  • 系統運行日誌

    /system/etc/event-log-tags

  • 機型、系統、廠商、CPU、ABI、Linux版本等

注意,咱們能夠去尋找共性問題,以下:

  • 設備狀態
  • 是否root
  • 是不是模擬器

三、內存信息

系統剩餘內存
/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

獲取fd的限制數量:

/proc/self/limits
複製代碼

通常單個進程容許打開的最大句柄個數爲1024,若是超過800需將全部fd和文件名輸出日誌進行排查

線程數

獲取線程數大小:

/proc/self/status
複製代碼

一個線程通常佔2MB的虛擬內存,線程數超過400個比較危險,須要將全部tid和線程名輸出到日誌進行排查。

JNI

容易出現引用失效、引用爆表等崩潰。

經過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 );
複製代碼

五、應用信息

  • 崩潰場景
  • 關鍵操做路徑
  • 其它跟自身應用相關的自定義信息:運行時間、是否加載補丁、是否全新安裝或升級。

六、崩潰分析流程

接下來進行崩潰分析:

一、肯定重點
  • 確認嚴重程度
  • 優先解決Top崩潰或對業務有重大影響的崩潰:如啓動、支付過程的崩潰
  • Java崩潰:若是是OOM,需進一步查看日誌中的內存信息和資源信息
  • Native崩潰:查看signal、code、fault addr以及崩潰時的Java堆棧

常見的崩潰類型有:

  • SIGSEGV:空指針、非法指針等
  • SIGABRT:ANR、調用abort推出等

若是是ANR,先看主線程堆棧、是否由於鎖等待致使,而後看ANR日誌中的iowait、CPU、GC、systemserver等信息,肯定是I/O問題或CPU競爭問題仍是大量GC致使的ANR。

注意,當從一條崩潰日誌中沒法看出問題緣由時,須要查看相同崩潰點下的更多崩潰日誌,或者也能夠查看內存信息、資源信息等進行異常排查。

二、查找共性

機型、系統、ROM、廠商、ABI這些信息均可以做爲共性參考,對於下一步復現問題有明確指引。

三、嘗試復現

復現以後再增長日誌或使用Debugger、GDB進行調試。如不能復現,能夠採用一些高級手段,如xlog日誌、遠程診斷、動態分析等等。

補充加油站:系統崩潰解決方式

  • 一、經過共性信息查找可能的緣由
  • 二、嘗試使用其它使用方式規避
  • 三、Hook解決

七、實戰:使用Breakpad捕獲native崩潰

首先,這裏給出《Android開發高手課》張紹文老師寫的crash捕獲示例工程,工程裏面已經集成了Breakpad 來獲取發生 native crash 時候的系統信息和線程堆棧信息。下面來詳細介紹下使用Breakpad來分析native崩潰的流程:

一、示例工程是採用cmake的構建方式,因此須要先到Android Studio中SDK Manager中的SDK Tools下下載NDK和cmake。

二、安裝實例工程後,點擊CRASH按鈕產生一個native崩潰。生成的 crash信息,若是授予Sdcard權限會優先存放在/sdcard/crashDump下,便於咱們作進一步的分析。反之會放到目錄 /data/data/com.dodola.breakpad/files/crashDump中。

三、使用adb pull命令將抓取到的crash日誌文件放到電腦本地目錄中:

adb pull /sdcard/crashDump/***.dmp > ~/Documents/crash_log.dmp
複製代碼

四、下載並編譯Breakpad源碼,在src/processor目錄下找到minidump_stackwalk,使用這個工具將dmp文件轉換爲txt文件:

// 在項目目錄下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 
複製代碼

五、打開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。接下來咱們須要將這個地址轉換爲代碼中對應的行。

六、使用ndk 中提供的addr2line來根據地址進行一個符號反解的過程。

若是是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解決方案

最後,筆者這裏再講解下一些疑難Crash的解決方案。

問題1:如何解決Android 7.0 Toast BadTokenException?

參考Android 8.0 try catch的作法,代理Toast裏的mTN(handler)就能夠實現捕獲異常。

問題2:如何解決 SharedPreference apply 引發的 ANR 問題

apply爲何會引發ANR?

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%左右),清理等待鎖隊列對持久化形成的影響不大。

問題3:如何解決TimeoutExceptin異常?

它是由系統的FinalizerWatchdogDaemon拋出來的。

這裏首先介紹下看門狗 WatchDog,它 的做用是監控重要服務的運行狀態,當重要服務中止時,發生 Timeout 異常崩潰,WatchDog 負責將應用重啓。而當關閉 WatchDog(執行stop()方法)後,當重要服務中止時,也不會發生 Timeout 異常,是一種經過非正常手段防止異常發生的方法。

規避方案

stop方法,在Android 6.0以前會有線程同步問題。 由於6.0以前調用threadToStop的interrupt方法是沒有加鎖的,因此可能會有線程同步的問題。

注意:Stop的時候有必定機率致使即便沒有超時也會報timeoutexception。

缺點

只是爲了不上報異常採起的一種hack方案,並無真正解決引發finialize超時的問題。

問題4:如何解決輸入法的內存泄漏?

經過反射將輸入法的兩個View置空。

七、進程保活

咱們能夠利用SyncAdapter提升進程優先級,它是Android系統提供一個帳號同步機制,它屬於核心進程級別,而使用了SyncAdapter的進程優先級自己也會提升,使用方式請Google,關聯SyncAdapter後,進程的優先級變爲1,僅低於前臺正在運行的進程,所以能夠下降應用被系統殺掉的機率

八、總結

對於App的Crash優化,總的來講,咱們須要考慮如下四個要點:

  • 一、重在預防:重視應用的整個流程、包括開發人員的培訓、編譯檢查、靜態掃描、規範的測試、灰度、發佈流程等
  • 二、不該該隨意使用try catch去隱藏問題:而應該從源頭入手,瞭解崩潰的本質緣由,保證後面的運行流程。
  • 三、解決崩潰的過程應該由點到面,考慮一類崩潰怎麼解決。
  • 四、崩潰與內存、卡頓、I/O內存緊密相關

3、ANR優化

一、ANR監控實現方式

一、使用FileObserver監聽 /data/anr/traces.txt的變化

缺點

高版本ROM須要root權限。

解決方案

海外Google Play服務、國內Hardcoder。

二、監控消息隊列的運行時間(WatchDog)

卡頓監控原理:

利用主線程的消息隊列處理機制,應用發生卡頓,必定是在dispatchMessage中執行了耗時操做。咱們經過給主線程的Looper設置一個Printer,打點統計dispatchMessage方法執行的時間,若是超出閥值,表示發生卡頓,則dump出各類信息,提供開發者分析性能瓶頸。

爲卡頓監控代碼增長ANR的線程監控,在發送消息時,在ANR線程中保存一個狀態,主線程消息執行完後再Reset標誌位。若是在ANR線程中收到發送消息後,超過必定時間沒有復位,就能夠任務發生了ANR。

缺點

  • 沒法準確判斷是否真正出現ANR,只能說明APP發生了UI阻塞,須要進行二次校驗。校驗的方式就是等待手機系統出現發生了Error的進程,而且Error類型是NOT_RESPONDING(值爲2)。 在每次出現ANR彈框前,Native層都會發出signal爲SIGNAL_QUIT(值爲3)的信號事件,也能夠監聽此信號。
  • 沒法獲得完整ANR日誌
  • 隸屬於卡頓優化的方式

三、須要考慮應用退出場景

  • 主動自殺
  • Process.killProcess()、exit()等。
  • 崩潰
  • 系統重啓
  • 系統異常、斷電、用戶重啓等:經過比較應用開機運行時間是否比以前記錄的值更小。
  • 被系統殺死
  • 被LMK殺死、從系統的任務管理器中劃掉等。

注意

因爲traces.txt上傳比較耗時,因此通常線下采用,線上建議綜合ProcessErrorStateInfo和出現ANR時的堆棧信息來實現ANR的實時上傳。

二、ANR優化

ANR發生緣由:沒有在規定的時間內完成要完成的事情。

ANR分類

發生場景

  • Activity onCreate方法或Input事件超過5s沒有完成
  • BroadcastReceiver前臺10s,後臺60s
  • ContentProvider 在publish過超時10s;
  • Service前臺20s,後臺200s

發生緣由

  • 主線程有耗時操做
  • 複雜佈局
  • IO操做
  • 被子線程同步鎖block
  • 被Binder對端block
  • Binder被佔滿致使主線程沒法和SystemServer通訊
  • 得不到系統資源(CPU/RAM/IO)

從進程角度看發生緣由有:

  • 當前進程:主線程自己耗時或者主線程的消息隊列存在耗時操做、主線程被本進程的其它子線程所blocked
  • 遠端進程:binder call、socket通訊

Andorid系統監測ANR的核心原理是消息調度和超時處理。

ANR排查流程

一、Log獲取

一、抓取bugreport

adb shell bugreport > bugreport.txt
複製代碼

二、直接導出/data/anr/traces.txt文件

adb pull /data/anr/traces.txt trace.txt
複製代碼

二、搜索「ANR in」處log關鍵點解讀

  • 發生時間(可能會延時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使用量不多,說明主線程可能阻塞。

三、在bugreport.txt中根據pid和發生時間搜索到阻塞的log處

----- pid 10494 at 2019-11-18 15:28:29 -----
複製代碼

四、往下翻找到「main」線程則可看到對應的阻塞log

"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=
複製代碼

上述關鍵字段的含義以下所示:

  • tid:線程號
  • sysTid:主進程線程號和進程號相同
  • Waiting/Sleeping:各類線程狀態
  • nice:nice值越小,則優先級越高,-17~16
  • schedstat:Running、Runable時間(ns)與Switch次數
  • utm:該線程在用戶態的執行時間(jiffies)
  • stm:該線程在內核態的執行時間(jiffies)
  • sCount:該線程被掛起的次數
  • dsCount:該線程被調試器掛起的次數
  • self:線程自己的地址

補充加油站:各類線程狀態

須要注意的是,這裏的各類線程狀態指的是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
};
複製代碼

其它分析方法:Java線程調用分析方法

  • 先使用jps命令列出當前系統中運行的全部Java虛擬機進程,拿到應用進程的pid。
  • 而後再使用jstack命令查看該進程中全部線程的狀態以及調用關係,以及一些簡單的分析結果。

三、關於ANR的一些常見問題

一、sp調用apply致使anr問題?

雖然apply並不會阻塞主線程,可是會將等待時間轉嫁到主線程。

二、檢測運行期間是否發生過異常退出?

在應用啓動時設定一個標誌,在主動自殺或崩潰後更新標誌 ,下次啓動時檢測此標誌便可判斷。

四、理解ANR的觸發流程

broadcast跟service超時機制大抵相同,但有一個很是隱蔽的技能點,那就是經過靜態註冊的廣播超時會受SharedPreferences(簡稱SP)的影響。

當SP有未同步到磁盤的工做,則需等待其完成,才告知系統已完成該廣播。而且只有XML靜態註冊的廣播超時檢測過程會考慮是否有SP還沒有完成,動態廣播並不受其影響。

  • 對於Service, Broadcast, Input發生ANR以後,最終都會調用AMS.appNotResponding。
  • 對於provider,在其進程啓動時publish過程可能會出現ANR, 則會直接殺進程以及清理相應信息,而不會彈出ANR的對話框。
  • 對於輸入事件發生ANR,首先會調用InputMonitor.notifyANR,最終也會調用AMS.appNotResponding。

一、AMS.appNotResponding流程

  • 輸出ANR Reason信息到EventLog. 也就是說ANR觸發的時間點最接近的就是EventLog中輸出的am_anr信息。
  • 收集並輸出重要進程列表中的各個線程的traces信息,該方法較耗時。
  • 輸出當前各個進程的CPU使用狀況以及CPU負載狀況。
  • 將traces文件和 CPU使用狀況信息保存到dropbox,即data/system/dropbox目錄(ANR信息最爲重要的信息)。
  • 根據進程類型,來決定直接後臺殺掉,仍是彈框告知用戶。

二、AMS.dumpStackTraces流程

一、收集firstPids進程的stacks:

  • 第一個是發生ANR進程;
  • 第二個是system_server;
  • 其他的是mLruProcesses中全部的persistent進程。

二、收集Native進程的stacks。(dumpNativeBacktraceToFile)

  • 依次是mediaserver,sdcard,surfaceflinger進程。

三、收集lastPids進程的stacks:

  • 依次輸出CPU使用率top 5的進程;
注意

上述導出每一個進程trace時,進程之間會休眠200ms。

4、移動端業務高可用方案建設

一、業務高可用重要性

關於業務高可用重要性有以下五點:

  • 高可用
  • 性能
  • 業務
  • 側重於用戶功能完整可用
  • 真實影響收入

二、業務高可用方案建設

業務高可用方案建設須要注意的點比較繁雜,可是整體能夠歸結爲以下幾點:

  • 數據採集
  • 梳理項目主流程、核心路徑、關鍵節點
  • Aop自動採集、統一上報
  • 報警策略:閾值報警、趨勢報警、特定指標報警、直接上報(或底閾值)
  • 異常監控
  • 單點追查:須要針對性分析的特定問題,全量日誌回撈,專項分析
  • 兜底策略
  • 配置中心、功能開關
  • 跳轉分發中心(組件化路由)

三、移動端容災方案

災包括:

  • 性能異常
  • 業務異常

傳統流程:

用戶反饋、從新打包、渠道更新、不可接受。

容災方案建設

關於容災方案的建設主要能夠細分爲如下七點,下面,咱們分別來了解下。

一、功能開關

配置中心,服務端下發配置控制

針對場景
  • 功能新增
  • 代碼改動

二、統跳中心

  • 界面切換經過路由,路由決定是否重定向
  • Native Bug不能熱修復則跳轉到臨時H5頁面

三、動態化修復

熱修復能力,可監控、灰度、回滾、清除。

四、推拉結合、多場景調用保證到達率

五、Weex、RN增量更新

六、安全模式

微信讀書、蘑菇街、淘寶、天貓等「重運營」的APP都使用了安全模式保障客戶端啓動流程,啓動失敗後給用戶自救機會。先介紹一下它的核心特色:

  • 根據Crash信息自動恢復,屢次啓動失敗重置應用爲安裝初始狀態
  • 嚴重Bug可阻塞性熱修復
安全模式設計

配置後臺:統一的配置後臺,具有灰度發佈機制

一、客戶端能力:

  • 在APP連續Crash的狀況下具有分級、無感自修復能力
  • 具有同步熱修復能力
  • 具有指定觸發某項特定功能的能力
  • 具體功能註冊能力,方便後期擴展安全模式

二、數據統計及告警

  • 統一的數據平臺
  • 監控告警功能,及時發現問題
  • 查看熱修復成功率等數據

三、快速測試

  • 優化預發佈環境下測試
  • 優化迴歸驗證安全模式難點等
天貓安全模式原理

一、如何判斷異常退出?

APP啓動時記錄一個flag值,知足如下條件時,將flag值清空

  • APP正常啓動10秒
  • 用戶正常退出應用
  • 用戶主動從前臺切換到後臺

若是在啓動階段發生異常,則flag值不會清空,經過flag值就能夠判斷客戶端是否異常退出,每次異常退出,flag值都+1。

二、安全模式的分級執行策略

分爲兩級安全模式,連續Crash 2次爲一級安全模式,連續Crash 2次及以上爲二級安全模式。

業務線能夠在一級安全模式中註冊行爲,好比清空緩存數據,再進入該模式時,會使用註冊行爲嘗試修復客戶端 若是一級安全模式沒法修復APP,則進入二級安全模式將APP恢復到初次安裝狀態,並將Document、Library、Cache三個根目錄清空。

三、熱修復執行策略

只要發現配置中須要熱修復,APP就會同步阻塞進行熱修復,保證修復的及時性

四、灰度方案

灰度時,配置中會包含灰度、正式兩份配置及其灰度機率 APP根據特定算法算出本身是否知足灰度條件,則使用灰度配置

易用性考量

一、接入成本

完善文檔、接口簡潔

二、統一配置後臺

可按照APP、版本配置

三、定製性

支持定製功能,讓接入方來決定具體行爲

四、灰度機制

五、數據分析

採用統一數據平臺,爲安全模式改進提供依據

六、快速測試

建立更多的針對性測試案例,如模擬連續Crash

七、異常熔斷

當屢次請求失敗則可以讓網絡庫主動拒絕請求。

容災方案集合路徑

功能開關 -> 統跳中心 -> 動態修復 -> 安全模式

5、穩定性長效治理

要實現App穩定性的長效治理,咱們須要從 開發階段 => 測試階段 => 合碼階段 => 發佈階段 => 運維階段 這五個階段來作針對性地處理。

一、開發階段

  • 統一編碼規範、加強編碼功底、技術評審、CodeReview機制
  • 架構優化
  • 能力收斂
  • 統一容錯:如在網絡庫utils中統一對返回信息進行預校驗,如不合法就直接不走接下來的流程。

二、測試階段

  • 功能測試、自動化測試、迴歸測試、覆蓋安裝
  • 特殊場景、機型等邊界測試:如服務端返回異常數據、服務端宕機
  • 雲測平臺:提供更全面的機型進行測試

三、合碼階段

  • 編譯檢測、靜態掃描
  • 預編譯流程、主流程自動迴歸

四、發佈階段

  • 多輪灰度
  • 分場景、緯度全面覆蓋

五、運維階段

  • 靈敏監控
  • 回滾、降級策略
  • 熱修復、本地容災方案

6、穩定性優化問題

一、大家作了哪些穩定性方面的優化?

隨着項目的逐漸成熟,用戶基數逐漸增多,DAU持續升高,咱們遇到了不少穩定性方面的問題,對於咱們技術同窗遇到了不少的挑戰,用戶常用咱們的App卡頓或者是功能不可用,所以咱們就針對穩定性開啓了專項的優化,咱們主要優化了三項:

  • Crash專項優化
  • 性能穩定性優化
  • 業務穩定性優化

經過這三方面的優化咱們搭建了移動端的高可用平臺。同時,也作了不少的措施來讓App真正地實現了高可用。

二、性能穩定性是怎麼作的?

  • 全面的性能優化:啓動速度、內存優化、繪製優化
  • 線下發現問題、優化爲主
  • 線上監控爲主
  • Crash專項優化

咱們針對啓動速度,內存、佈局加載、卡頓、瘦身、流量、電量等多個方面作了多維的優化。

咱們的優化主要分爲了兩個層次,即線上和線下,針對於線下呢,咱們側重於發現問題,直接解決,將問題儘量在上線以前解決爲目的。而真正到了線上呢,咱們最主要的目的就是爲了監控,對於各個性能緯度的監控呢,可讓咱們儘量早地獲取到異常狀況的報警。

同時呢,對於線上最嚴重的性能問題性問題:Crash,咱們作了專項的優化,不只優化了Crash的具體指標,並且也儘量地獲取了Crash發生時的詳細信息,結合後端的聚合、報警等功能,便於咱們快速地定位問題。

三、業務穩定性如何保障?

  • 數據採集 + 報警
  • 須要對項目的主流程與核心路徑進行埋點監控
  • 同時還需知道每一步發生了多少異常,這樣,咱們就知道了全部業務流程的轉換率以及相應界面的轉換率
  • 結合大盤,若是轉換率低於某個值,進行報警
  • 異常監控 + 單點追查
  • 兜底策略,如天貓安全模式

移動端業務高可用它側重於用戶功能完整可用,主要是爲了解決一些線上一些異常狀況致使用戶他雖然沒有崩潰,也沒有性能問題,可是呢,只是單純的功能不可用的狀況,咱們須要對項目的主流程、核心路徑進行埋點監控,來計算每一步它真實的轉換率是多少,同時呢,還須要知道在每一步到底發生了多少異常。這樣咱們就知道了全部業務流程的轉換率以及相應界面的轉換率,有了大盤的數據呢,咱們就知道了,若是轉換率或者是某些監控的成功率低於某個值,那頗有可能就是出現了線上異常,結合了相應的報警功能,咱們就不須要等用戶來反饋了,這個就是業務穩定性保障的基礎。

同時呢,對於一些特殊狀況,好比說,開發過程中或代碼中出現了一些catch代碼塊,捕獲住了異常,讓程序不崩潰,這實際上是不合理的,程序雖然沒有崩潰,當時程序的功能已經變得不可用,因此呢,這些被catch的異常咱們也須要上報上來,這樣咱們才能知道用戶到底出現了什麼問題而致使的異常。此外,線上還有一些單點問題,好比說用戶點擊登陸一直進不去,這種就屬於單點問題,其實咱們是沒法找出其和其它問題的共性之處的,因此呢,咱們就必需要找到它對應的詳細信息。

最後,若是發生了異常狀況,咱們還採起了一系列措施進行快速止損。(=>4)

四、若是發生了異常狀況,怎麼快速止損?

  • 功能開關
  • 統跳中心
  • 動態修復:熱修復、資源包更新
  • 自主修復:安全模式

首先,須要讓App具有一些高級的能力,咱們對於任何要上線的新功能,要加上一個功能的開關,經過配置中心下發的開關呢,來決定是否要顯示新功能的入口。若是有異常狀況,能夠緊急關閉新功能的入口,那就可讓這個App處於可控的狀態了。

而後,咱們須要給App設立路由跳轉,全部的界面跳轉都須要經過路由來分發,若是咱們匹配到須要跳轉到有bug的這樣一個新功能時,那咱們就不跳轉了,或者是跳轉到統一的異常正處理中的界面。若是這兩種方式都不能夠,那就能夠考慮經過熱修復的方式來動態修復,目前熱修復的方案其實已經比較成熟了,咱們徹底能夠低成本地在咱們的項目中添加熱修復的能力,固然,若是有些功能是由RN或WeeX來實現就更好了,那就能夠經過更新資源包的方式來實現動態更新。而這些若是都不能夠的話呢,那就能夠考慮本身去給應用加上一個自主修復的能力,若是App啓動屢次的話,那就能夠考慮清空全部的緩存數據,將App重置到安裝的狀態,到了最嚴重的等級呢,能夠阻塞主線程,此時必定要等App熱修復成功以後才容許用戶進入。

7、總結

Android穩定性優化是一個須要 長期投入,持續運營和維護 的一個過程,上文中咱們不只深刻探討了Java Crash、Native Crash和ANR的解決流程及方案,還分析了其內部實現原理和監控流程。到這裏,能夠看到,要想作好穩定性優化,咱們 必須對虛擬機運行、Linux信號處理和內存分配 有必定程度的瞭解,只有深刻了解這些底層知識,咱們才能比別人設計出更好的穩定性優化方案

參考連接:

一、《Android性能優化最佳實踐》第五章 穩定性優化

二、慕課網之國內Top團隊大牛帶你玩轉Android性能分析與優化 第十一章 App穩定性優化

三、極客時間之Android開發高手課 崩潰優化

四、Android 平臺 Native 代碼的崩潰捕獲機制及實現

五、安全模式:天貓App啓動保護實踐

六、美團外賣Android Crash治理之路 (進階)

七、海神平臺Crash監控SDK(Android)開發經驗總結

八、Android Native Crash 收集

九、理解Android Crash處理流程

十、Android應用ANR分析

十一、理解Android ANR的觸發原理

十二、Input系統—ANR原理分析

1三、ANR監測機制

1四、理解Android ANR的觸發原理

1五、理解Android ANR的信息收集過程

1六、應用與系統穩定性第一篇---ANR問題分析的通常套路

1七、巧妙定位ANR問題

1八、剖析 SharedPreference apply 引發的 ANR 問題

1九、Linux錯誤信號

Contanct Me

● 微信:

歡迎關注個人微信:bcce5360

● 微信羣:

微信羣若是不能掃碼加入,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~

About me

很感謝您閱讀這篇文章,但願您能將它分享給您的朋友或技術羣,這對我意義重大。

但願咱們能成爲朋友,在 Github掘金上一塊兒分享知識。

相關文章
相關標籤/搜索