App奔溃日志收集(Native Crash,java crash),UncaughtExceptionHandler

经典随机Crash之一:线程安全- http://mp.weixin.qq.com/s?__biz=MzAxMzYyNDkyNA==&mid=2651332781&idx=1&sn=f751d770f8853d503300b3c19abaea6c&chksm=8063339eb714ba88cd547f4e81981337a78da41563c01ef82b2e2e30450d84e5f0d308d86e4e&mpshare=1&scene=23&srcid=0405o4nlDjNFIS3GDESgT9L5#rd

-- Android捕获崩溃异常- https://github.com/shenhuniurou/BlogDemos/tree/master/CrashDemo
 捕捉异常:try catch finally ;
 try catch捕获不到的异常,使用:Thread.UncaughtExceptionHandler 。

-- Android 平台 Native 代码的崩溃捕获机制及实现- https://mp.weixin.qq.com/s/g-WzYF3wWAljok1XjPoo7w?
解决崩溃
1.优先级: 优先解决Top 崩溃或者对业务有重大影响;其次简单易排查的java崩溃,疑难杂症放到最后
2.尝试复现:针对疑难杂症,可以通过早先在日志中自定义的应用信息来复现崩溃过程
3.着眼硬件:排查是否是手机系统,内存,机型引起的。
4.系统错误:有些问题是Android系统不同版本之间存在的问题。

-- 捕获异常
 1. 在4.1.1以上,5.0以下:使用安卓系统自带的libcorkscrew.so
 2. 5.0以上:安卓系统中没有了libcorkscrew.so,使用自己编译的libunwind
  在用户环境中的很多native crash单靠堆栈是解决不了的,logcat是非常重要的补充。好几例webview crash都是通过发生crash时的logcat定位的。

> Android Native Crash;APP java和native Crash;(libcorkscrew,libbacktrace,libunwind)
Android崩溃有两类,一类是Java Exception异常,一类是Native Signal异常。
  4.1.1以上,5.0以下利用libcorkscrew.so来获取,但是libcorkscrew.so在Android4.4之后废弃了。当Android版本号大于4.4时,可以采用libbacktrace.so(5.0以上)来获取崩溃数据。
  基于google-breakpad的方法体量较大,但获取的信息较全面。基于异常信号处理方法体量小,但信息不全面,只有崩溃线程的堆栈信息,不利于定位多线程崩溃的问题。
-- Android平台Native代码的崩溃捕获机制及实现- https://blog.csdn.net/mba16c35/article/details/54178067
5.0以下使用libcorkscrew,只在4.1.1以上,5.0以下的系统存在;5.0以上的系统使用了libunwind来代替libcorkscrew。
 CoffeeCatch, a tiny native POSIX signal catcher (especially useful for JNI code on Android/Dalvik)- https://github.com/xroche/coffeecatch
-- Android 平台 Native 代码的崩溃捕获机制及实现- https://my.oschina.net/bugly/blog/1354954
 在4.1.1以上,5.0以下:使用安卓系统自带的libcorkscrew.so;5.0以上:安卓系统中没有了libcorkscrew.so,使用自己编译的libunwind,libunwind是一个独立的开源库,高版本的安卓源码中也使用了libunwind作为解堆栈的工具,并针对安卓做了一些适配。
 libunwind下载编译- http://download.savannah.nongnu.org/releases/libunwind/
-- native crash现有的方案:
1.Google Breakpad 权威,跨平台        代码体量较大
2.利用LogCat日志 利用安卓系统实现 需要在crash时启动新进程过滤logcat日志,不可靠
3.coffeecatch        实现简洁,改动容易 存在兼容性问题

综合考虑,可以基于coffeecatch + libunwind改进。

-- 移动应用崩溃日志收集工具对比- http://blog.csdn.net/chaosminds/article/details/56287953
  1.腾讯bugly,应用崩溃分析能力优秀,并且提供了一定的运营数据统计能力。
  2.友盟u-app,侧重于专业的运营数据的统计,但在崩溃分析方面,相比其他工具显得单一。
  3.Crasheye,同样拥有优秀的崩溃分析能力,但不具备运营数据的统计。但在测试阶段,不需要统计运营数据时,其一行代码快速接入SDK的能力,方便快速开发,是其优势所在。
  总而言之,这三款工具各自的优势和侧重点不同。根据具体场景的需要,可以选择合适的工具进行开发。

