個(gè)人博客
Crash監(jiān)控的簡(jiǎn)單實(shí)現(xiàn)方案
前言
本文從Java層及Native展開,簡(jiǎn)單記錄Android中的Crash監(jiān)控方案。
Java層Crash
Java層的crash監(jiān)控,可以通過實(shí)現(xiàn)Thread.UncaughtExceptionHandler
接口,重寫uncaughtException
方法來實(shí)現(xiàn),簡(jiǎn)單示意代碼如下:
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
private static Context mContext;
private static String mPath;
public static void init(Context context, String path) {
mContext = context.getApplicationContext();
mPath = path;
defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new CrashHandler());
}
@Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
File dir = new File(mContext.getExternalCacheDir(), mPath);
if (!dir.exists()) {
dir.mkdirs();
}
long l = System.currentTimeMillis();
File file = new File(dir, l + ".txt");
try {
PrintWriter pw = new PrintWriter(new FileWriter(file));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
pw.println("time: " + sdf.format(new Date()));
pw.println("thread: " + thread.getName());
throwable.printStackTrace(pw);
pw.flush();
pw.close();
} catch (Exception ioException) {
ioException.printStackTrace();
} finally {
if (defaultUncaughtExceptionHandler != null) {
defaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
}
}
}
}
新建一個(gè)CrashUtil
類艳吠,封裝CrashHandler
public class CrashUtil {
//Java層Crash信息保存目錄
public static final String JAVA_CRASH_DIR = "java_crash";
public static void init(Context context) {
//初始化Java層CrashHandler
Context applicationContext = context.getApplicationContext();
CrashHandler.init(applicationContext, JAVA_CRASH_DIR);
}
}
在Application的onCreate方法中,初始化CrashHandler
public class AppInit extends Application {
@Override
public void onCreate() {
super.onCreate();
CrashUtil.init(this);
}
}
在AndroidManifest
中配置Application
的name
屬性
<application
android:name=".AppInit"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!--省略其它代碼-->
</application>
Native層Crash
Native層crash監(jiān)控,可以采用Google的BreakPad開源框架腹缩。開源地址:https://github.com/google/breakpad
BreakPad需要編譯后才能使用。
新建一個(gè)module,專門編譯BreakPad藏鹊。
要新建的breakpadlib的src/main目錄下润讥,新建cpp目錄,這個(gè)目錄名稱隨意盘寡。
在cpp目錄下新建breakpad目錄楚殿,這個(gè)目錄名稱隨意。
將下載回來的BreakPad代碼解壓后竿痰,將里面的src目錄都復(fù)制到剛才新建的breakpad目錄下脆粥。
-
在breakpad目錄下新建CMakeLists.txt文件,在里面配置如下
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)
-
在breakpadlib模塊的src/main/java/包名/目錄下影涉,創(chuàng)建NativeCrash.java文件变隔,在這里聲明Native方法,方便其它模塊調(diào)用
public class NativeCrash { static { System.loadLibrary("breakpad_lib"); } public static void init(String path) { initNativeCrash(path); } /** * 初始化NativeCrash * * @param path */ private static native void initNativeCrash(String path); }
-
在cpp目錄下蟹倾,新建breakpad_lib.cpp文件匣缘,實(shí)現(xiàn)native方法,并配置Native層crash的監(jiān)聽
#include <jni.h> #include <android/log.h> #include "client/linux/handler/minidump_descriptor.h" #include "client/linux/handler/exception_handler.h" #define TAG "breakpad_lib" bool DumpCallback(const google_breakpad::MinidumpDescriptor &descriptor, void *context, bool succeeded) { __android_log_print(ANDROID_LOG_ERROR, TAG, "native crash:%s", descriptor.path()); return false; } extern "C" JNIEXPORT void JNICALL Java_com_wangyz_breadpadlib_NativeCrash_initNativeCrash(JNIEnv *env, jclass clazz, jstring path_) { const char *path = env->GetStringUTFChars(path_, 0); __android_log_print(ANDROID_LOG_INFO, TAG, "initNativeCrash,dmp path:%s", path); google_breakpad::MinidumpDescriptor descriptor(path); static google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback, NULL, true, -1); env->ReleaseStringUTFChars(path_, path); }
-
在cpp目錄下創(chuàng)建CMakeLists.txt文件
cmake_minimum_required(VERSION 3.4.1) include_directories(breakpad/src breakpad/src/common/android/include) add_library(breakpad_lib SHARED breakpad_lib.cpp) add_subdirectory(breakpad) target_link_libraries(breakpad_lib breakpad log)
-
在breakpadlib的build.gradle文件中配置ndk
android { //... defaultConfig { //... // 要編譯哪些CPU架構(gòu) externalNativeBuild { cmake { abiFilters 'armeabi-v7a', 'x86' } } // 把哪些CPU架構(gòu)打包進(jìn)去 ndk { abiFilters 'armeabi-v7a', 'x86' } } externalNativeBuild { cmake { path 'src/main/cpp/CMakeLists.txt' } } //... }
build這個(gè)module鲜棠,就會(huì)生成包含so的aar孵户,可以給其它模塊引用,這樣就可以為Native層的Crash增加監(jiān)控岔留。
breakpadlib目錄結(jié)構(gòu)如圖
注意事項(xiàng)
在編譯時(shí)夏哭,一般會(huì)報(bào)錯(cuò)。這是由于缺少相應(yīng)的文件導(dǎo)致的献联。將linux_syscall_support.h
文件放到cpp/breakpad/src/third_part/lss目錄下竖配,再編譯就可以了。
app模塊引用breakpad編譯出來的aar
將編譯出來的aar放到app模塊的libs目錄下里逆,并配置app的build.gradle文件
android {
//...
sourceSets {
main {
jniLibs.srcDir(['libs'])
}
}
repositories {
flatDir {
dirs 'libs'
}
}
}
dependencies {
//...
api(name: 'breadpadlib-release', ext: 'aar')
}
同步工程进胯,然后在前面創(chuàng)建的CrashUtil中增加對(duì)Native的Crash的初始化。
public class CrashUtil {
//Java層Crash信息保存目錄
public static final String JAVA_CRASH_DIR = "java_crash";
//Native層Crash信息保存目錄
public static final String NATIVE_CRASH_DIR = "native_crash";
public static void init(Context context) {
//初始化Java層CrashHandler
Context applicationContext = context.getApplicationContext();
CrashHandler.init(applicationContext, JAVA_CRASH_DIR);
//初始化Native層CrashHandler
File file = new File(context.getExternalCacheDir(), NATIVE_CRASH_DIR);
if (!file.exists()) {
file.mkdirs();
}
NativeCrash.init(file.getAbsolutePath());
}
}
模擬Native層Crash
新建一個(gè)module:crash,用于生成演示Native層Crash所用的so原押。
-
在crash模塊下的src/main/java/目錄下創(chuàng)建CrashTest.java胁镐,聲明native方法
public class CrashTest { static { System.loadLibrary("crash_test"); } public static void testCrash() { test(); } private static native void test(); }
在crash模塊下的src/main/目錄下,新建jni目錄
-
在jni目錄下新建cpp目錄诸衔,在cpp目錄下創(chuàng)建crash_test.cpp
#include <jni.h> extern "C" JNIEXPORT void JNICALL Java_com_wangyz_crash_CrashTest_test(JNIEnv* env,jclass clazz) { int * i = NULL; *i = 0; }
-
生成so
可以通過ndk-build直接生成so文件盯漂。在jni目錄下創(chuàng)建Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := crash_test LOCAL_SRC_FILES := cpp/crash_test.cpp include $(BUILD_SHARED_LIBRARY)
然后在jni的目錄下,執(zhí)行cmd命令ndk-build笨农。如果沒有在系統(tǒng)環(huán)境變量中配置ndk就缆,一般會(huì)報(bào)
'ndk-build' 不是內(nèi)部或外部命令,也不是可運(yùn)行的程序
谒亦。通過Android Studio下載的ndk竭宰,一般在Android Studio配置的SDK目錄下空郊。進(jìn)入SDK目錄下的ndk目錄,選擇一個(gè)版本的ndk切揭,將這個(gè)路徑配置到環(huán)境變量中狞甚。重新打開CMD窗口,并定位到j(luò)ni目錄下廓旬,重新執(zhí)行ndk-build,就會(huì)在jni目錄下生成libs和obj目錄哼审。也可以在crash模塊的build.gradle中配置來生成aar
android { //... defaultConfig { //... // 要編譯哪些CPU架構(gòu) externalNativeBuild { cmake { abiFilters 'armeabi-v7a', 'x86' } } // 把哪些CPU架構(gòu)打包進(jìn)去 ndk { abiFilters 'armeabi-v7a', 'x86' } } externalNativeBuild { cmake { path 'src/main/jni/CMakeLists.txt' } } //... }
在jni目錄下創(chuàng)建CMakeLists.txt
# CMake方式需要在build.grale中配置其它信息,但可以build生成aar # 采用Android.mk配置,不需要再額外配置信息 cmake_minimum_required(VERSION 3.4.1) include_directories(cpp/) add_library(crash_test SHARED cpp/crash_test.cpp) target_link_libraries(crash_test)
build后嗤谚,就可以生成包含so的aar文件
crash模塊的目錄結(jié)構(gòu)
breakpadlib目錄結(jié)構(gòu) -
將aar放到app模塊的libs目錄,并在build.gradle中增加對(duì)新加入的aar的引用
api(name: 'crash-release', ext: 'aar')## 測(cè)試Crash
測(cè)試Crash
Java Crash
在Activity的onCreate中加入以下代碼
//模擬java crash
int i = 1 / 0;
打包APK怔蚌,安裝到手機(jī)巩步,運(yùn)行APK,程序Crash桦踊。查看/sdcard/Android/data/包名/椅野,發(fā)現(xiàn)在java_crash目錄,生成了記錄crash信息的文件籍胯。
Native Crash
在Activity的onCreate中加入以下代碼
//模擬native crash
CrashTest.testCrash();
打包APK竟闪,安裝到手機(jī),運(yùn)行APK杖狼,程序Crash炼蛤。查看/sdcard/Android/data/包名/,發(fā)現(xiàn)在native_crash目錄蝶涩,生成了記錄crash信息的文件xxx.dmp理朋。
將dmp文件導(dǎo)出到電腦。然后在Android Studio的安裝目錄/bin/lldb/bin中绿聘,執(zhí)行CMD命令
minidump_stackwalk D:\15d05b74-5316-4082-cb24cda2-80f355e4.dmp > crash.txt
在當(dāng)前目錄下會(huì)生成crash.txt文件
Operating system: Android
0.0.0 Linux 4.4.23+ #1 SMP PREEMPT Wed Sep 30 13:40:36 CST 2020 armv7l
CPU: arm
ARMv1 ARM part(0x4100d0b0) features: half,thumb,fastmult,vfpv2,edsp,neon,vfpv3,tls,vfpv4,idiva,idivt
8 CPUs
GPU: UNKNOWN
Crash reason: SIGILL
Crash address: 0xcb13875c
Process uptime: not available
Thread 0 (crashed)
0 libcrash_test.so + 0x75c
r0 = 0xed7b1230 r1 = 0xffc18d0c r2 = 0x00430000 r3 = 0xffc18ec8
r4 = 0xef0c35f4 r5 = 0x00000000 r6 = 0x00000000 r7 = 0xffc18fc8
r8 = 0x00000056 r9 = 0xed7cb000 r10 = 0x00000000 r12 = 0xcb13875d
fp = 0xffc18d74 sp = 0xffc18d00 lr = 0xcb2d3519 pc = 0xcb13875c
Found by: given as instruction pointer in context
1 dalvik-LinearAlloc (deleted) + 0xe5f2
sp = 0xffc18d04 pc = 0xef0c35f4
Found by: stack scanning
2 dalvik-main space (region space) (deleted) + 0xc909ce
sp = 0xffc18d10 pc = 0x138909d0
Found by: stack scanning
3 libart.so + 0x3e0931
sp = 0xffc18d70 pc = 0xeb2a0933
Found by: stack scanning
4 base.vdex + 0x1d521f
sp = 0xffc18d78 pc = 0xcb519221
Found by: stack scanning
這個(gè)文件列出了操作系統(tǒng)嗽上、CPU等信息。
在ndk目錄下熄攘,找到ndk\21.0.6113669\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\目錄兽愤,這里的arm-linux-androideabi-4.9具體以手機(jī)的CPU架構(gòu)為準(zhǔn)。
執(zhí)行CMD
arm-linux-androideabi-addr2line.exe -f -C -e D:\libcrash_test.so 0x75c
這里的libcrash_text.so就是發(fā)生crash的so文件挪圾,0x75c為上面轉(zhuǎn)換出來的crash.txt中發(fā)生crash的地址浅萧。通過addr2line工具,可以看到發(fā)生crash的具體行數(shù)哲思。
后記
關(guān)于crash監(jiān)控方案惯殊,本文記錄得比較淺顯的,可以查閱其它更詳細(xì)的資料也殖。
源碼
https://github.com/milovetingting/Samples/tree/master/BreakPad