Android異常監(jiān)控

Android異常監(jiān)控

Crash就是由于代碼異常而導致App非正常退出現(xiàn)象霞丧,也就是我們常說的『崩潰

通常情況下會有以下兩種類型Crash:

  • Java Crash
  • Native Crash

Java Crash

Java的Crash監(jiān)控非常簡單拆又,Java中的Thread定義了一個接口: UncaughtExceptionHandler 搪锣;用于

處理未捕獲的異常導致線程的終止(注意:****catch****了的是捕獲不到的)杨耙,當我們的應用crash的時候臣缀,就

會走 UncaughtExceptionHandler.uncaughtException ,在該方法中可以獲取到異常的信息匈棘,我們通

過 Thread.setDefaultUncaughtExceptionHandler 該方法來設置線程的默認異常處理器飘蚯,我們可以

將異常信息保存到本地或者是上傳到服務器熙参,方便我們快速的定位問題艳吠。

Android系統(tǒng)崩潰處理的流程:

圖片來源:https://xiang-yu.blog.csdn.net/article/details/106647428

20200609193238913

具體的處理過程可參考:https://xiang-yu.blog.csdn.net/article/details/106647428

ZygoteInit每次fork出一個進程之后,會先初始化日志孽椰、crash監(jiān)控讲竿,再利用反射調(diào)用 ActivityThread 的main()方法啟動app。那么我們可以自己的app中重新注冊Crash監(jiān)控回調(diào)接口弄屡,然后在自己的app中處理Crash题禀。

具體的實現(xiàn)類如下:

public class CrashHandler implements Thread.UncaughtExceptionHandler{

    private static final String FILE_NAME_SUFFIX = ".trace";
    private static Thread.UncaughtExceptionHandler mDefaultCrashHandler;
    private static Context mContext;
    private static CrashHandler crashHandler;
    private CrashHandler() {
    }
    public static CrashHandler getCrashHander(){
        if (crashHandler == null){
            synchronized (CrashHandler.class){
                if (crashHandler == null){
                    crashHandler = new CrashHandler();
                }
            }
        }
        return crashHandler;
    }

    public void init(@NonNull Context context) {
        //默認為:RuntimeInit#KillApplicationHandler
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
        mContext = context.getApplicationContext();
    }

    /**
     * 當程序中有未被捕獲的異常,系統(tǒng)將會調(diào)用這個方法
     *
     * @param t 出現(xiàn)未捕獲異常的線程
     * @param e 得到異常信息
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        try {
            //自行處理:保存本地
            File file = dealException(t, e);
            //上傳服務器
            //......
        } catch (Exception e1) {
            e1.printStackTrace();
        } finally {
            //交給系統(tǒng)默認程序處理
            if (mDefaultCrashHandler != null) {
                mDefaultCrashHandler.uncaughtException(t, e);
            }
        }
    }

    /**
     * 導出異常信息到SD卡
     *
     * @param e
     */
    private File dealException(Thread t, Throwable e) throws Exception{
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        File f = new File(mContext.getExternalCacheDir().getAbsoluteFile(), "crash_info");
        if (!f.exists()) {
            f.mkdirs();
        }
        File crashFile = new File(f, time + FILE_NAME_SUFFIX);
        //往文件中寫入數(shù)據(jù)
        PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(crashFile)));
        pw.println(time);
        pw.println("Thread: " + t.getName());
        pw.println(getPhoneInfo());
        e.printStackTrace(pw); //寫入crash堆棧
        pw.close();
        return crashFile;
    }


    private String getPhoneInfo() throws PackageManager.NameNotFoundException {
        PackageManager pm = mContext.getPackageManager();
        PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
        StringBuilder sb = new StringBuilder();
        //App版本
        sb.append("App Version: ");
        sb.append(pi.versionName);
        sb.append("_");
        sb.append(pi.versionCode + "\n");

        //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架構(gòu)
        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();
    }

}

