Android穩定性優化總結

App Crash對於用戶來講是一種最糟糕的體驗,它會導致流程中斷、app口碑變差、app卸載、用戶流失、訂單流失等。

Crash治理方法

常見Crash的處理方式:

• 根據Crash統計平臺的堆棧,用戶日誌,操作路徑定位和解決。

• 尋找共性,機型、品牌、系統版本、所在頁面、用戶操作等輔助解決問題。

• 復現場景,能夠復現通常就很容易解決,可以線下復現或者雲真機復現。

• 對crash率較高的模塊進行業務梳理,排查,重構等。

• 與第三方sdk溝通升級解決問題,修改SDK的使用方式。

Crash治理實踐

1.預防Crash
對於穩定性來說,如果App已經到了線上才發現異常,那其實已經造成了損失,所以,對於穩定性的優化,其重點在於預防。Gerrit 是一個免費、開放源代碼的代碼審查軟件,使用網頁界面。我們公司大多數項目都使用Gerrit進行CodeReview,從項目開發階段就會審查各種潛在的crash或者邏輯上的問題,嚴格按照代碼+1、+2之後才能提交入庫。

2.長效保持需要科學流程
應用穩定性的建設過程是一個細活,所以很容易出現這個版本優化好了,但是在接下來的版本中如果我們不管它,它就會發生持續惡化的情況,因此,我們必須從項目研發的每一個流程入手,建立科學完善的相關規範,才能保證長效的優化效果。在每個版本都要追蹤崩潰監測平臺,將對應的崩潰分發給對應業務的開發人員處理。

Crash率評價
性能指標 優秀值 及格值 極差值 行業參考值
崩潰率(%) <=0.1 0.6 >=1 0.5

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

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

Crash關鍵問題

如果應用發生了Crash,我們應該儘可能還原Crash現場。因此,我們需要全面地採集應用發生Crash時的相關信息,如下所示:

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

Crash監控方案

Java中的Thread定義了一個接口: UncaughtExceptionHandler ;用於處理未捕獲的異常導致線程的終止(注意:catch了的是捕獲不到的),當我們的應用crash的時候,就會走 UncaughtExceptionHandler.uncaughtException ,在該方法中可以獲取到異常的信息,我們通過 Thread.setDefaultUncaughtExceptionHandler 該方法來設置線程的默認異常處理器,我們可以將異常信息保存到本地或者是上傳到服務器,方便我們快速的定位問題。

class MyExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
        File file = dealException(thread, throwable);
        //上傳服務器
        ...
    }

    /*** 導出異常信息到SD卡 ** @param e */
    private File dealException(Thread thread, Throwable throwable) {
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        File crashFolder = new File(mContext.getExternalCacheDir().getAbsoluteFile(), CrashMonitor.DEFAULT_JAVA_CRASH_FOLDER_NAME);
        if (!crashFolder.exists()) {
            crashFolder.mkdirs();
        }
        File crashFile = new File(crashFolder, time + FILE_NAME_SUFFIX);
        try {
            // 往文件中寫入數據
            PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(crashFile)));
            pw.println(time);
            pw.println(thread);
            pw.println(getPhoneInfo());
            throwable.printStackTrace(pw);  //將異常信息堆棧寫入文件
            // 寫入crash堆棧
            pw.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return crashFile;
    }

    private String getPhoneInfo() {
        PackageManager pm = mContext.getPackageManager();
        PackageInfo pi = null;
        StringBuilder sb = new StringBuilder();

        try {
            pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);

            // App版本
            sb.append("App Version: ");
            sb.append(pi.versionName);
            sb.append("_");
            sb.append(pi.versionCode + "\n");
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        // Android版本號
        sb.append("OS Version: ");
        sb.append(Build.VERSION.RELEASE);
        sb.append("_");
        sb.append(Build.VERSION.SDK_INT + "\n");

        // 手機制造商
        sb.append("Vendor: ");
        sb.append(Build.MANUFACTURER + "\n");

        // 手機型號
        sb.append("Model: ");
        sb.append(Build.MODEL + "\n");

        // CPU架構
        sb.append("CPU: ");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            sb.append(Arrays.toString(Build.SUPPORTED_ABIS));
        } else {
            sb.append(Build.CPU_ABI);
        }
        return sb.toString();
    }
}

