Android 崩潰日誌採集組件-DhccCrashLib


異常崩潰怎麼辦?

關於異常崩潰是每一個App都要面對的,平時開發還好,在調試狀態下遇到的問題,能夠經過LogCat打印的異常日誌信息進行分析處理,可是一旦App上線後,大量用戶安裝了你的應用,每一個用戶的手機大小、傳感器、SDK版本都不盡相同,可能你在測試機上跑的穩穩的應用,到了客戶手機上就會出現一些莫名其妙的異常,若是隻是一些內存泄露的問題可能還好,最起碼不會瞬間崩潰,可是若是遇到一些能夠致使手機崩潰Bug的話,你讓出問題的用戶來複現Bug是不可能的,因此,全局異常捕獲就顯得很重要了,而DhccCrashLib就是一個全局異常捕獲的組件。javascript

DhccCrashLib怎麼用?

Github地址java

使用方法仍是比較簡單的,首先在項目的根目錄下的build.gradle中加入Jcenter倉庫:node

repositories {
        jcenter()
    }
複製代碼

而後在你的項目的build.gradle中添加依賴:android

implementation 'com.dhcc.crashlib:CrashLib:1.0.3'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.github.zhaokaiqiang.klog:library:1.6.0'
implementation "com.sun.mail:android-mail:1.6.0"
複製代碼

這四個依賴都須要加,由於擔憂版本衝突,因此我在組件中使用的依賴方式是compileOnly,那麼你在你的項目中若是有引用除了CrashLib外的這三個依賴的話,就能夠換成你本身的版本號便可。webpack

使用方式 在你項目的自定義Application中:git

public class MyApplication extends Application {

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

    /** * 初始化崩潰採集服務 */
    private void initCrash() {
        EmailConfigBean emailConfigBean = new EmailConfigBean("你的發送郵箱", "你的接收郵箱", "你的發送郵箱密碼");
        Configuration configuration=Configuration.getInstance()
                //你的郵件配置實例
                .setEmailConfig(emailConfigBean)
                //是否經過郵件發送異常
                .setSendWithEmail(true)
                //是否經過郵件發送異常並將本地存儲的異常已附件的形式發送
                .setSendEmailWithFile(true)
                //異常服務器的API
                .setCrashServerUrl("http://111.222.333.444:9999/api/crashs")
                //是否給服務器發送異常信息
                .setSendWithNet(true)
                //異常的描述信息
                .setCrashDescription("測試異常~~")
                //捕獲異常後退出App的等待時間 毫秒
                .setExitWaitTime(5000)
                ;
        LogCenter.getLogCenter("進程名", configuration)
                //能夠自定義異常 只要實現ICollector 並傳入網絡提交時所須要的key便可
                .strategy(new TestCollectInfo(), "網絡屬性的Key")
                .init(this);
    }
}
複製代碼

就這麼簡單,首先先把你的發送郵箱和接收郵箱的相關信息都配置到EmailConfigBean中去,而後再調用LogCenter初始化相關參數便可,不過這裏有一個細節須要講一下,注意看TestCollectInfo()這個方法:github

public class TestCollectInfo implements ICollector {
    @Override
    public String collectInfo(Context context) {
        return "這是一條測試採集異常信息";
    }
}
複製代碼

因爲每一個項目不一樣,可能須要採集的異常信息外的其餘一些手機信息都不盡相同,我這裏在源碼中只設計了Key爲deviceInfo和Key爲exceptionInfo的兩種捕獲信息,deviceInfo主要是爲了捕獲手機信息的而exceptionInfo就是捕獲異常崩潰信息的了,若是你的項目中還須要捕獲其餘類型的信息,能夠經過實現ICollector接口來定義本身想提交的採集信息便可,記得在初始化時調用.strategy(new TestCollectInfo(), "網絡屬性的Key")將採集信息傳入便可。web

面臨的問題

在網上能夠看到不少相似於全局捕獲異常發送服務器或者發送郵件給指定郵箱的功能,可是這些文章都沒有實際的深刻場景,只是寫出了邏輯代碼,這樣就會面臨到一個很實際的問題:express

異常發生時,咱們要作的是將異常信息和一些其餘捕獲到的手機信息或上傳服務器或經過郵件發送給指定郵箱,可是若是這個時間過長,致使App已經退出,進程退出後,此進程的線程也不復存在,那麼若是你要作的邏輯操做還沒作完,那麼你此次異常的捕獲就是失敗的。json

基於這個緣由,我在異常發生時作的操做是這樣的:

  1. 捕獲異常並寫入本地異常捕獲文件;
  2. 給寫入文件的操做加入回調接口,告知主線程異常寫入完畢;
  3. 將異常信息、異常文件路徑、手機設備信息等參數傳入子進程的IntentService;

因爲是子進程啓動的Service進行的業務邏輯操做,就算主進程已經退出,也不會影響子進程的耗時操做,問題也就隨之解決了。

配套的Express文件

你可能會納悶了,什麼是Express?這文件是幹嗎的?

看過前面的部分後,你可能知道了這個組件是能夠將異常信息發送給服務器的,而看這篇文章的不少可能都是移動端的開發人員,不必定懂服務端,就算懂,也未必能很快的搭建一個能夠接受異常信息的服務端來測試,那麼爲了你們測試方便,我就把個人Express文件分享出來,若是你還不知道什麼是Express或者Node.js,建議你先看這篇:

Express快速搭建移動端測試用Api服務端

以後將你Nodejs根目錄下的app.js改成:

var fs = require('fs');
var path = require('path');
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var CRASH_FILE = path.join(__dirname, 'api/crashs.json'); // user.json文件的路徑

app.set('port', (process.env.PORT || 9999));
app.use('/', express.static(path.join(__dirname, 'public')));
//使用body-parser中間件
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(function(req, res, next) {
    // Set permissive CORS header - this allows this server to be used only as
    // an API server in conjunction with something like webpack-dev-server.
    res.setHeader('Access-Control-Allow-Origin', '*');
    // Disable caching so we'll always get the latest comments.
    res.setHeader('Cache-Control', 'no-cache');
    next();
});


//處理/api/crashs的POST請求
app.post('/api/crashs', function(req, res) {
  fs.readFile(CRASH_FILE, function(err, data) {
    if (err) {
      console.error(err);
      process.exit(1);
    }
    var crashs = JSON.parse(data);
    //控制post提交的參數類型
    var crash = {
      deviceinfo: req.body.deviceInfo,
      exceptioninfo: req.body.exceptionInfo,
		  testinfo:req.body.testInfo
    };
    //將user加入到users中去。
    crashs.push(crash);
    fs.writeFile(CRASH_FILE, JSON.stringify(crashs, null, 4), function(err) {
      if (err) {
        console.error(err);
        process.exit(1);
      }
      //請求成功後返回的提示json
      res.json("{code: 200, message: 'upload crash successful.'}");
    });
  });
});


app.listen(app.get('port'), function() {
  console.log('Server started: http://localhost:' + app.get('port') + '/');
});
複製代碼

而且在同目錄的文件夾:api中放入crashs.json:

[
    {
        "deviceinfo": "手機信息異常===========================================<br>DISPLAY=Flyme 6.8.3.31R beta<br>REGION=CN<br>SERIAL=d4aa09c3<br>BOOTLOADER=unknown<br>SOFT_VERSION=Y.30<br>SUPPORTED_64_BIT_ABIS=[Ljava.lang.String;@e6de412<br>PERMISSIONS_REVIEW_REQUIRED=false<br>AUTO_TEST_ONEPLUS=false<br>ID=NMF26F<br>TAG=Build<br>HOST=xs-MacBookPro<br>TAGS=test-keys<br>TIME=1522481855000<br>TYPE=user<br>USER=xs<br>BOARD=QC_Reference_Phone<br>BRAND=OnePlus<br>MODEL=ONEPLUS A3010<br>RADIO=unknown<br>SUPPORTED_ABIS=[Ljava.lang.String;@833c7e3<br>MANUFACTURER=OnePlus<br>PRODUCT=OnePlus3<br>UNKNOWN=unknown<br>versionCode=1<br>versionName=1.0<br>IS_EMULATOR=false<br>FINGERPRINT=OnePlus/OnePlus3/OnePlus3T:7.1.1/NMF26F/builder.20180331153735_R:user/test-keys<br>HARDWARE=qcom<br>SUPPORTED_32_BIT_ABIS=[Ljava.lang.String;@b31279d<br>IS_BETA_ROM=true<br>CPU_ABI2=<br>CPU_ABI=arm64-v8a<br>IS_DEBUGGABLE=false<br>DEBUG_ONEPLUS=false<br>DEVICE=OnePlus3T<br>===========================================<br>",
        "exceptioninfo": "Time:Fri May 10 14:23:32 GMT+08:00 2019  [Thread(id:3321, name:pool-2-thread-1, priority:5, groupName:main): LogCenter.java:184 run java.lang.RuntimeException: 測試CrashLib\n\tat com.dhcc.test.MainActivity$1.onClick(MainActivity.java:18)\n\tat android.view.View.performClick(View.java)\n\tat android.view.View$PerformClick.run(View.java:22549)\n\tat android.os.Handler.handleCallback(Handler.java:751)\n\tat android.os.Handler.dispatchMessage(Handler.java:95)\n\tat android.os.Looper.loop(Looper.java:154)\n\tat android.app.ActivityThread.main(ActivityThread.java)\n\tat java.lang.reflect.Method.invoke(Native Method)\n\tat com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)\n\tat com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)\n ] - 測試異常~~",
        "testinfo": "這是一條測試採集異常信息"
    }
]
複製代碼

而後啓動服務:

在Cmd下輸入:
> node app.js
複製代碼

以後在移動端,咱們就能夠設置成一下代碼來捕獲咱們的異常信息了:

...
    .setCrashServerUrl("http://你的ip:9999/api/crashs")
...

LogCenter.getLogCenter("com.dhcc.crashInfo", configuration)
                //能夠自定義異常 只要實現ICollector 並傳入網絡提交時所須要的key便可
                .strategy(new TestCollectInfo(), "testInfo")
                .init(this);
複製代碼

這裏注意.strategy(new TestCollectInfo(), "testInfo")的testInfo,其實就是app.js中的req.body.testInfo和crashs.json中的testInfo字段。

總體設計架構

源碼就不細說了,你們能夠本身去看,有什麼問題能夠給我留言,謝謝你看完。

感謝

這是一份詳細清晰的 上傳Android Library到JCenter 教程

編寫 Android Library 的最佳實踐

Android程序製做本身Log日誌收集系統

Android進階:1、日誌打印和保存策略

相關文章
相關標籤/搜索