天天看點

【Android】Android Crash之異常資訊回報機制

1.為什麼需要回報Crash報告?

       Crash最通俗直覺的感受就是App軟體出現崩潰導緻的閃退等現象,在Android原生态下會出現一個App Force Close的Dialog,但是對于使用者體驗相當不好。Crash的産生是不可避免的,它産生的原因可能來自于Android底層的Bug,或是因為網絡不暢,又或者是手機适配性問題,更嚴重的是代碼品質不過關。Crash的産生是我們最不願意看到的,即使我們花費大量時間進行測試,測試,再測試,bug還是會産生,為了能夠擷取到Crash資訊,正式的軟體中都會有Crash的回報機制,開發人員會根據這些Crash資訊對軟體進行改進。

2.如何擷取這些Crash資訊?

      1)首先,先看這樣一個方法

       public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {        Thread.defaultUncaughtHandler = handler;    }

       這個方法可以設定系統的預設異常處理器,我們可以利用這個方法解決應用中常見的crash問題。當crash發生的時候,我們可以捕獲到異常資訊,把異常資訊存儲到SD卡中,然後在合适的時機通過網絡将crash資訊上傳到伺服器上,這樣開發人員就可以分析使用者crash的場景進而在後面的版本中修複此類crash。我們還可以在crash發生時,彈出一個通知告訴使用者程式crash了,然後再退出,這樣做比閃退要溫和一點。

       2)再看一個Java中的接口——UncaughtExceptionHandler

static interface

  Thread.UncaughtExceptionHandler

           當 Thread 因未捕獲的異常而突然終止時,調用處理程式的接口。

我們可以實作這個接口,然後在這個接口中的uncaughtException(Thread thread, Throwable ex)方法中實作對Crash資訊的捕獲。

3.思路描述

4.具體實作

1)實作異常處理CrashHandler

package com.example.crash.overall; import java.io.BufferedWriter;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.io.PrintWriter;import java.lang.Thread.UncaughtExceptionHandler;import java.text.SimpleDateFormat;import java.util.Date; import com.example.crash.utils.AppUtil; import android.content.Context;import android.os.Environment;import android.os.Process;import android.util.Log; public class CrashHandler implements UncaughtExceptionHandler {     private static final String TAG = "CrashHandler";     private Context context;     private UncaughtExceptionHandler defaultExceptionHandler;     private static CrashHandler crashHandler;     private CrashHandler() {        ;    }     // 單例模式    public static CrashHandler getInstance() {        if (null == crashHandler) {            crashHandler = new CrashHandler();        }         return crashHandler;    }     public void init(Context context) {        // 系統預設的異常處理器        defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();        // 将目前執行個體設為系統預設的異常處理器        Thread.setDefaultUncaughtExceptionHandler(this);        // 擷取App資訊時使用者此參數        context = context.getApplicationContext();    }     @Override    public void uncaughtException(Thread thread, Throwable ex) {         // 導出崩潰資訊到日志檔案        dumpExceptionToSDCard(ex);         // 列印崩潰資訊到Log日志        ex.printStackTrace();         // 如果系統提供了預設的異常處理器,則交給系統去結束我們的程式,否則就由我們自己結束自己        if (null != defaultExceptionHandler) {            defaultExceptionHandler.uncaughtException(thread, ex);        } else {            Process.killProcess(Process.myPid());        }    }     // 導出崩潰資訊到日志檔案    private void dumpExceptionToSDCard(Throwable ex) {        // 檢測SDCard是否可用--如果不可以在log中給予提醒        if (!Environment.getExternalStorageState().equals(                Environment.MEDIA_MOUNTED)) {            Log.w(TAG, "SDCard unmounted!");            return;        }         // 判斷Crash檔案夾是否存在,如果不存在則建立        File crashDir = new File(Constants.CRASH_FILE_PATH);        if (!crashDir.exists()) {            crashDir.mkdir();        }         // 建立目前崩潰日志檔案        String currentTime = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")                .format(new Date(System.currentTimeMillis()));        String logFileName = currentTime + ".txt";        File logFile = new File(crashDir, logFileName);         BufferedWriter bufferedWriter = null;        PrintWriter printWriter = null;        try {            bufferedWriter = new BufferedWriter(new FileWriter(logFile));            printWriter = new PrintWriter(bufferedWriter);             // 崩潰發生時間            printWriter.write(currentTime);            // 崩潰手機的系統資訊及其使用者所用的軟體資訊            String phoneInfo = new AppUtil(context).getPhoneInfoToCrash();            printWriter.write(phoneInfo);            // 崩潰資訊            ex.printStackTrace(printWriter);             printWriter.flush();        } catch (IOException e) {            e.printStackTrace();        } finally {            if (null != bufferedWriter) {                try {                    bufferedWriter.close();                } catch (IOException e) {                    e.printStackTrace();                }            }             if (null != printWriter) {                printWriter.close();            }        }    } }

2)擷取手機及App軟體等相關資訊