-- 常见的Android崩溃有两类,一类是Java Exception异常,一类是Native Signal异常。
Java的异常可以分为两类:Checked Exception和UnChecked Exception。
  所有RuntimeException类及其子类的实例被称为Runtime异常,即UnChecked Exception;
  Checked异常又称为编译时异常,即在编译阶段被处理的异常。RuntimeException类及其子类的异常实例则被称为Checked Exception。

Android平台的崩溃捕获机制及实现- http://blog.csdn.net/leeo1010/article/details/50522892
app崩溃日志收集以及上传- https://github.com/DrJia/AndroidLogCollector

在Android中自定义捕获Application全局异常- http://blog.csdn.net/qq_23547831/article/details/41725069

-- 美团外卖Android Crash治理之路- https://blog.csdn.net/MeituanTech/article/details/80701773
 常见的Crash类型包括:空节点、角标越界、类型转换异常、实体对象没有序列化、数字转换异常、Activity或Service找不到等。这类Crash是App中最为常见的Crash,也是最容易反复出现的。在获取Crash堆栈信息后,解决这类Crash一般比较简单,更多考虑的应该是如何避免。
 两个我们治理的量比较大的Crash:NullPointerException,IndexOutOfBoundsException

 下面是常见的Crash解决思路:
 1.尝试找到造成Crash的可疑代码,看是否有特异的API或者调用方式不当导致的,尝试修改代码逻辑来进行规避。
 2.通过Hook来解决,Hook分为Java Hook和Native Hook。Java Hook主要靠反射或者动态代理来更改相应API的行为,需要尝试找到可以Hook的点,一般Hook的点多为静态变量,同时需要注意Android不同版本的API,类名、方法名和成员变量名都可能不一样,所以要做好兼容工作;Native Hook原理上是用更改后方法把旧方法在内存地址上进行替换,需要考虑到Dalvik和ART的差异;相对来说Native Hook的兼容性更差一点,所以用Native Hook的时候需要配合降级策略。
 3.如果通过前两种方式都无法解决的话,我们只能尝试反编译ROM,寻找解决的办法。
> native crash
Android 平台 Native 代码的崩溃捕获机制及实现- http://geek.csdn.net/news/detail/210852
Android平台,native crash一直是crash里的大头:
1.Google Breakpad,权威,跨平台 代码体量较大
2.利用LogCat日志,利用安卓系统实现,需要在crash时启动新进程过滤logcat日志,不可靠
(推荐)3.coffeecatch,实现简洁,改动容易,存在兼容性问题

-- Native Crash在Android上的特点:
1.出错时界面不会弹出提示框提醒程序崩溃(Android 5.0以下);
2.出错时会弹出提示框提醒程序崩溃(Android 5.0以上);
3.程序会直接闪退到系统桌面;
4.这类错误一般是由C++层代码错误引起的;

5.绝大部分Crash工具不能够捕获;

-- Native崩溃时,Android系统同样会输出崩溃堆栈到LogCat,那么拿到了LogCat信息也就拿到了Native的崩溃堆栈。

Process process = Runtime.getRuntime().exec(new String[]{"logcat","-d","-v","threadtime"});
String logTxt = getSysLogInfo(process.getInputStream());

