android crash log收集總結(jié)

一般應(yīng)用在運(yùn)行時(shí)芯急,不可避免的會(huì)發(fā)生crash感帅。一般應(yīng)用crash分為三種:未捕獲的異常强饮、ANR(Application Not Responding)圆雁、閃退(NDK引發(fā)錯(cuò)誤)忍级。

  1. 未捕獲的異常:一般系統(tǒng)會(huì)將crash log輸入出控制臺(tái),開(kāi)發(fā)人員可以及時(shí)查看伪朽。對(duì)于發(fā)布的版本轴咱,則可以通過(guò)一些方式收集crash log, 這樣方便開(kāi)發(fā)人員分析程序崩潰原因,更好的改進(jìn)代碼烈涮。同時(shí)當(dāng)程序崩潰時(shí)朴肺,表現(xiàn)出閃退或已停止,可能會(huì)給用戶帶來(lái)很不好的體驗(yàn)坚洽,這需要我們重新定義程序崩潰時(shí)發(fā)出一些友情提示戈稿。
  2. ANR:系統(tǒng)一般會(huì)彈出提示框,讓用戶選擇waiting還是close應(yīng)用讶舰,在/data/anr目錄下還生成一個(gè)traces.txt文件鞍盗,方便程序員查看系統(tǒng)產(chǎn)生anr異常的堆棧和線程信息需了。
  3. 應(yīng)用閃退,很可能是由于應(yīng)用中的NDK引發(fā)錯(cuò)誤般甲,logcat打印出來(lái)的那堆日志根據(jù)看不懂肋乍,很難從日志當(dāng)中定位錯(cuò)誤的根源。如果是上線產(chǎn)品敷存,一般很難收集到這些crash log,但也不是沒(méi)有辦法住拭。

下面就我們常遇到的crash log收集進(jìn)行總結(jié):

一、java層崩潰:

對(duì)于java層的crash,android中有處理這類問(wèn)題的方法历帚,在Thread類中的一個(gè)方法#setDefaultUncaughtExceptionHandler

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

通過(guò)該方法我們可以設(shè)置系統(tǒng)的默認(rèn)異常處理器滔岳。其實(shí)UncaughtExceptionHandler是一個(gè)接口,

 public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * <p>Any exception thrown by this method will be ignored by the
         * Java Virtual Machine.
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }

我們可以實(shí)現(xiàn)uncaughtException方法挽牢,自定義crash收集方法谱煤,然后將crash信息保存到本地的文件中就好了。
具體實(shí)現(xiàn)步驟:

1? 建立異常處理的CrashHandler禽拔,實(shí)現(xiàn)系統(tǒng)的UncaughtExceptionHandler

當(dāng)crash發(fā)生的時(shí)候刘离,我們可以捕獲到異常信息,把異常信息存儲(chǔ)到SD卡中睹栖,然后在合適的時(shí)機(jī)通過(guò)網(wǎng)絡(luò)將crash信息上傳到服務(wù)器上硫惕,這樣開(kāi)發(fā)人員就可以分析用戶crash的場(chǎng)景從而在后面的版本中修復(fù)此類crash。具體實(shí)現(xiàn)如下:

public class MyCrashHandler implements UncaughtExceptionHandler {

    private static MyCrashHandler crashHandler;
    private static SimpleDateFormat formatter = new          SimpleDateFormat("yyyyMMddHHmmss");
    private static Date curDate = new Date(System.currentTimeMillis());//獲取當(dāng)前時(shí)間
    private static String str = formatter.format(curDate);
    private static String TAG = "MyCrashHandler";
    //系統(tǒng)默認(rèn)的UncaughtException處理類
    private UncaughtExceptionHandler mDefaultHandler;
    //程序的Context對(duì)象
    private Context mContext;
    //用來(lái)存儲(chǔ)設(shè)備信息和異常信息
    private Map<String, String> infos = new HashMap<String, String>();


