1. 程式人生 > >Android全域性異常捕獲並彈窗提示

Android全域性異常捕獲並彈窗提示

  Android 難免有崩潰的時候,但是崩潰了該如何處理呢?雖然那天有位同仁說 “既然崩潰了,使用者體驗就差了,心裡會想這是毛APP,下次也不想用了” ,所以檢查BUG以防崩潰是必須的,但是也需要一個後備方案,崩潰了能友好些,我們也能收集一些崩潰的資訊。

  說到全域性捕獲異常的UncaughtExceptionHandler,就不得不說期間遇到的各種坑:

  1. 初始化肯定在Application,網上說的Activity啟各種不認同。但在Application啟就存在不能彈AlertDialog的問題(目前不確定,不知道是自己哪裡沒處理好還是的確是這個問題,有時間再驗證一下)

  2. 崩潰不一定是單次,在多層Activity中,崩潰一個頂層的Activity可能導致下層的Activity連續崩潰,所以uncaughtException可能會捕獲到多次崩潰資訊(具體影響後面會說到)

先來張崩潰後的效果圖:

背景是另一個APP,當前的APP已崩潰並彈出該提示

崩潰後

實現流程:

寫個類繼承於UncaughtExceptionHandler,實現方法

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null
) { // 如果使用者沒有處理則讓系統預設的異常處理器來處 mDefaultHandler.uncaughtException(thread, ex); } else { // 跳轉到崩潰提示Activity Intent intent = new Intent(mContext, CrashDialog.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); System.exit(0
);// 關閉已奔潰的app程序 } }

然後轉handleException方法處理異常:

    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }

        // 收集錯誤資訊
        getCrashInfo(ex);

        return true;
    }

  上面的程式碼很清楚了,如果異常被捕獲到並且異常資訊不會NULL,處理完則跳轉到CrashDialog。為什麼跳Activity用Dialog樣式,而不直接彈AlertDialog,是因為的確彈不出來。

收集錯誤資訊:

private void getCrashInfo(Throwable ex) {
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();
        String errorMessage = writer.toString();
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            String mFilePath = Environment.getExternalStorageDirectory() + "/" + App.ERROR_FILENAME;
            FileTxt.WirteTxt(mFilePath, FileTxt.ReadTxt(mFilePath) + '\n' + errorMessage);
        } else {
            Log.i(App.TAG, "哦豁,說好的SD呢...");
        }
    }

  是的,我把錯誤資訊寫到了儲存並在剛才的CrashDialog中讀取。為什麼不直接傳值呢?因為剛說到的坑第2條,多次崩潰的情況下,將導致直接傳值只會傳最後一次崩潰資訊,而最後一次崩潰資訊並不是主要引發崩潰的點,收集上來的錯誤資訊可讀性不大。那為什麼我不寫個全域性變數來儲存呢?因為嘗試過,不知道是機型問題(Huawei Mate7 - API 23)還是全部問題,變數壓根就不記錄資料,最後只有將資訊依次寫到儲存。

主要程式碼

App.java

package cn.qson.androidcrash;

/**
 *  @author x024
 */

import android.app.Application;

public class App extends Application {

    public final static String TAG = "x024";
    public final static String ERROR_FILENAME = "x024_error.log";

    @Override
    public void onCreate() {
        super.onCreate();

        CrashHanlder.getInstance().init(this);

    }
}

CrashHanlder.java

package cn.qson.androidcrash;

/**
 *  @author x024
 */

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;

import android.content.Context;
import android.content.Intent;
import android.os.Environment;
import android.util.Log;

/**
 * 收集錯誤報告並上傳到伺服器
 * 
 * @author x024
 *
 */
public class CrashHanlder implements UncaughtExceptionHandler {
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    // CrashHandler例項
    private static CrashHanlder INSTANCE = new CrashHanlder();
    // 程式的Context物件
    private Context mContext;

    private CrashHanlder() {
    }

    public static CrashHanlder getInstance() {
        return INSTANCE;
    }

    /**
     * 初始化
     * 
     * @param context
     */
    public void init(Context context) {
        mContext = context;
        // 獲取系統預設的UncaughtException處理
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        // 設定該CrashHandler為程式的預設處理
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 異常捕獲
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            // 如果使用者沒有處理則讓系統預設的異常處理器來處
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            // 跳轉到崩潰提示Activity
            Intent intent = new Intent(mContext, CrashDialog.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mContext.startActivity(intent);
            System.exit(0);// 關閉已奔潰的app程序
        }
    }

