作了好久的面試專題,不知道對各位須要面試和有跳槽想法的小夥伴有沒有幫助,今天收集一篇關於崩潰優化方面的文章,面試方面的收集,後續我還會持續更新若是以爲有用能夠點個關注java
開發人員碰到 APP 崩潰(閃退)什麼辦?很多人會說根據 Log,找到閃退的代碼,捕獲異常,「消化」掉了全部 Java 崩潰。至於程序是否會出現其餘異常表現,那是上帝要管的事情。是的,這種方法對於緊急狀況下不失爲一種解決辦法,但閃退的真相是什麼?是否從根源上解決問題呢?android
崩潰率是衡量一個應用質量高低的基本指標,那麼,該怎樣客觀地衡量崩潰這個指標,以及又該如何看待和崩潰相關的穩定性。面試
Android 的兩種崩潰:微信
簡單來講,Java 崩潰就是在 Java 代碼中,出現了未捕獲異常,致使程序異常退出。那 Native 崩潰通常都是由於在 Native 代碼中訪問非法地址,也多是地址對齊出現了問題,或者發生了程序主動 Abort,這些都會產生相應的 Signal 信號,致使程序異常退出。架構
「崩潰」就是程序出現異常,而一個產品的崩潰率,跟咱們如何捕獲、處理這些異常有比較大的關係。對於不少中小型公司來講,能夠選擇一些第三方的服務。目前各類平臺也是百花齊放,包括阿里的友盟、騰訊的Bugly、網易雲捕、Google 的 Firebase 等等。要懂得借力!app
崩潰率是否是就能徹底等價於應用的穩定性呢?答案是確定不行。處理了崩潰,咱們還會常常遇到 ANR(Application Not Responding,程序沒有響應)這個問題。框架
出現 ANR 的時候,系統還會彈出對話框打斷用戶的操做,這是用戶很是不能忍受的。ide
ANR處理方法:
使用 FileObserver 監聽 /data/anr/traces.txt 的變化。很是不幸的是,不少高版本的 ROM,已經沒有讀取這個文件的權限了。這個時候你可能只能思考其餘路徑,海外可使用 Google Play 服務,而國內微信利用Hardcoder框架(HC 框架是一套獨立於安卓系統實現的通訊框架,它讓 App 和廠商 ROM 可以實時「對話」了,目標就是充分調度系統資源來提高 App 的運行速度和畫質,切實提升你們的手機使用體驗)向廠商獲取了更大的權限。也能夠將手機 ROOT 掉,而後取得 traces.txt 文件。工具
除了常見的崩潰,還有一些會致使應用異常退出的狀況,例如:oop
咱們能夠在應用啓動的時候設定一個標誌,在主動自殺或崩潰後更新標誌,這樣下次啓動時經過檢測這個標誌就能確認運行期間是否發生過異常退出。對應上面的五種退出場景,咱們排除掉主動自殺和崩潰(崩潰會單獨的統計)這兩種場景,但願能夠監控到剩下三種的異常退出,理論上這個異常捕獲機制是能夠達到 100% 覆蓋的。
經過這個異常退出的檢測,能夠反映如 ANR、low memory killer、系統強殺、死機、斷電等其餘沒法正常捕獲到的問題。固然異常率會存在一些誤報,好比用戶從系統的任務管理器中劃掉應用。對於線上的大數據來講,仍是能夠幫助咱們發現代碼中的一些隱藏問題。
根據應用的先後臺狀態,咱們能夠把異常退出分爲前臺異常退出和後臺異常退出。「被系統殺死」 是後臺異常退出的主要緣由,固然咱們會更關注前臺的異常退出的狀況,這會跟 ANR、OOM 等異常狀況有更大的關聯。
咱們天天工做也會遇到各類各樣的疑難問題,「崩潰」就是其中比較常見的一種問題。解決問題跟破案同樣須要經驗,咱們分析的問題越多越熟練,定位問題就會越快越準。固然這裏也有不少套路,好比對於 「案發現場」 咱們應該留意哪些信息?怎樣找到更多的 「證人」 和 「線索」 ? 「偵查案件」 的通常流程是什麼?對不一樣類型的 「案件」 分別應該使用什麼樣的調查方式?
要相信 「真相永遠只有一個」,崩潰也並不可怕。
崩潰現場是咱們的「第一案發現場」,它保留着不少有價值的線索。如今能夠挖掘到的信息越多,下一步分析的方向就越清晰,而不是去靠盲目猜想。
從崩潰的基本信息,咱們能夠對崩潰有初步的判斷。進程名、線程名。崩潰的進程是前臺進程仍是後臺進程,崩潰是否是發生在 UI 線程。
崩潰堆棧和類型。崩潰是屬於 Java 崩潰、Native 崩潰,仍是 ANR,對於不一樣類型的崩潰關注的點也不太同樣。特別須要看崩潰堆棧的棧頂,看具體崩潰在系統的代碼,仍是 APP 代碼裏面。
FATAL EXCEPTION: main Process: com.cchip.csmart, PID: 27456 java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(int)' on a null object reference at com.cchip.alicsmart.activity.SplashActivity$1.handleMessage(SplashActivity.java:67) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:179) at android.app.ActivityThread.main(ActivityThread.java:5672) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:674)
系統的信息有時候會帶有一些關鍵的線索,對咱們解決問題有很是大的幫助。
Logcat。這裏包括應用、系統的運行日誌。因爲系統權限問題,獲取到的 Logcat 可能只包含與當前 APP 相關的。其中系統的 event logcat 會記錄 APP 運行的一些基本狀況,記錄在文件 /system/etc/event-log-tags 中。
//system logcat: 10-25 17:13:47.788 21430 21430 D dalvikvm: Trying to load lib ... //event logcat: 10-25 17:13:47.788 21430 21430 I am_on_resume_called: 生命週期 10-25 17:13:47.788 21430 21430 I am_low_memory: 系統內存不足 10-25 17:13:47.788 21430 21430 I am_destroy_activity: 銷燬 Activty 10-25 17:13:47.888 21430 21430 I am_anr: ANR 以及緣由 10-25 17:13:47.888 21430 21430 I am_kill: APP 被殺以及緣由
機型、系統、廠商、CPU、ABI、Linux 版本等。經過採集多達幾十個維度,這對尋找共性問題會頗有幫助。
OOM、ANR、虛擬內存耗盡等,不少崩潰都跟內存有直接關係。若是把用戶的手機內存分爲「2GB 如下」和「2GB 以上」兩個區,就會發現「2GB 如下」用戶的崩潰率是「2GB 以上」用戶的幾倍。
系統剩餘內存。關於系統內存狀態,能夠直接讀取文件 /proc/meminfo。當系統可用內存很小(低於 MemTotal 的 10%)時,OOM、大量 GC、系統頻繁自殺拉起等問題都很是容易出現。
應用使用內存。包括 Java 內存、RSS(Resident Set Size)、PSS(Proportional Set Size),咱們能夠得出應用自己內存的佔用大小和分佈。PSS 和 RSS 經過 /proc/self/smap 計算,能夠進一步獲得例如 apk、dex、so 等更加詳細的分類統計。
虛擬內存。虛擬內存能夠經過 /proc/self/status 獲得,經過 /proc/self/maps 文件能夠獲得具體的分佈狀況。有時候咱們通常不過重視虛擬內存,可是不少相似 OOM、tgkill 等問題都是虛擬內存不足致使的。
Name: com.xmamiga.name // 進程名 FDSize: 800 // 當前進程申請的文件句柄個數 VmPeak: 3004628 kB // 當前進程的虛擬內存峯值大小 VmSize: 2997032 kB // 當前進程的虛擬內存大小 Threads: 600 // 當前進程包含的線程個數
通常來講,對於 32 位進程,若是是 32 位的 CPU,虛擬內存達到 3GB 就可能會引發內存申請失敗的問題。若是是 64 位的 CPU,虛擬內存通常在 3~4GB 之間。固然若是咱們支持 64 位進程,虛擬內存就不會成爲問題。Google Play 要求 2019 年 8 月必定要支持 64 位,在國內雖然支持 64 位的設備已經在 90% 以上了,可是商店都不支持區分 CPU 架構類型發佈,普及起來須要更長的時間。
有的時候會發現應用堆內存和設備內存都很是充足,仍是會出現內存分配失敗的狀況,這跟資源泄漏可能有比較大的關係。
文件句柄 fd。文件句柄的限制能夠經過 /proc/self/limits 得到,通常單個進程容許打開的最大文件句柄個數爲 1024。可是若是文件句柄超過 800 個就比較危險,須要將全部的 fd 以及對應的文件名輸出到日誌中,進一步排查是否出現了有文件或者線程的泄漏。
opened files count 812: 0 -> /dev/null 1 -> /dev/log/main4 2 -> /dev/binder 3 -> /data/data/com.xmamiga.sample/files/test.config ...
線程數。當前線程數大小能夠經過上面的 status 文件獲得,一個線程可能就佔 2MB 的虛擬內存,過多的線程會對虛擬內存和文件句柄帶來壓力。根據個人經驗來講,若是線程數超過 400 個就比較危險。須要將全部的線程 id 以及對應的線程名輸出到日誌中,進一步排查是否出現了線程相關的問題。
threads count 412: 1820 com.xmamiga.crashsdk 1844 ReferenceQueueD 1869 FinalizerDaemon ...
JNI。使用 JNI 時,若是不注意很容易出現引用失效、引用爆表等一些崩潰。
除了系統,其實咱們的應用更懂本身,能夠留下不少相關的信息。崩潰場景。崩潰發生在哪一個 Activity 或 Fragment,發生在哪一個業務中; 關鍵操做路徑,不一樣於開發過程詳細的打點日誌,咱們能夠記錄關鍵的用戶操做路徑,這對咱們復現崩潰會有比較大的幫助。其餘自定義信息。不一樣的應用關心的重點可能不太同樣。
有了這麼多現場信息以後,就能夠開始真正的「破案」之旅了。絕大部分的 「案件」 只要肯花功夫,最後都能真相大白。不要畏懼問題,通過耐心和細心地分析,總能敏銳地發現一些異常或關鍵點,而且還要勇於懷疑和驗證。
確認和分析重點,關鍵在於終過日誌中找到重要的信息,對問題有一個大體判斷。通常來講,我建議在肯定重點這一步能夠關注如下幾點。
Java 崩潰。Java 崩潰類型比較明顯,好比 NullPointerException 是空指針,OutOfMemoryError 是資源不足,這個時候須要去進一步查看日誌中的 「內存信息」和「資源信息」。
Native 崩潰。須要觀察 signal、code、fault addr 等內容,以及崩潰時 Java 的堆棧。關於各 signal 含義的介紹,你能夠查看崩潰信號介紹。比較常見的是有 SIGSEGV 和 SIGABRT,前者通常是因爲空指針、非法指針形成,後者主要由於 ANR 和調用 abort() 退出所致使。
ANR。先看看主線程的堆棧,是不是由於鎖等待致使。接着看看 ANR 日誌中 iowait、CPU、GC、system server 等信息,進一步肯定是 I/O 問題,或是 CPU 競爭問題,仍是因爲大量 GC 致使卡死。
若是使用了上面的方法仍是不能有效定位問題,咱們能夠嘗試查找這類崩潰有沒有什麼共性。找到了共性,也就能夠進一步找到差別,離解決問題也就更進一步。
機型、系統、ROM、廠商、ABI,這些採集到的系統信息均可以做爲維度聚合,共性問題例如是否是隻出如今 x86 的手機,是否是隻有三星這款機型,是否是隻在 Android 8.0 的系統上。應用信息也能夠做爲維度來聚合,好比正在打開的連接、正在播放的視頻、國家、地區等。
找到了共性,能夠對你下一步復現問題有更明確的指引。
若是咱們已經大概知道了崩潰的緣由,爲了進一步確認更多信息,就須要嘗試復現崩潰。若是咱們對崩潰徹底沒有頭緒,也但願經過用戶操做路徑來嘗試重現,而後再去分析崩潰緣由。
「只要能本地復現,我就能解」,相信這是不少開發跟測試說過的話。有這樣的底氣主要是由於在穩定的復現路徑上面,咱們能夠採用增長日誌或使用 Debugger、GDB 等各類各樣的手段或工具作進一步分析。
咱們可能會遇到了各類各樣的奇葩問題。好比某個廠商改了底層實現、新的 Android 系統實現有所更改,都須要去 Google、翻源碼,有時候還須要去摳廠商的 ROM 或手動刷 ROM。不少疑難問題須要咱們耐得住寂寞,反覆猜想、反覆發灰度、反覆驗證。–但這種問題仍是要看問題的嚴重程序,不可撿了芝麻丟了西瓜。
系統崩潰經常令咱們感到很是無助,它多是某個 Android 版本的 Bug,也多是某個廠商修改 ROM 致使。這種狀況下的崩潰堆棧可能徹底沒有咱們本身的代碼,很難直接定位問題。能作的有:
查找可能的緣由。經過上面的共性歸類,咱們先看看是某個系統版本的問題,仍是某個廠商特定 ROM 的問題。雖然崩潰日誌可能沒有咱們本身的代碼,但經過操做路徑和日誌,能夠找到一些懷疑的點。
嘗試規避。查看可疑的代碼調用,是否使用了不恰當的 API,是否能夠更換其餘的實現方式規避。
Hook 解決。這裏分爲 Java Hook 和 Native Hook。它可能只出如今 Android 7.0 的系統中,參考 Android 8.0 的作法,直接 catch 住這個異常。
若是作到了上面說的這些,以上大部分的崩潰應該都能解決或者規避,大部分的系統崩潰也是如此。固然總有一些疑難問題須要依賴到用戶的真實環境,這些須要具有相似動態跟蹤和調試的能力。
崩潰攻防是一個長期的過程,咱們儘量地提早預防崩潰的發生,將它消滅在萌芽階段。做爲技術人員,咱們不該該盲目追求崩潰率這一個數字,應該以用戶體驗爲先,若是強行去掩蓋一些問題每每更加拔苗助長。咱們不該該隨意使用 try catch 去隱藏真正的問題,要從源頭入手,瞭解崩潰的本質緣由,保證後面的運行流程。在解決崩潰的過程,也要作到由點到面,不能只針對這個崩潰去解決,而應該要考慮這一類崩潰怎麼解決和預防。