    @Override
    public void uncaughtException(Thread thread, Throwable ex) {

        if (!handleException(ex) && mDefaultHandler != null) {
            //如果用戶沒(méi)有處理則讓系統(tǒng)默認(rèn)的異常處理器來(lái)處理
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                Log.e(TAG, "error : ", e);
            }
            //退出程序
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(1);
        }
    }

    /**
     * 自定義錯(cuò)誤處理,收集錯(cuò)誤信息 發(fā)送錯(cuò)誤報(bào)告等操作均在此完成.
     *
     * @param ex
     * @return true:如果處理了該異常信息;否則返回false.
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        //使用Toast來(lái)顯示異常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();//準(zhǔn)備發(fā)消息的MessageQueue
                Toast.makeText(mContext, "很抱歉,程序出現(xiàn)異常,即將退出.", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();
        //收集設(shè)備參數(shù)信息
        collectDeviceInfo(mContext);
        //保存日志文件
        saveCrashInfo2File(ex);
        return true;
    }


    /**
     * 保存錯(cuò)誤信息到文件中
     *
     * @param ex
     * @return 返回文件名稱, 便于將文件傳送到服務(wù)器
     */
    private String saveCrashInfo2File(Throwable ex) {

        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }

        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 result = writer.toString();
        sb.append(result);
        try {
            long timestamp = System.currentTimeMillis();

            String fileName = "crash-" + str + "-" + timestamp + ".log";
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String path = Environment.getExternalStorageDirectory().getPath()+"/crash/";
                File dir = new File(path);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                FileOutputStream fos = new FileOutputStream(path + fileName);
                fos.write(sb.toString().getBytes());
                System.out.println(sb.toString());
                fos.close();
            }
            return fileName;
        } catch (Exception e) {
            Log.e(TAG, "an error occured while writing file...", e);
        }
        return null;
    }

    /**
     * 初始化
     *
     * @param context
     */
    public void init(Context context) {
        mContext = context;
        //獲取系統(tǒng)默認(rèn)的UncaughtException處理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //設(shè)置該CrashHandler為程序的默認(rèn)處理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 收集設(shè)備參數(shù)信息
     *
     * @param ctx
     */
    public void collectDeviceInfo(Context ctx) {
        try {
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (pi != null) {
                String versionName = pi.versionName == null ? "null" : pi.versionName;
                String versionCode = pi.versionCode + "";
                infos.put("versionName", versionName);
                infos.put("versionCode", versionCode);
            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "an error occured when collect package info", e);
        }
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
                Log.d(TAG, field.getName() + " : " + field.get(null));
            } catch (Exception e) {
                Log.e(TAG, "an error occured when collect crash info", e);
            }
        }
    }

    private MyCrashHandler() {
    }

    //單例
    public static MyCrashHandler instance() {
        if (crashHandler == null) {
            crashHandler = new MyCrashHandler();
        }
        return crashHandler;
    }
}

2? 為ui線程添加默認(rèn)異常事件Handler

在ui線程中添加異常處理Handler野来,在應(yīng)用的Application中添加而不是在Activity中添加恼除。應(yīng)用啟動(dòng)時(shí)是先走Application,故只需要在Application的onCreate方法里進(jìn)行CrashHandler的初始化即可曼氛。

   public class CrashApplication extends MultiDexApplication  {
        private static final String TAG ="CrashApplication" ;
        private static Context mContext;
        @Override
        public void onCreate() {
            super.onCreate();
            MyCrashHandler crashHandler = MyCrashHandler.instance();
            crashHandler.init(getApplicationContext());
        }
    }

3?方法2