> java crash
通过UncaughtExceptionHandler来记录dump异常日志
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import android.util.Log;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CrashHandler implements UncaughtExceptionHandler {
    private static final String TAG = "CrashHandler";
    private static final boolean DEBUG = true;

    private static final String PATH = Environment.getExternalStorageDirectory() + "/CrashDemo/log/";
    private static final String FILE_NAME = "crash";
    private static final String FILE_NAME_SUFFIX = ".trace";
    private static final String ABOLUTE_PATH = PATH + FILE_NAME + FILE_NAME_SUFFIX;
    private String deviceToken;

    private static CrashHandler sInstance = new CrashHandler();
    private UncaughtExceptionHandler mDefaultCrashHandler;
    private Context mContext;

    private CrashHandler() {
    }

    public static CrashHandler getInstance() {
        return sInstance;
    }

    public void init(Context context) {
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
        mContext = context.getApplicationContext();
    }

    /**
     * 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用#uncaughtException方法
     * thread为出现未捕获异常的线程,ex为未捕获的异常,有了这个ex,我们就可以得到异常信息。
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        try {
            // 导出异常信息到SD卡中
            dumpExceptionToSDCard(ex);
        } catch (IOException e) {
            e.printStackTrace();
        }
        ex.printStackTrace();
        // 如果系统提供了默认的异常处理器,则交给系统去结束我们的程序,否则就由我们自己结束自己
        if (mDefaultCrashHandler != null) {
            mDefaultCrashHandler.uncaughtException(thread, ex);
        } else {
            Process.killProcess(Process.myPid());
        }
    }
    private File dumpExceptionToSDCard(Throwable ex) throws IOException {
        // 如果SD卡不存在或无法使用,则无法把异常信息写入SD卡
        if (!Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            if (DEBUG) {
                Log.w(TAG, "sdcard unmounted,skip dump exception");
                return null;
            }
        }
        File dir = new File(PATH);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        long current = System.currentTimeMillis();
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                .format(new Date(current));
        // File file = new File(PATH + FILE_NAME + time + "_"+ deviceToken +
        // FILE_NAME_SUFFIX);
        File file = new File(PATH + FILE_NAME + FILE_NAME_SUFFIX);
        if (!file.exists()) {
            file.createNewFile();
        } else {
            try {
                // 追加内容
                PrintWriter pw = new PrintWriter(new BufferedWriter(
                        new FileWriter(file, true)));
                pw.println(time);
                dumpPhoneInfo(pw);
                pw.println();
                ex.printStackTrace(pw);
                pw.println("---------------------------------分割线----------------------------------");
                pw.println();
                pw.close();
            } catch (Exception e) {
                Log.e(TAG, "dump crash info failed");
            }
        }
        return file;
    }

    private void dumpPhoneInfo(PrintWriter pw) throws NameNotFoundException {
        PackageManager pm = mContext.getPackageManager();
        PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(),
                PackageManager.GET_ACTIVITIES);
        pw.print("App Version: ");
        pw.print(pi.versionName);
        pw.print('_');
        pw.println(pi.versionCode);

        // android版本号
        pw.print("OS Version: ");
        pw.print(Build.VERSION.RELEASE);
        pw.print("_");
        pw.println(Build.VERSION.SDK_INT);

        // 手机制造商
        pw.print("Vendor: ");
        pw.println(Build.MANUFACTURER);
        // 手机型号
        pw.print("Model: ");
        pw.println(Build.MODEL);
        // cpu架构
        pw.print("CPU ABI: ");
        pw.println(Build.CPU_ABI);
    }

    /**
     * 提供方法上传异常信息到服务器
     * @param log
     */
    private void uploadExceptionToServer(File log) {
        // TODO Upload Exception Message To Your Web Server
    }
}

public class CrashApplication extends Application {  
 
    @Override  
    public void onCreate() {  
        // TODO Auto-generated method stub  
        super.onCreate();  
        CrashHandler crashHandler = CrashHandler.getInstance();  
        crashHandler.init(getApplicationContext());  
    }  
}  

-------------------------------

-- android和iOS平台的崩溃捕获和收集- https://www.cnblogs.com/sevenyuan/p/4347757.html
  要实现崩溃捕获和收集的困难主要有这么几个:
1、如何捕获崩溃(比如c++常见的野指针错误或是内存读写越界,当发生这些情况时程序不是异常退出了吗,我们如何捕获它呢)
2、如何获取堆栈信息(告诉我们崩溃是哪个函数,甚至是第几行发生的,这样我们才可能重现并修改问题)
3、将错误日志上传到指定服务器(这个最好办)

  会引发崩溃的代码本质上就两类,一个是c++语言层面的错误,比如野指针,除零,内存访问异常等等;另一类是未捕获异常(Uncaught Exception)
 
首先是java代码的崩溃捕获,这个可以仿照最下面的完整代码写一个UncaughtExceptionHandler,然后在所有的Activity的onCreate函数最开始调用
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(this));

 这样,当发生崩溃的时候,就会自动调用UncaughtExceptionHandler的public void uncaughtException(Thread thread, Throwable exception)函数,其中的exception包含堆栈信息,我们可以在这个函数里面打印我们需要的信息,并且上传错误日志
 然后是重中之重,jni的c++代码如何进行崩溃捕获。
#include
#include
static struct sigaction old_sa[NSIG];

void InitCrashReport() 

    CCLOG("InitCrashReport"); 
 
    // Try to catch crashes... 
    struct sigaction handler; 
    memset(&handler, 0, sizeof(struct sigaction)); 
 
    handler.sa_sigaction = android_sigaction; 
    handler.sa_flags = SA_RESETHAND; 
 