NDK異常

NDK異常指的是app在使用c和c++ so庫的時候膀捷,如果庫運行引起app崩潰迈嘹,我們同樣可以采用類似的方式來收集和處理異常,用找出代碼問題的具體位置。

Linux信號機制了解

信號機制是Linux進程間通信的一種重要方式秀仲,Linux信號一方面用于正常的進程間通信和同步融痛,另一方

面它還負責監(jiān)控系統(tǒng)異常及中斷。當應用程序運行異常時神僵,Linux內(nèi)核將產(chǎn)生錯誤信號并通知當前進

程雁刷。當前進程在接收到該錯誤信號后,可以有三種不同的處理方式保礼。

忽略該信號沛励;

捕捉該信號并執(zhí)行對應的信號處理函數(shù)(信號處理程序);

執(zhí)行該信號的缺省操作(如終止進程)炮障;

當Linux應用程序在執(zhí)行時發(fā)生嚴重錯誤目派,一般會導致程序崩潰。其中胁赢,Linux專門提供了一類crash信

號企蹭,在程序接收到此類信號時,缺省操作是將崩潰的現(xiàn)場信息記錄到核心文件智末,然后終止進程谅摄。

常見崩潰信號列表:

SIGSEGV

內(nèi)存引用無效。

SIGBUS

訪問內(nèi)存對象的未定義部分系馆。

SIGFPE

算術(shù)運算錯誤螟凭,除以零。

SIGILL

非法指令它呀,如執(zhí)行垃圾或特權(quán)指令

SIGSYS

糟糕的系統(tǒng)調(diào)用

SIGXCPU

超過CPU時間限制。

SIGXFSZ

文件大小限制棒厘。

一般的出現(xiàn)崩潰信號纵穿,Android系統(tǒng)默認缺省操作是直接退出我們的程序。但是系統(tǒng)允許我們給某一個

進程的某一個特定信號注冊一個相應的處理函數(shù)(signal)奢人,即對該信號的默認處理動作進行修改谓媒。因

此NDK Crash的監(jiān)控可以采用這種信號機制,捕獲崩潰信號執(zhí)行我們自己的信號處理函數(shù)從而捕獲NDK

Crash何乎。

BreakPad

Google breakpad是一個跨平臺的崩潰轉(zhuǎn)儲和分析框架和工具集合句惯,其開源地址是:https://github.co

m/google/breakpad。breakpad在Linux中的實現(xiàn)就是借助了Linux信號捕獲機制實現(xiàn)的支救。因為其實現(xiàn)

為C++抢野,因此在Android中使用,必須借助NDK工具各墨。

引入項目

將Breakpad源碼下載解壓指孤,首先查看README.ANDROID文件。

If you're using the ndk-build build system, you can follow
these simple steps:

  1/ Include android/google_breakpad/Android.mk from your own
     project's Android.mk

     This can be done either directly, or using ndk-build's
     import-module feature.

  2/ Link the library to one of your modules by using:

     LOCAL_STATIC_LIBRARIES += breakpad_client

NOTE: The client library requires a C++ STL implementation,
      which you can select with APP_STL in your Application.mk

      It has been tested succesfully with both STLport and GNU libstdc++
      
      使用步驟:

1.將

android/google_breakpad/Android.mk 導入到我們自己項目中;

2.在自己的Android.mk中依賴breakpad_client

第一步:我們先新建一個module命名為breakpad恃轩;

第二步:在該module下的main目錄新建cpp文件夾结洼;

第三步:將Breakpad下載解壓后的src拷貝至cpp包下面;

image-20201109014337179

第四步:在Breakpad下與src同級目錄下新建CMakeLists.txt叉跛,并編寫編譯內(nèi)容松忍,該內(nèi)容來源于android/google_breakpad/Android.mk文件,但是需要用cmake語法來完成編譯腳本筷厘,具體內(nèi)容如下:

cmake_minimum_required(VERSION 3.4.1)


include_directories(src src/common/android/include)

enable_language(ASM)

add_library(breakpad STATIC
        src/client/linux/crash_generation/crash_generation_client.cc
        src/client/linux/dump_writer_common/thread_info.cc
        src/client/linux/dump_writer_common/ucontext_reader.cc
        src/client/linux/handler/exception_handler.cc
        src/client/linux/handler/minidump_descriptor.cc
        src/client/linux/log/log.cc
        src/client/linux/microdump_writer/microdump_writer.cc
        src/client/linux/minidump_writer/linux_dumper.cc
        src/client/linux/minidump_writer/linux_ptrace_dumper.cc
        src/client/linux/minidump_writer/minidump_writer.cc
        src/client/minidump_file_writer.cc
        src/common/convert_UTF.cc
        src/common/md5.cc
        src/common/string_conversion.cc
        src/common/linux/breakpad_getcontext.S
        src/common/linux/elfutils.cc
        src/common/linux/file_id.cc
        src/common/linux/guid_creator.cc
        src/common/linux/linux_libc_support.cc
        src/common/linux/memory_mapped_file.cc
        src/common/linux/safe_readlink.cc)


target_link_libraries(breakpad log)

第五步:在cpp目錄下新建breakpad.cpp文件鸣峭,編寫breakpad crash監(jiān)控代碼,具體內(nèi)容如下敞掘;

//
// Created by Administrator on 2020/11/5.
//


#include <jni.h>
#include <android/log.h>
#include "breakpad/src/client/linux/handler/minidump_descriptor.h"
#include "breakpad/src/client/linux/handler/exception_handler.h"

bool DumpCallback(const google_breakpad::MinidumpDescriptor &descriptor,
                  void *context,
                  bool succeeded) {
    __android_log_print(ANDROID_LOG_ERROR, "native", "native crash:%s", descriptor.path());
    return false;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_breakpad_crash_BreakpadCrash_initNativeCrash(JNIEnv *env, jclass clazz, jstring path_) {

    const char *path = env->GetStringUTFChars(path_, 0);

    __android_log_print(ANDROID_LOG_INFO, "native", "===> %s", path);
    google_breakpad::MinidumpDescriptor descriptor(path);
    static google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback,
                                                NULL, true, -1);
    env->ReleaseStringUTFChars(path_, path);

}

第六步:在cpp目錄新建CMakeLists.txt來將breakpad庫和crash 監(jiān)控編譯為可移植的so叽掘;

cmake_minimum_required(VERSION 3.4.1)

include_directories(breakpad/src breakpad/src/common/android/include)
# libbugly.so
add_library(breakpadcrash-lib SHARED breakpad.cpp)

add_subdirectory(breakpad)

# 鏈接ndk中的log庫
target_link_libraries(breakpadcrash-lib  breakpad log)

第七步:新建java文件調(diào)用so 的native方法

public class BreakpadCrash {
    static {
        System.loadLibrary("breakpadcrash-lib");
    }

    public static void init(Context context) {
        Context applicationContext = context.getApplicationContext();
        File file = new File(applicationContext.getExternalCacheDir(), "native_crash");
        if (!file.exists()) {
            file.mkdirs();
        }
        initNativeCrash(file.getAbsolutePath());
    }

    public static native void initNativeCrash(String file);
}

第八步:在build.gradle文件中添加編譯配置