package com.example.crash.utils; import android.content.Context;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.content.pm.PackageManager.NameNotFoundException; public class AppUtil {     private Context context;     public AppUtil(Context context) {        this.context = context;    }     // 擷取App的VersionCode    public int getAppVersionCode() {        PackageManager packageManager = context.getPackageManager();         PackageInfo packageInfo = null;        try {            packageInfo = packageManager.getPackageInfo(                    context.getPackageName(), PackageManager.GET_ACTIVITIES);        } catch (NameNotFoundException e) {            e.printStackTrace();        }         int versionCode = 0;        if (null != packageInfo) {            versionCode = packageInfo.versionCode;        }         return versionCode;    }     // 擷取App的VersionName    public String getAppVersionName() {        PackageManager packageManager = context.getPackageManager();         PackageInfo packageInfo = null;        try {            packageInfo = packageManager.getPackageInfo(                    context.getPackageName(), PackageManager.GET_ACTIVITIES);        } catch (NameNotFoundException e) {            e.printStackTrace();        }         return packageInfo.versionName;    }     public String getPhoneInfoToCrash() {        StringBuilder stringBuilder = new StringBuilder();         stringBuilder.append("PackageName:").append(context.getPackageName())                .append('\n');        stringBuilder.append("VesionCode:").append(getAppVersionCode())                .append('\n');        stringBuilder.append("VersionName:").append(getAppVersionName())                .append('\n');        stringBuilder.append("OS Version:")                .append(android.os.Build.VERSION.RELEASE).append('_')                .append(android.os.Build.VERSION.SDK_INT).append('\n');        stringBuilder.append("Model:").append(android.os.Build.MODEL)                .append('\n');        stringBuilder.append("Manufacturer:")                .append(android.os.Build.MANUFACTURER).append('\n');        stringBuilder.append("CPU:").append(android.os.Build.CPU_ABI)                .append('\n');         return stringBuilder.toString();    }}

3)為UI線程添加預設異常事件Handler

//Thread類中辨別預設異常事件Handler的成員

private static UncaughtExceptionHandler defaultUncaughtHandler;

這裡涉及到在哪裡添加的問題,從源碼中注意到,這個defaultUncaughtHandler是Thread類中一個靜态的成員,是以,按道理,我們為任意一個線程設定異常處理,所有的線程都應該能共用這個異常處理器,這個是我的猜測,沒有經過驗證,不過沒關系,有一個觀點是大家都認可的:就是為主線程也就是ui線程添加異常程式器。為了在ui線程中添加異常處理Handler,我們推薦大家在Application中添加而不是在Activity中添加。Application辨別着整個應用,在Android聲明周期中是第一個啟動的,早于任何的Activity、Service等。

package com.example.crash.overall; import android.app.Application; public class App extends Application {     @Override    public void onCreate() {        super.onCreate();         // 開啟崩潰日志,這樣我們的程式就能捕獲未處理的異常        CrashHandler crashHandler = CrashHandler.getInstance();        crashHandler.init(getApplicationContext());    } }

4.測試代碼

package com.example.crash.activity; import android.app.Activity;import android.os.Bundle;import android.view.Menu;import android.view.View; import com.example.crash.R; public class MainActivity extends Activity {     @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);     }     @Override    public boolean onCreateOptionsMenu(Menu menu) {        // Inflate the menu; this adds items to the action bar if it is present.        getMenuInflater().inflate(R.menu.main, menu);        return true;    }     public void testCrash(View view) {        if (1 > 0) {            throw new RuntimeException("Test Exception!!!");        }    } }

5.備注

     日志的上傳屬于網絡相關的内容,暫且不做專門介紹

參考:

       1.http://blog.csdn.net/singwhatiwanna/article/details/17289479

       2.http://www.cnblogs.com/draem0507/archive/2013/05/25/3099461.html

       3.http://blog.csdn.net/wangjia55/article/details/14000833

--------------------- 

作者:小屁孩2013 

來源:CSDN 

原文:https://blog.csdn.net/u010119170/article/details/38236991 

版權聲明:本文為部落客原創文章,轉載請附上博文連結!