    /**
     * 自定義錯誤捕獲
     * 
     * @param ex
     * @return true:如果處理了該異常資訊;否則返回false.
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }

        // 收集錯誤資訊
        getCrashInfo(ex);

        return true;
    }

    /**
     * 收集錯誤資訊
     * 
     * @param ex
     */
    private void getCrashInfo(Throwable ex) {
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();
        String errorMessage = writer.toString();
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            String mFilePath = Environment.getExternalStorageDirectory() + "/" + App.ERROR_FILENAME;
            FileTxt.WirteTxt(mFilePath, FileTxt.ReadTxt(mFilePath) + '\n' + errorMessage);
        } else {
            Log.i(App.TAG, "哦豁,說好的SD呢...");
        }

    }

}

CrashDialog.java

package cn.qson.androidcrash;

/**
 *  @author x024
 */

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class CrashDialog extends Activity {

    private String mFilePath;
    private Button btnExit, btnRestart;
    private Boolean StorageState = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_crash);
        CrashDialog.this.setFinishOnTouchOutside(false);
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            mFilePath = Environment.getExternalStorageDirectory() + "/" + App.ERROR_FILENAME;
            StorageState = true;
        } else {
            Log.i(App.TAG, "哦豁,說好的SD呢...");
        }

        new Thread(upLog).start();
        initView();
    }

    private void initView() {
        btnExit = (Button) findViewById(R.id.cash_exit);
        btnRestart = (Button) findViewById(R.id.cash_restart);

        btnExit.setOnClickListener(mOnClick);
        btnRestart.setOnClickListener(mOnClick);

    }

    OnClickListener mOnClick = new OnClickListener() {

        @Override
        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.cash_exit:
                exit();
                break;
            case R.id.cash_restart:
                restart();
                break;
            default:
                break;
            }
        }
    };

    // 上傳錯誤資訊
    Runnable upLog = new Runnable() {
        @Override
        public void run() {
            try {

                String Mobile = Build.MODEL;
                String maxMemory = "" + getmem_TOLAL() / 1024 + "m";
                String nowMemory = "" + getmem_UNUSED(CrashDialog.this) / 1024 + "m";
                String eMessage = "未獲取到錯誤資訊";
                if (StorageState) {
                    eMessage = FileTxt.ReadTxt(mFilePath).replace("'", "");
                }
                Log.i(App.TAG, "Mobile:" + Mobile + " | maxMemory:" + maxMemory + " |nowMemory:" + nowMemory
                        + " |eMessage:" + eMessage);

                /**
                 * 可以在這調你自己的介面上傳資訊
                 */
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    private void exit() {
        FileTxt.deleteFile(mFilePath);
        System.exit(0);
        android.os.Process.killProcess(android.os.Process.myPid());
    }

    private void restart() {
        Intent intent = getBaseContext().getPackageManager()
                .getLaunchIntentForPackage(getBaseContext().getPackageName());
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
        exit();
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        exit();
    }

    // 獲取可用記憶體
    public static long getmem_UNUSED(Context mContext) {
        long MEM_UNUSED;
        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        am.getMemoryInfo(mi);

        MEM_UNUSED = mi.availMem / 1024;
        return MEM_UNUSED;
    }

    // 獲取剩餘記憶體
    public static long getmem_TOLAL() {
        long mTotal;
        String path = "/proc/meminfo";
        String content = null;
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(path), 8);
            String line;
            if ((line = br.readLine()) != null) {
                content = line;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        int begin = content.indexOf(':');
        int end = content.indexOf('k');

        content = content.substring(begin + 1, end).trim();
        mTotal = Integer.parseInt(content);
        return mTotal;
    }

}

相關推薦

Android全域性異常捕獲提示

  Android 難免有崩潰的時候,但是崩潰了該如何處理呢?雖然那天有位同仁說 “既然崩潰了,使用者體驗就差了,心裡會想這是毛APP,下次也不想用了” ,所以檢查BUG以防崩潰是必須的,但是也需要一個後備方案,崩潰了能友好些,我們也能收集一些崩潰的資訊。

android全域性異常捕獲器UncaughtExceptionHandler的基本使用

——再過十天就可以去找工作了,最近為了簡歷好看點,複習下我的Cocos和QT啊.不知道以後用得到用不到啊,每個禮拜堅持寫一篇吧,昨天晚上把淘寶物流資訊的控制元件做出來了一半,實在沒什麼時間,下個禮拜看看能不能發. 我們講這個全域性異常捕獲器Uncaug

Android 全域性異常捕獲的完整實踐

前言 在 Android 開發中在所難免的會出現程式 Crash,俗稱崩潰。使用者的隨意性訪問出現測試時未知的 Bug 導致我們的程式 Crash,此時我們是無法直接獲取的錯誤 Log 的。Crash 極大的影響使用者體驗,甚至很可能因此被解除安裝。為了獲取錯

Android全域性異常捕獲,解決日誌列印三次的BUG

最近寫的專案需要自己寫全域性的異常捕捉,所以百度了很多解決方案,發現出現各種問題,好不容易找到一些比較靠譜的方案,但是卻發現出現了一個讓我無語的問題——日誌列印輸出三次。於是又開始去尋找答案,發現並沒有相關的解決。好嘛,看來還是要自己搞了。 發生該現象的基本原

Android 全域性異常捕獲

全域性異常捕獲 廢話不多說,直接上程式碼 package com.util; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import

Android全域性異常捕獲機制

文章背景 程式猿或是程式媛們在開發Android專案的時候,經常出現各種奇葩的Crash,有可能是服務端返回資料的原因所造成的、也可能是客戶端自己的原因。我個人認為出現Bug並不是那麼的重要,快速定位問題才是解決問題的開始、如果我們有一個能夠幫

Android 全域性異常捕獲之CrashHandler

一個App上線或者投入到生產環境的時候崩潰了,還不知道是什麼原因,這肯定是開發者的痛...所以肯定要加入全域性異常捕獲,如果專案較大的話,可以考慮加入第三方諸如友盟的崩潰統計外掛,以達到異常捕獲的效果! Crash,可以理解為崩潰、垮臺,通常來講就是App執行期間發生了不可

Android開發之全域性異常捕獲完美閃退APP專題

其實寫這邊文章之前,一直在考慮要不要標註為原創,因為全域性異常捕獲的機制,自己也是看了別人的文章學來的,百度全域性異常捕獲,出來的也都是一模一樣的內容,只是部落格位置不一樣而已。但是最後要是決定標準為原創,因為網上的那些全域性異常捕獲的文章,雖然交代瞭如何去處理全域性異常捕獲,但是卻沒有完美的處理捕

點選提示,3秒後關閉視窗跳轉新的頁面

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="

Android開發之全域性異常捕獲

前言 大家都知道,現在安裝Android系統的手機版本和裝置千差萬別,在模擬器上執行良好的程式安裝到某款手機上說不定就出現崩潰的現象,開發者個人不可能購買所有裝置逐個除錯,所以在程式釋出出去之後,如果出現了崩潰現象,開發者應該及時獲取在該裝置上導致崩潰的資訊,

Android全域性異常捕獲以及動態logcat列印。方便上線專案分析

很多時候我們會出現出現了一個問題,但是我們自己並沒有日誌的情況。這個時候怎麼辦呢。其實在我們的軟體中整合一些日誌上報的功能有時候是有需要的。那麼問題來了:我們該在自己程式碼中動態捕獲自己應用的日誌,以及錯誤資訊呢。其實android 給出了兩種: 1.執行時異

Android開發之全域性異常捕獲完美閃退

一、Application的生命週期 在說如何完美退出APP之前,我們先來講講Application的生命週期. 1、onCreate,app啟動的主入口,程式啟動的時候呼叫 @Override publicvoid onC

詳解Android全域性異常捕獲處理

這篇文章主要為大家介紹了Android全域性異常的捕獲處理,為什麼要進行捕獲處理,如何進行捕獲處理,想要了解的朋友可以參考一下 在Android開發中在所難免的會出現程式crash,俗稱崩潰。使用者的隨意性訪問出現測試時未知的Bug導致我們的程式crash,此時我們是無法直

Android鎖屏狀態下點亮螢幕提醒

類似於手機鎖屏狀態下QQ來訊息然後點亮螢幕並彈窗,如圖。 相信QQ的這個功能大家都是很熟悉的了,下面就開始講具體的實現步驟。 一、新建一個Activity並在OnCreate中新增四個標識 @Override protected

提示插件(全局提示

osi .html global 遮罩 filter ase 方便 timeout obi 彈窗可以說是每個項目都會用到的一個東西,彈窗有很多種,有系統默認的,網上也有一堆插件。默認的彈窗一般不好看,都會被設計嫌棄的,如果用插件的話,又比較占資源空間,所有我開發的項

form表單右邊提示不能為空

style class RM form表單 == code div span tips 1 if (key == null || key == "") { 2 layer.tips(‘流程標識key不能為空‘, $(‘#search_input‘)

layui.js 提示 (示例)

HTML 檔案必須引入這兩個 js 檔案 <script type="text/javascript" src="./js/jquery-1.11.3.min.js"></script> <script type="text/javascript" src

全域性異常捕獲

/** 全域性異常捕獲類 */ public class UnCatchExceptionHandler implements Thread.UncaughtExceptionHandler { private Context context; private Thread.Un

SpringBoot全域性異常捕獲及處理(包括自定義異常捕獲處理)

在做專案的時候需要對自定義異常做捕獲和處理,現在將程式碼記錄下來便於以後查閱。 1、全域性異常捕捉處理 @ControllerAdvice( annotations = {RestController.class} ) public class ExceptionHandlerAdv

python 提示警告框MessageBox

需要安裝pywin32模組,pip install pywin32  ##pip install pywin32 import win32api,win32con ##提醒OK訊息框 win32api.MessageBox(0, "這是一個測試提醒OK訊息框", "提醒",wi