#define CATCHSIG(X) sigaction(X, &handler, &old_sa[X]) 
    CATCHSIG(SIGILL); 
    CATCHSIG(SIGABRT); 
    CATCHSIG(SIGBUS); 
    CATCHSIG(SIGFPE); 
    CATCHSIG(SIGSEGV); 
    CATCHSIG(SIGSTKFLT); 
    CATCHSIG(SIGPIPE); 

通过singal的设置,当崩溃发生的时候就会调用android_sigaction函数。这同样是linux的信号机制。 此处设置信号回调函数的代码跟iOS有点不同,这个只是同一个功能的两种不同写法,没有本质区别。有兴趣的可以google下两者的区别。

jni的崩溃回调函数如下:
void android_sigaction(int signal, siginfo_t *info, void *reserved) 

    if (!g_env) { 
        return; 
    } 
 
    jclass classID = g_env->FindClass(CLASS_NAME); 
    if (!classID) { 
        return; 
    } 
 
    jmethodID methodID = g_env->GetStaticMethodID(classID, "onNativeCrashed", "()V"); 
    if (!methodID) { 
        return; 
    } 
 
    g_env->CallStaticVoidMethod(classID, methodID); 
 
    old_sa[signal].sa_handler(signal); 

java对应的函数实现如下:
public static void onNativeCrashed() { 
    // http://stackoverflow.com/questions/1083154/how-can-i-catch-sigsegv-segmentation-fault-and-get-a-stack-trace-under-jni-on-a 
    Log.e("handller", "handle"); 
    new RuntimeException("crashed here (native trace should follow after the Java trace)").printStackTrace(); 
    s_instance.startActivity(new Intent(s_instance, CrashHandler.class)); 

我们开启了一个新的activity,因为当jni发生崩溃的时候,原始的activity可能已经结束掉了。  这个新的activity实现如下:
publicclass CrashHandler extends Activity 

    publicstaticfinal String TAG = "CrashHandler"; 
    protectedvoid onCreate(Bundle state) 
    { 
        super.onCreate(state); 
        setTitle(R.string.crash_title); 
        setContentView(R.layout.crashhandler); 
        TextView v = (TextView)findViewById(R.id.crashText); 
        v.setText(MessageFormat.format(getString(R.string.crashed), getString(R.string.app_name))); 
        final Button b = (Button)findViewById(R.id.report), 
              c = (Button)findViewById(R.id.close); 
        b.setOnClickListener(new View.OnClickListener(){ 
            publicvoid onClick(View v){ 
                final ProgressDialog progress = new ProgressDialog(CrashHandler.this); 
                progress.setMessage(getString(R.string.getting_log)); 
                progress.setIndeterminate(true); 
                progress.setCancelable(false); 
                progress.show(); 
                final AsyncTask task = new LogTask(CrashHandler.this, progress).execute(); 
                b.postDelayed(new Runnable(){ 
                    publicvoid run(){ 
                        if (task.getStatus() == AsyncTask.Status.FINISHED) 
                            return; 
                        // It's probably one of these devices where some fool broke logcat. 
                        progress.dismiss(); 
                        task.cancel(true); 
                        new AlertDialog.Builder(CrashHandler.this) 
                            .setMessage(MessageFormat.format(getString(R.string.get_log_failed), getString(R.string.author_email))) 
                            .setCancelable(true) 
                            .setIcon(android.R.drawable.ic_dialog_alert) 
                            .show(); 
                    }}, 3000); 
            }}); 
        c.setOnClickListener(new View.OnClickListener(){ 
            publicvoid onClick(View v){ 
                finish(); 
            }}); 
    } 
 
    static String getVersion(Context c) 
    { 
        try { 
            return c.getPackageManager().getPackageInfo(c.getPackageName(),0).versionName; 
        } catch(Exception e) { 
            return c.getString(R.string.unknown_version); 
        } 
    } 

 
class LogTask extends AsyncTask 

    Activity activity; 
    String logText; 
    Process process; 
    ProgressDialog progress;  
 
    LogTask(Activity a, ProgressDialog p) { 
        activity = a; 
        progress = p; 
    } 
 
    @Override 
    protected Void doInBackground(Void... v) { 
        try { 
            Log.e("crash", "doInBackground begin"); 
            process = Runtime.getRuntime().exec(new String[]{"logcat","-d","-t","500","-v","threadtime"}); 
            logText = UncaughtExceptionHandler.readFromLogcat(process.getInputStream()); 
            Log.e("crash", "doInBackground end"); 
        } catch (IOException e) { 
            e.printStackTrace(); 
            Toast.makeText(activity, e.toString(), Toast.LENGTH_LONG).show(); 
        } 
        returnnull; 
    } 
 
    @Override 
    protectedvoid onCancelled() { 
        Log.e("crash", "onCancelled"); 
        process.destroy(); 
    } 
 
    @Override 
    protectedvoid onPostExecute(Void v) { 
        Log.e("crash", "onPostExecute"); 
        progress.setMessage(activity.getString(R.string.starting_email)); 
        UncaughtExceptionHandler.sendLog(logText, activity); 
        progress.dismiss(); 
        activity.finish(); 
        Log.e("crash", "onPostExecute over"); 
    } 

获取到错误日志后,就可以写到sd卡(同样不要忘记添加权限),或者是上传。

-- Android崩溃SDK日志捕获(Checked、unChecked异常)
  常见的Android崩溃有两类,一类是Java Exception异常,一类是Native Signal异常。对于很多基于Unity、Cocos平台的游戏,还会有C#、JavaScript、Lua等的异常。
  Java的异常可以分为两类:Checked Exception和UnChecked Exception。所有RuntimeException类及其子类的实例被称为Runt ime异常,即UnChecked Exception,不是RuntimeException类及其子类的异常实例则被称为Checked Exception。
  Checked异常又称为编译时异常,即在编译阶段被处理的异常。编译器会强制程序处理所有的Checked异常,也就是用try…catch显式的捕获并处理,因为Java认为这类异常都是可以被处理(修复)的。在Java API文档中,方法说明时,都会添加是否throw某个exception,这个exception就是Checked异常。如果没有try…catch这个异常,则编译出错,错误提示类似于“Unhandled 

-- 该类异常捕获的流程是:
  执行try块中的代码出现异常,系统会自动生成一个异常对象,并将该异常对象提交给Java运行环境,这个就是异常抛出(throw)阶段;
  当Java运行环境收到异常对象时,会寻找最近的能够处理该异常对象的catch块,找到之后把该异常对象交给catch块处理,这个就是异常捕获(catch)阶段。

- Checked异常一般是不引起Android App Crash的,注意是“一般”,这里之所以介绍,有两个原因:
 形成系统的了解,更好地对比理解UnCheckedException;
 对于一些Checked Exception,虽然我们在程序里面已经捕获并处理了,但是如果能同时将该异常收集并发送到后台,将有助于提升App的健壮性。比如修改代码逻辑回避该异常,或者捕获后采用更好的方法去处理该异常。至于应该收集哪些Checked Exception,则取决于App的业务逻辑和开发者的经验了。

- UnChecked异常又称为运行时异常,即Runtime-Exception,最常见的莫过于NullPointerException。UnChecked异常发生时,由于没有相应的try…catch处理该异常对象,所以Java运行环境将会终止,程序将退出,也就是我们所说的Crash。当然,你可能会说,那我们把这些异常也try…catch住不就行了。理论上确实是可以的,但有两点会导致这种方案不可行:
  无法将所有的代码都加上try…catch,这样对代码的效率和可读性将是毁灭性的;
  UnChecked异常通常都是较为严重的异常,或者说已经破坏了运行环境的。比如内存地址,即使我们try…catch住了,也不能明确知道如何处理该异常,才能保证程序接下来的运行是正确的。

-- Java层异常捕获,Exception的分类及捕获:
static class MyCrashHandler implements UncaughtExceptionHandler {  

private UncaughtExceptionHandler originalHandler;  

private MyCrashHandler(Context context) {  

originalHandler = Thread.getDefaultUncaughtExceptionHandler(); }  

@Override
public void uncaughtException(Thread thread, final Throwable throwable) {  
// Deal this exception  
if (originalHandler != null) {  
  originalHandler.uncaughtException(thread, throwable);  
}  
}}

 获取Exception崩溃堆栈:
捕获Exception之后,我们还需要知道崩溃堆栈的信息,这样有助于我们分析崩溃的原因,查找代码的Bug。异常对象的printStackTrace方法用于打印异常的堆栈信息,根据printStackTrace方法的输出结果,我们可以找到异常的源头,并跟踪到异常一路触发的过程。

public static String getStackTraceInfo(final Throwable throwable) {
    String trace = "";
    try {
        Writer writer = new StringWriter();
        PrintWriter pw = new PrintWriter(writer);
        throwable.printStackTrace(pw);
        trace = writer.toString();
        pw.close();
    } catch (Exception e) {
        return "";
    }
    return trace;
}

-- Native崩溃分析与捕获
Linux进程对信号异常的响应可以归结为以下几类:
 忽略信号:对信号不做任何处理,除了SIGKILL及SIGSTOP以外(超级用户杀掉进程时产生),其他都可以忽略;
 捕获信号:注册信号处理函数,当信号发生时,执行相应的处理函数;
 默认处理:执行内核分配的默认信号处理函数,大多数我们遇到的信号异常,默认处理是终止程序并生成core文件。

对Native代码的崩溃,可以通过调用sigaction()注册信号处理函数来完成。
#include  
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
signum:代表信号编码,可以是除SIGKILL及SIGSTOP外的任何一个特定有效的信号,如果为这两个信号定义自己的处理函数,将导致信号安装错误。
act:指向结构体sigaction的一个实例的指针,该实例指定了对特定信号的处理,如果设置为空,进程会执行默认处理。
oldact:和参数act类似,只不过保存的是原来对相应信号的处理,也可设置为NULL。

Native代码崩溃(即信号异常)捕获的代码片段如下:
constint handledSignals[] = { SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS };
constint handledSignalsNum = sizeof(handledSignals) / sizeof(handledSignals[0]);
struct sigaction old_handlers[handledSignalsNum];
int nativeCrashHandler_onLoad(JNIEnv *env) {  
struct sigaction handler;  
memset(&handler,0,sizeof(sigaction));  
handler.sa_sigaction = my_sigaction;  
handler.sa_flags = SA_RESETHAND;  
for (int i = 0; i < handledSignalsNum; ++i) { sigaction(handledSignals[i], &handler, &old_handlers[i]); }  
return 1; }

  当Android应用程序加载so库的时候,调用nativeCrashHandler_onLoad会为SIGSEGV、SIGABRT、SIGFPE、SIGILL、SIGBUS通过sigaction注册信号处理函数my_sigaction。当发生Native崩溃并且发生前面几个信号异常时,就会调用my_sigaction完成信号处理。
notifyNativeCrash  = (*env)->GetMethodID(env, cls,  "notifyNativeCrash", "()V");
void my_sigaction(int signal, siginfo_t *info, void *reserved) {
    // Here catch the native crash
}
Android没有提供像throwable.printStackTrace一样的接口去获取Native崩溃后堆栈信息,所以我们需要自己想办法实现。这里有两种思路可以考虑。

-- 利用LogCat日志
  在本地调试代码时,我们经常通过查看LogCat日志来分析解决问题。对于发布的应用,在代码中执行命令“logcat -d -v threadtime”也能达到同样的效果,只不过是获取到了用户手机的logcat。当Native崩溃时,Android系统同样会输出崩溃堆栈到LogCat,那么拿到了LogCat信息也就拿到了Native的崩溃堆栈。
 Process process = Runtime.getRuntime().exec(new String[]{"logcat","-d","-v","threadtime"});
 String logTxt = getSysLogInfo(process.getInputStream());
在my_sigaction捕获到异常信号后,通知Java层代码,在Java层启动新的进程,并在新的进程中完成上面的操作。这里注意一定要在新的进程中完成,因为原有的进程马上就会结束。
  网络上有一些对应这种思路的代码,但是在很多手机上都无法获得Native的崩溃堆栈。原因是对崩溃堆栈产生了破坏,使得相关信息并没有输出到logcat中。研究一下Android backtrace的底层实现以及Google Breakpad的源码,会帮助你解决这个问题。

-- Google Breakpad
  Linux提供了Core Dump机制,即操作系统会把程序崩溃时的内存内容dump出来,写入一个叫做core的文件里面。Google Breakpad作为跨平台的崩溃转储和分析模块(支持Windows、OS X、Linux、iOS和Android等),便是通过类似的MiniDump机制来获取崩溃堆栈的。
  通过Google Breakpad捕获信号异常,并将堆栈信息写入你指定的本地MiniDump文件中。下次启动应用程序的时候,便可以读取该MiniDump文件进行相应的操作,比如上传到后台服务器。当然,也可以修改Google Breakpad的源码,不写MiniDump文件,而是通过dumpCallback直接获得堆栈信息,并将相关信息通知到Java层代码,做相应的处理。
  Google Breakpad是权威的捕获Native崩溃的方法,相关的使用方法可以查看官网文档。由于它跨平台,代码体量较大,所以建议大家裁剪源码,只保留Android相关的功能,保持自己APK的小巧。

你可能感兴趣的:(行业和产业,热点(hot)技术,性能优化与测试)