當(dāng)然也可以集成第三方SDK,如友盟統(tǒng)計(jì)分析(http://dev.umeng.com/analytics/android-doc/integration)豁辉,不僅能自動(dòng)幫助應(yīng)用收集crash log,還可以統(tǒng)計(jì)應(yīng)用用戶使用情況,總用戶數(shù)舀患、新增用戶數(shù)徽级、日活、月活聊浅、用戶手手機(jī)版本餐抢、每個(gè)應(yīng)用版本信息等情況。

具體集成方式也很簡(jiǎn)單低匙,只需要在友盟平臺(tái)上注冊(cè)開(kāi)發(fā)者賬號(hào)旷痕,申請(qǐng)應(yīng)用,并在AndroidMainfest.xml里配置權(quán)限

<meta-data android:value="YOUR_APP_KEY" android:name="UMENG_APPKEY"/>
<meta-data android:value="Channel ID" android:name="UMENG_CHANNEL"/>

在每個(gè)Activity的onResume方法中調(diào)用 MobclickAgent.onResume(Context) ,
onPause方法中調(diào)用 MobclickAgent.onPause(Context)

   public void onResume() {
        super.onResume();
        MobclickAgent.onResume(this);
    }
    public void onPause() {
        super.onPause();
        MobclickAgent.onPause(this);
    }

當(dāng)然這可以寫(xiě)在基類里努咐,省去每個(gè)類都去寫(xiě)苦蒿。

二殴胧、NDK的崩潰:

由于jni代碼不支持backtrace渗稍,故不可通過(guò)實(shí)現(xiàn)UncaughtExceptionHandler來(lái)捕獲異常佩迟。樓主沒(méi)有具體深入研究過(guò),但大致可歸綱以下方法:

1? 通過(guò)api sigaction來(lái)捕捉對(duì)應(yīng)的異常信號(hào)

#include <signal.h> 
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

參考資料:
1)【Android平臺(tái)的崩潰捕獲機(jī)制及實(shí)現(xiàn)】http://www.uml.org.cn/mobiledev/2016011510.asp

  1. stackOverFlow上提出的:http://stackoverflow.com/questions/1083154/how-can-i-catch-sigsegv-segmentation-fault-and-get-a-stack-trace-under-jni-on

2? 借助第三方SDK:如騰訊 Bugly

騰訊 Bugly不權(quán)可以抓取到JAVA的崩潰竿屹,同樣也可以抓取到NDK代碼的崩潰报强。

3? 借助google的google-breakpad