apply plugin: 'com.android.library'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"


    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles 'consumer-rules.pro'

        externalNativeBuild {
            cmake {
//                cppFlags "-std=c++11"
                cppFlags ""
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.2.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

第九步:之后build該模塊就可以在build\intermediates\cmake目錄中看到生成的so

使用:在Application的oncreate中調(diào)用

image-20201109015041781

另外我們需要新建一個moudle來模擬第三方的jni so庫,然后在activity中使用該測試庫實現(xiàn)崩潰玖雁;

image-20201109015240136

image-20201109015501092

應用崩潰之后可以在data.包名.cache.native下面找到日志文件更扁,改文件為dmp文件,需要使用minidump_stackwalk 工具來編寫為正常的日志文件赫冬,在 Android Studio 的安裝目錄下的 bin\lldb\bin 里面就存在一

個對應平臺的 minidump_stackwalk浓镜,進入該目錄或者配置該目錄到環(huán)境變量,然后執(zhí)行:

minidump_stackwalk xxxx.dump > crash.txt

打開txt文件可以看到:

image-20201109015903606

有對應的錯誤類型和堆棧信息劲厌。

接下來使用 Android NDK 里面提供的 addr2line 工具將寄存器地址轉(zhuǎn)換為對應符號膛薛。addr2line 要用和

自己 so 的 ABI 匹配的目錄,同時需要使用有符號信息的so(一般debug的就有)补鼻。

因為我使用的是模擬器x86架構(gòu)哄啄,因此addr2line位于:

E:\sdk\ndk\21.1.6352462\toolchains\x86_64-4.9\prebuilt\windows-x86_64\bin

image-20201109020201718
x86_64-linux-android-addr2line.exe -f -C -e E:\workspace\AndroidCrash\jnibug\build\intermediates\cmake\debug\obj\x86\libbugly-lib.so 0x5a4
image-20201109020241444

image-20201109020311629

項目源碼:https://github.com/heezier/AndroidCrash

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市风范,隨后出現(xiàn)的幾起案子咨跌,更是在濱河造成了極大的恐慌,老刑警劉巖硼婿,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锌半,死亡現(xiàn)場離奇詭異,居然都是意外死亡寇漫,警方通過查閱死者的電腦和手機刊殉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來州胳,“玉大人记焊,你說我怎么就攤上這事∷ㄗ玻” “怎么了亚亲?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我捌归,道長肛响,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任惜索,我火速辦了婚禮特笋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巾兆。我一直安慰自己猎物,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布角塑。 她就那樣靜靜地躺著蔫磨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪圃伶。 梳的紋絲不亂的頭發(fā)上堤如,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音窒朋,去河邊找鬼搀罢。 笑死,一個胖子當著我的面吹牛侥猩,可吹牛的內(nèi)容都是我干的榔至。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼欺劳,長吁一口氣:“原來是場噩夢啊……” “哼唧取!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起划提,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤枫弟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后腔剂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡驼仪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年掸犬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绪爸。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡湾碎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奠货,到底是詐尸還是另有隱情介褥,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站柔滔,受9級特大地震影響溢陪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜睛廊,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一形真、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧超全,春花似錦咆霜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疏遏,卻和暖如春脉课,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背改览。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工下翎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宝当。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓视事,卻偏偏與公主長得像,于是被迫代替她去往敵國和親庆揩。 傳聞我的和親對象是個殘疾皇子俐东,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

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

  • 原文[http://www.reibang.com/p/4278915847b6]源碼[https://down...
    WaterYuan閱讀 2,079評論 1 2
  • Crash(應用崩潰)是由于代碼異常而導致 App 非正常退出,導致應用程序無法繼續(xù)使用订晌,所有工作都 停止的現(xiàn)象虏辫。...
    zcwfeng閱讀 690評論 0 2
  • 背景: 支付SDK面向游戲提供支付服務,高效的游戲引擎一般都會C++編寫的锈拨,通過NDK編譯成so文件在Androi...
    jpchen_hn閱讀 4,947評論 0 6
  • 用兩張圖告訴你砌庄,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,714評論 2 59
  • 做為程序員奕枢,最不愿意看到的就是自己寫的程序崩潰娄昆,特別是遇到?jīng)]有錯誤信息的崩潰的時候,往往程序員自己也就隨之一起崩潰...
    點融黑幫閱讀 1,949評論 2 2