然後在application中設置自定義的UncaughtExceptionHandler。

Thread.setDefaultUncaughtExceptionHandler(MyExceptionHandler())

ANR問題分析

ANR發生的原因總結和解決辦法

1.在主線程中,進行了觸屏點擊滑動等操作,在5秒之內對該事件沒有響應,就會導致ANR(例如,按鍵按下,屏幕觸摸)
2.BroadcastReceiver在10秒內沒有執行完畢
3.service是20秒

根本原因是在主線程進行了耗時操作,導致後面的消息無法處理,比如:
1.耗時的網絡訪問
2.大量的數據讀寫
3.數據庫操作
4.在主線程中調用thread的join()方法、sleep()方法、wait()方法
5.其他線程持有鎖,導致主線程等待超時
6.子線程終止或崩潰導致主線程一直等待

解決的主要辦法就是開啓子線程去處理這些耗時操作。修改主線程去等待子線程的鎖。

ANR排查流程

1、抓取bugreport

adb shell bugreport > bugreport.txt

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

adb pull /data/anr/traces.txt trace.txt
本地ANR監控:

監控方法:通過 FileOberver 監控 data/anr 文件夾下文件的變化,來確定ANR的發生。
獲取信息:主線程堆棧信息+ANR信息。

由於剛監控到ANR發生時進程並沒有進入ANR狀態,而是先向 /data/anr/ 文件中寫入進程信息,此時並不會立即獲取到進程的ANR信息,需要循環等待進程進入ANR狀態。

解決ANR問題,首先要做的是找到問題,線下我們可以通過ADB命令導出ANR文件進行分析,線上我們可以使用FileObserver或ANR-WatchDog保存ANR堆棧信息,然後上傳到服務器。

ANR發生之後我們可以使用以下命令導出ANR文件:

adb bugreport

在關鍵詞DALVIK THREADS和"main" prio=5 tid=1 Native下面可獲取ANR的日誌:

使用FileObserver監控data/anr目錄,當文件狀態發生改變、創建或刪除時,說明當前發生了anr事件。

創建類繼承FileObserver,監控data/anr文件:

class AnrFileObserver extends FileObserver {

    @Override
    public void onEvent(int event, @Nullable String path) {
        switch (event) {
            case FileObserver.ACCESS:

                break;
            case FileObserver.CREATE:

                break;
            case FileObserver.MODIFY:

                break;
        }
    }
}
線上ANR監控:

接入 bugly,根據App版本,手機型號,發生時間,次數,產生位置進行位置問題定位並解決。

ANR異常我們可分爲線上監測和線下監測兩個方向

線上監測主要是利用FileObserver進行ANR目錄文件變化監聽,以ANR-WatchDog進行補充。
FileObserver在使用過程中應注意高版本程序不可用以及預防死鎖出現。
線下監測主要是在報錯之後利用ADB命令將錯誤的日誌導出並找到錯誤的類進行分析。

參考:
https://www.jianshu.com/p/8abce3ff4687

如果發生了異常情況,怎麼快速止損?

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

2.關閉灰度發佈
如果問題更嚴重,範圍更廣,可以立即關閉灰度發佈版本。

3.動態修復:熱修復、資源包更新
目前熱修復的方案其實已經比較成熟了,我們完全可以低成本地在我們的項目中添加熱修復的能力,當然,如果有些功能是由RN或WeeX來實現就更好了,那就可以通過更新資源包的方式來實現動態更新。

參考:
https://mp.weixin.qq.com/s/Wwazb5HFd5hunq5GOkjWmQ

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章