google-breakpad(https://github.com/svn2github/google-breakpad)可以捕獲ndk的異常信息,它會(huì)將堆棧信息寫(xiě)入用戶指定的本地MiniDump文件中拱燃。下次啟動(dòng)應(yīng)用程序的時(shí)候秉溉,便可以讀取該MiniDump文件進(jìn)行相應(yīng)的操作,比如上傳到后臺(tái)服務(wù)器碗誉。
參考資料:1) [Android崩潰日志獲取與解析](http://openflight.blog.51cto.com/11047400/1728909

  1. [Android端線上NativeCrash收集的兩種方法(上)](http://www.reibang.com/p/c65186af3089

三召嘶、總結(jié)及注意事項(xiàng)

1? 如果代碼被proguad混淆后,輸出的crash log哮缺,出錯(cuò)的行號(hào)看不出來(lái)(Unknown Source)

需要在代碼混淆的時(shí)候弄跌,加上在混淆文件里面記得加上這句:
-keepattributes SourceFile,LineNumberTable # keep住源文件以及行號(hào)

2?java層crash log收集

  1. 自己收集,可以實(shí)現(xiàn)系統(tǒng)的UncaughtExceptionHandler類尝苇,并重寫(xiě)uncaughtException方法铛只,將crash log寫(xiě)到手機(jī)本地,在適時(shí)的時(shí)候糠溜,上傳到我們的服務(wù)器淳玩,優(yōu)點(diǎn)是,所需類和方法少非竿,可以自定義log輸出蜕着,缺點(diǎn)是沒(méi)有第三方平臺(tái)強(qiáng)大,只能收集crash log,統(tǒng)計(jì)信息等需要自己去做红柱;
  2. 借助第三方平臺(tái)侮东,如友盟、騰訊 Bugly等豹芯,這些平臺(tái)不僅能收集crash log,還添加了豐富的統(tǒng)計(jì)信息悄雅,可以很方便看出各版本、各平臺(tái)等出現(xiàn)的crash,缺點(diǎn)是铁蹈,導(dǎo)入第三庫(kù)宽闲,代碼量肯定比自己重寫(xiě)的類大,無(wú)法自定義應(yīng)用崩潰時(shí)彈框握牧;

3?ndk層crash log收集

1)在自己寫(xiě)的jni方法里容诬,用sigaction來(lái)鋪獲,

  1. 采用google的google-breakpad的NativeCrash日志收集方法沿腰,優(yōu)點(diǎn)是:可以很方便google-breakpad是夸平臺(tái)開(kāi)源工具览徒,體量較大,在其基礎(chǔ)上生成的通用SO和dmp日志也都較大颂龙,對(duì)于sdk大小有嚴(yán)格要求的APP习蓬,可能不是很方便纽什。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市躲叼,隨后出現(xiàn)的幾起案子芦缰,更是在濱河造成了極大的恐慌,老刑警劉巖枫慷,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件让蕾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡或听,警方通過(guò)查閱死者的電腦和手機(jī)探孝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)誉裆,“玉大人再姑,你說(shuō)我怎么就攤上這事≌矣” “怎么了元镀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)霎桅。 經(jīng)常有香客問(wèn)我栖疑,道長(zhǎng),這世上最難降的妖魔是什么滔驶? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任遇革,我火速辦了婚禮,結(jié)果婚禮上揭糕,老公的妹妹穿的比我還像新娘萝快。我一直安慰自己,他們只是感情好著角,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布揪漩。 她就那樣靜靜地躺著,像睡著了一般吏口。 火紅的嫁衣襯著肌膚如雪奄容。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天产徊,我揣著相機(jī)與錄音昂勒,去河邊找鬼。 笑死舟铜,一個(gè)胖子當(dāng)著我的面吹牛戈盈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谆刨,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼塘娶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼归斤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起血柳,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤官册,失蹤者是張志新(化名)和其女友劉穎生兆,沒(méi)想到半個(gè)月后难捌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸦难,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年根吁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片合蔽。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡击敌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拴事,到底是詐尸還是另有隱情沃斤,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布刃宵,位于F島的核電站衡瓶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏牲证。R本人自食惡果不足惜哮针,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坦袍。 院中可真熱鬧十厢,春花似錦、人聲如沸捂齐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)奠宜。三九已至筛武,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挎塌,已是汗流浹背徘六。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留榴都,地道東北人待锈。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親钦扭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祟牲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,735評(píng)論 25 707
  • 一.概述 ??本文主要介紹Android平臺(tái)下bug類型和產(chǎn)生原因饮戳、崩潰捕獲和收集解決方案她倘、以及bugly的使用方...
    Haraway閱讀 9,227評(píng)論 3 14
  • 轉(zhuǎn)載(漫談 iOS Crash 收集框架) 前言 很早以前就和念茜認(rèn)識(shí)怔鳖,念茜不但技術(shù)功底扎實(shí)手报,而且長(zhǎng)得很漂亮彤侍,說(shuō)她...
    狂風(fēng)無(wú)跡閱讀 3,286評(píng)論 1 11
  • 其實(shí)時(shí)間管理是一件費(fèi)精力的事情,好比手機(jī)內(nèi)存中的系統(tǒng)內(nèi)存占用萄涯,進(jìn)行時(shí)間管理也會(huì)消耗時(shí)間绪氛,如果每天花在時(shí)間管理上的時(shí)...
    朵三三閱讀 526評(píng)論 0 1
  • 也許我們看過(guò)太多身邊追求“安穩(wěn)”的人生,以至于認(rèn)為這就該是自己的一生涝影; 也許我們聽(tīng)過(guò)太多道理枣察,卻不能按照自己的意愿...
    土豆卷閱讀 518評(píng)論 0 2