精华内容
下载资源
问答
  • Android Crash 治理之道

    2021-01-03 16:32:45
    Crash是指由于未处理的异常或者信号导致的意外退出,使得Android应用崩溃。当应用崩溃时,Android会杀死应用的进程并显示一个对话框来告知用户,他的应用由于未知的意外而停止了。当然现在的国内厂商自定义的系统...
  • AndroidCrash全局崩溃异常捕获,友好的提示页面,收集手机信息+捕获异常信息,并上传到服务器,带重启APP功能。
  • Android crash 日志

    2017-10-18 15:15:56
    Android crash 文件。用于分析 force close ,快速定位 bug。
  • Android端崩溃发生时候,收集错误信息以及手机型号,发送到指定的邮箱,便于开发者了解崩溃原因,及时修改错误,提高用户体验
  • android crash

    2015-12-31 10:05:06
    抓取app的奔溃日志报告,并且可以以邮件的形式将发送给开发者,也可以将错误日志以.txt文件形式保存
  • 软件是人思维的产物。智者千虑,必有一失,人的思维总有缺陷,反映到软件层面上就是程序 bug。程序 bug 的终极体现就是core dump,core dump 是软件错误无法恢复的产物。 我们经常见到的bug说“闪退、意外终止等”...
  • Android crash log分析工具

    热门讨论 2012-08-24 11:26:10
    一个linux下面分析Android崩溃日志(crash log)的工具。把崩溃的日志信息(带I/DEBUG标记)复制到xx文件中,用编辑器编辑工具中Android编译的符号(symbol)所在的位置,我的是在309行"SYMBOLS_DIR = './out/target/...
  • 保存Android未捕获到的Crash信息,保存到SD卡中并上传到服务器中。
  • Android Crash监控

    2020-11-07 18:12:11
    多么优秀,也无法避免 Crash 发生,特别是在 Android 系统中,系统碎片化严重、各 ROM 之间的差 异,甚至系统Bug,都可能会导致Crash的发生. package com.example.crash; import android.content.Context; import...

    Crash(应用崩溃)是由于代码异常而导致 App 非正常退出,导致应用程序无法继续使用,所有工作都
    停止的现象。发生 Crash 后需要重新启动应用(有些情况会自动重启),而且不管应用在开发阶段做得
    多么优秀,也无法避免 Crash 发生,特别是在 Android 系统中,系统碎片化严重、各 ROM 之间的差
    异,甚至系统Bug,都可能会导致Crash的发生.

    package com.example.crash;
    
    import android.content.Context;
    import android.content.pm.PackageInfo;
    import android.content.pm.PackageManager;
    import android.os.Build;
    import android.os.Looper;
    import android.util.Log;
    import android.widget.Toast;
    
    import androidx.annotation.NonNull;
    
    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class CrashHandler implements Thread.UncaughtExceptionHandler {
    
        //文件后缀名
        public static final String FILE_NAME_SUFFIX = ".trace";
    
        private static Thread.UncaughtExceptionHandler mDefalutCrashHandler;
    
        private static Context context;
    
        private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
    
        private static class Singleton{
            public static final CrashHandler INSTNCE=new CrashHandler();
        }
    
        public CrashHandler() {
        }
    
        public static CrashHandler getInstance(){
            return Singleton.INSTNCE;
        }
    
    
        public void init(@NonNull Context context) {
            //默认为 RuntimeInit#KillApplicationHandler
            mDefalutCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
            Thread.setDefaultUncaughtExceptionHandler(this);
            this.context=context.getApplicationContext();
    
        }
    
        @Override
        public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
            Log.e("filesDir","uncaughtException +++++++++++++++++++++++++++++++++++++ ");
            try {
    
                new Thread() {
                    @Override
                    public void run() {
                        Looper.prepare();
                        Toast.makeText(context, "很抱歉,程序出现异常,正在收集日志,即将退出", Toast.LENGTH_LONG)
                                .show();
                        Looper.loop();
                    }
                }.start();
                //自行处理
                File file = dealException(e, t);
    
                //上传服务器
    
            } catch (Exception e1) {
                e1.printStackTrace();
            } finally {
                //交给系统默认程序处理
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e1) {
                    Log.e("filesDir", "error : ", e1);
                }
                // 退出程序
                if (mDefalutCrashHandler != null) {
                    mDefalutCrashHandler.uncaughtException(t, e);
                }
            }
        }
    
        /**
         * 到处异常处理消息到SD卡
         *
         * @param e
         * @param t
         * @return
         */
        private File dealException(Throwable e, Thread t) {
    
            String time = dateFormat.format(new Date());
    
           // File file = new File(context.getExternalCacheDir().getAbsoluteFile(), "crash_info");
            File file = new File(context.getCacheDir().getAbsoluteFile(), "crash_info");
            if (!file.exists()) {
                file.mkdirs();
            }
            File crashFile = new File(file, time + FILE_NAME_SUFFIX);
    
            //往文件中写入数据
            PrintWriter pw=null;
            try {
                pw = new PrintWriter(new BufferedWriter(new FileWriter(crashFile)));
                pw.println(time);
                pw.println("thread : " + t.getName());
                pw.println(getPhoneInfo());
                e.printStackTrace(pw);
    
            } catch (IOException ex) {
                ex.printStackTrace();
            } catch (PackageManager.NameNotFoundException ex) {
                ex.printStackTrace();
            } finally {
                if (null!=pw){
                pw.flush();
                pw.close();
                }
            }
    
            return crashFile;
        }
    
        private String getPhoneInfo() throws PackageManager.NameNotFoundException {
    
            PackageManager pm=context.getPackageManager();
            PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
    
            StringBuilder builder=new StringBuilder();
    
            //App版本号
            builder.append("APP Version :");
            builder.append(packageInfo.versionName);
            builder.append("_");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                builder.append(packageInfo.getLongVersionCode());
            }else {
                builder.append(packageInfo.versionCode);
            }
            builder.append("\n");
    
            //Android版本号
             builder.append("OS version :");
             builder.append(Build.VERSION.RELEASE);
             builder.append("_");
             builder.append(Build.VERSION.SDK_INT);
             builder.append("\n");
    
             //手机制造商
            builder.append("Vendor : ");
            builder.append(Build.MANUFACTURER);
            builder.append("\n");
    
            //手机型号
            builder.append("Model : ");
            builder.append(Build.MODEL);
            builder.append("\n");
    
    
    
          return builder.toString();
        }
    }
    

    直接在application中调用

    public class MyApplication extends Application {
        private static MyApplication instance;
       
    
        public static MyApplication getInstance() {
            return instance;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            instance = this;
    
            CrashHandler.getInstance().init(this);
    
           
        }

     

    展开全文
  • Android crash 收集

    2020-12-19 18:38:53
    Android Crash在开发中,会遇到crash问题,一般来说,crash发生在java层,但是,有时候会发生在其他层面上。大致,Android Crash 大致有三类:Java uncatch exceptionANR crashNative crashJava全局异常处理通过...

    Android Crash

    在开发中,会遇到crash问题,一般来说,crash发生在java层,但是,有时候会发生在其他层面上。大致,Android Crash 大致有三类:

    Java uncatch exception

    ANR crash

    Native crash

    Java全局异常处理

    通过Thread.setDefaultUncaughtExceptionHandler我们可以指定一个默认的全局异常处理器,该处理器由JVM发现UNCATCH EXCEPTION 的时候调用

    public class Main {

    public static void main(String args[]){

    Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {

    @Override

    public void uncaughtException(Thread t, Throwable e) {

    System.out.println("FFF");

    }

    });

    new Thread(new Runnable() {

    @Override

    public void run() {

    throw new RuntimeException("CC");

    }

    }).start();

    }

    }

    堆栈图:

    堆栈的ROOT方法:

    /**

    * Dispatch an uncaught exception to the handler. This method is

    * intended to be called only by the JVM.

    */

    private void dispatchUncaughtException(Throwable e) {

    getUncaughtExceptionHandler().uncaughtException(this, e);

    }

    ANR crash

    ANR 产生的原因主要是UI线程被卡住太长时间了,其大部分是因为网络访问引起的,幸好4.0以后,就不支持在UI线程访问网络了。

    ANR 一旦发生,通常会在Logcat中刷出问题,但是,如果该APP已经发布了,就无法通过logcat查看到崩溃日志了。

    幸好,我们可以通过**/data/anr/traces.txt** 来读取最后一次ANR日志。 具体可以参考ANR文章。

    Native crash

    因为性能的问题,Android中的游戏开发等,通常使用native(C语言)开发,因为脱离了JVM环境,所以一旦发生crash,错误就比较难以定位,我们需要借助操作系统的力量进行crash日志收集。

    DEBUG

    如果正在开发APP中,及时发现问题,那么可以通过 ndk-stack 来查看崩溃位置ndk-stack使用

    RELEASE

    麻烦的问题就是在于,如果APP发布了,就不能通过logcat来获取崩溃日志了。此时,只能借助于Android依赖的系统来完成日志捕获的功能。大部分Android系统是依赖Linux系统,所以,只用通过Linux对原生程序崩溃的处理方法,就可以完成日志的捕获。

    在Linux 中,通常采用信号来捕获各种异常状态的,所以只需要注册一个我们的信号量监听器就可以完成对崩溃事件的监听。然后,利用参数info可以获取崩溃stack位置,最后通过回溯stack,就可以打印出崩溃点的C栈了。

    具体可以参考善用backtrace解决大问题,也可以直接采用 **google breakpad **开源项目解决这个问题。

    总结

    经过上述分析,可以发现Android平台的崩溃点还是比较多的,对于ANR和Native crash 的日志收集还是比较困难的。 通过TX Bugly 可以直接Hold住这些问题,看介绍是这样子的。对于TX Bugly的原理,个人认为应该和本文描述类似,希望有大牛能解密。

    对于应用的crash情况,相信各个平台都有相对应的一套机制来帮助程序员定位异常点。

    展开全文
  • 应用程序crash在开发过程中还是很常见的,本文主要是从源码的角度去跟踪下Android对于crash的处理流程。App crash的全称:Application crash。而Crash又分为:native crash和framework crash(包含App Crash)。我们...

    应用程序crash在开发过程中还是很常见的,本文主要是从源码的角度去跟踪下Android对于crash的处理流程。App crash的全称:Application crash。而Crash又分为:native crash和framework crash(包含App Crash)。我们在平时开发的时候对于可能有异常的地方,一般都是用try-catch语句去catch 异常信息,但当没有有效的catch住的时候,就会导致应用crash,此时系统就会进行捕获,并进入到crash的流程。

    《从源码角度看Android系统Zygote进程启动过程》一文中可知:system_server进程和上层应用程序的进程都是Zygote进程fork来的,而在这些进程创建的时候会设置未捕获异常的处理器,当系统中有未捕获的异常时候,就会交给异常器去处理。

    无论是system_server进程还是应用程序进程都会在创建的过程中调用RuntimeInit.java的commonInit方法去做一些通用的初始化操作,其中就有设置默认的未捕捉异常处理器UncaughtHandler。

    备注:本文将结合Android6.0的源码深入理解Android Crash处理流程

    1. Android Crash 处理流程

    下面以RuntimeInit.java的commonInit方法为起点对Crash流程进行跟踪分析。

    1.1 RuntimeInit.java—>commonInit方法

    代码路径:frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

    深入到commonInit的方法中:

    private static final void commonInit() {
        if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
    
    	//设置默认的未捕获异常处理器
        Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler()); //注释1
    
    	...省略...
    }
    

    注释解析:

    • 注释1处就是给要创建的进程设置未捕获异常处理器。setDefaultUncaughtExceptionHandler是将异常处理器的handler对象赋给Thread的成员变量。

    下面继续跟踪看UncaughtHandler对象实例化的过程!!!

    1.2 RuntimeInit.java—>静态内部类UncaughtHandler

    代码路径:frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

    深入到静态内部类UncaughtHandler中:

    private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
        public void uncaughtException(Thread t, Throwable e) {
            try {
                //确保crash过程不会重复进入
                if (mCrashing) return;
                mCrashing = true;
    
    			//判断是否为系统进程
                if (mApplicationObject == null) {
    				//系统进程
                    Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e); //注释1
                } else {
    				//普通进程
    				// 注释2
                    StringBuilder message = new StringBuilder();
                    message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
                    final String processName = ActivityThread.currentProcessName();
                    if (processName != null) {
                        message.append("Process: ").append(processName).append(", ");
                    }
                    message.append("PID: ").append(Process.myPid());
                    Clog_e(TAG, message.toString(), e);
                }
    
                // 注释3
                ActivityManagerNative.getDefault().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
            } catch (Throwable t2) {
                try {
                    Clog_e(TAG, "Error reporting crash", t2);
                } catch (Throwable t3) {
                    // Even Clog_e() fails!  Oh well.
                }
            } finally {
                // 注释4
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }
    }
    

    注释解析:

    • 注释1处当是系统进程Crash信息时:

      1. 开头是: *** FATAL EXCEPTION IN SYSTEM PROCESS:[线程名]

      2. 输出发生Crash时的调用栈信息

    • 注释2处当是普通应用进程时:

      1. 开头是:FATAL EXCEPTION:[线程名]

      2. 然后是:Process:[进程名],PID:[进程id]

      3. 输出发生Crash时的调用栈信息

    • 注释3处是启动Crash的弹框。而ActivityManagerNative.getDefault()返回的是ActivityManagerProxy(简称AMP),AMP经过binder调用最后会交给ActivityManagerService(简称AMS),即Crash的弹框最终调用的是AMS的handleApplicationCrash方法。其中传入的参数new ApplicationErrorReport.CrashInfo(e)的作用是将crash信息文件名、类名、方法名、对应行号和异常信息封装到CrashInfo对象中。

    • 注释4处彻底杀掉当前进程

    备注:

    在平时看Crash 的log时,如果是普通进程,我们只需要搜索关键词“FATAL EXCEPTION:”;如果是系统进程,则需要搜索关键词“*** FATAL EXCEPTION IN SYSTEM PROCESS”

    1.3 AMS—>handleApplicationCrash方法

    代码路径:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    深入到handleApplicationCrash方法中:

    public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfo crashInfo) {
    	// 注释1
        ProcessRecord r = findAppProcess(app, "Crash");
    	// 注释2
        final String processName = app == null ? "system_server"
                : (r == null ? "unknown" : r.processName);
    	// 注释3
        handleApplicationCrashInner("crash", r, processName, crashInfo);
    }
    

    注释解析:

    • 注释1处获取进程的ProcessRecord对象

    • 注释2处获取进程名

      1. 当远程IBinder对象为空时,则进程名为system_server

      2. 当远程IBinder对象不为空时,并且ProcessRecord为空时,则进程名为unknown

      3. 当远程IBinder对象不为空时,并且ProcessRecord不为空时,则进程名为ProcessRecord对象中相应的进程名

    • 注释3处继续调用内部方法handleApplicationCrashInner

    1.4 AMS—>handleApplicationCrashInner方法

    代码路径:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    深入到handleApplicationCrashInner方法中:

    void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
            ApplicationErrorReport.CrashInfo crashInfo) {
    	// 注释1
        EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(),
                UserHandle.getUserId(Binder.getCallingUid()), processName,
                r == null ? -1 : r.info.flags,
                crashInfo.exceptionClassName,
                crashInfo.exceptionMessage,
                crashInfo.throwFileName,
                crashInfo.throwLineNumber);
    	// 注释2
        addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
    	// 注释3
        crashApplication(r, crashInfo);
    }
    

    注释解析:

    • 注释1处将Crash信息写入到Event Log中

    • 注释2处将错误信息添加到DropBox。其中dropbox的Crash信息是输出到/data/system/dropbox目录下,如果是system_server的crash,那么dropbox的文件名就是system_server_crash@xxx.txt(xxx是时间戳)

    • 注释3处继续调用内部方法crashApplication

    1.5 AMS—>crashApplication方法

    代码路径:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    深入到crashApplication方法中:

    private void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
        long timeMillis = System.currentTimeMillis();
        String shortMsg = crashInfo.exceptionClassName;
        String longMsg = crashInfo.exceptionMessage;
        String stackTrace = crashInfo.stackTrace;
        if (shortMsg != null && longMsg != null) {
            longMsg = shortMsg + ": " + longMsg;
        } else if (shortMsg != null) {
            longMsg = shortMsg;
        }
    
        AppErrorResult result = new AppErrorResult();
        synchronized (this) {
    
            ...省略...
    
            // 注释1
            if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace)) {
                Binder.restoreCallingIdentity(origId);
                return;
            }
    
            Message msg = Message.obtain();
            msg.what = SHOW_ERROR_MSG;
            HashMap data = new HashMap();
            data.put("result", result);
            data.put("app", r);
            msg.obj = data;
    		// 注释2
            mUiHandler.sendMessage(msg);
    
        }
    
        ...省略...
    }
    

    注释解析:

    • 注释1处调用makeAppCrashingLocked,继续处理crash流程

    • 注释2处发送SHOW_ERROR_MSG,弹出crash提示框,等待用户选择

    下面继续深入到makeAppCrashingLocked方法中!!!

    1.6 AMS—>makeAppCrashingLocked方法

    代码路径:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    深入到makeAppCrashingLocked方法中:

    private boolean makeAppCrashingLocked(ProcessRecord app,
            String shortMsg, String longMsg, String stackTrace) {
        app.crashing = true;
    	// 注释1
        app.crashingReport = generateProcessError(app,
                ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
    	// 注释2
        startAppProblemLocked(app);
    	// 注释3
        app.stopFreezingAllLocked();
    	// 注释4
        return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace);
    }
    

    注释解析:

    • 注释1处封装crash信息到crashingReport对象中

    • 注释2处调用startAppProblemLocked方法,主要功能是获取当前用户下crash应用的error receiver和忽略当前app的广播接受者

    • 注释3处调用ProcessRecord的stopFreezingAllLocked方法停止屏幕冻结

    • 注释4处调用handleAppCrashLocked方法继续处理crash流程

    其中注释1和注释2处的调用流程不是本文的重点,这里就不继续跟踪,大家如果有兴趣,可以自行阅读!后面我会给出一个函数调用链,可供参考。

    下面继续跟踪查看注释4处的handleAppCrashLocked方法。

    1.7 AMS—>handleAppCrashLocked方法

    代码路径:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    深入到handleAppCrashLocked方法中:

    private boolean handleAppCrashLocked(ProcessRecord app, String reason,
            String shortMsg, String longMsg, String stackTrace) {
        long now = SystemClock.uptimeMillis();
    
        Long crashTime;
        if (!app.isolated) {
            crashTime = mProcessCrashTimes.get(app.info.processName, app.uid);
        } else {
            crashTime = null;
        }
    	// 当同一个进程,连续两次crash的时间间隔小于1分钟时,则认为crash太过于频繁
        if (crashTime != null && now < crashTime+ProcessList.MIN_CRASH_INTERVAL) {
            Slog.w(TAG, "Process " + app.info.processName
                    + " has crashed too many times: killing!");
            EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
                    app.userId, app.info.processName, app.uid);
            mStackSupervisor.handleAppCrashLocked(app); //注释1
            if (!app.persistent) {
                EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid,
                        app.info.processName);
                if (!app.isolated) {
                    mBadProcesses.put(app.info.processName, app.uid,
                            new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
                    mProcessCrashTimes.remove(app.info.processName, app.uid);
                }
                app.bad = true;
                app.removed = true;
    			// 移除进程的所有服务
                removeProcessLocked(app, false, false, "crash"); //注释2
    			// 恢复最顶部的Activity
                mStackSupervisor.resumeTopActivitiesLocked(); //注释3
                return false;
            }
            mStackSupervisor.resumeTopActivitiesLocked();
        } else {
    		// 结束最顶端正在运行的Activity
            mStackSupervisor.finishTopRunningActivityLocked(app, reason);
        }
    
        // 运行在当前进程中的所有服务的crash次数执行加1操作
        for (int i=app.services.size()-1; i>=0; i--) {
            ServiceRecord sr = app.services.valueAt(i);
            sr.crashCount++;
        }
    
        ...省略...
    	// 当app存在crash的handler,那么交给其处理
        if (app.crashHandler != null) mHandler.post(app.crashHandler); //注释4
        return true;
    }
    

    注释解析:

    • 注释1处调用ActivityStackSupervisor的handleAppCrashLocked方法继续处理crash流程

    • 注释2处调用内部的removeProcessLocked方法,主要用于移除进程的所有服务

    • 注释3处调用ActivityStackSupervisor的resumeTopActivitiesLocked方法恢复最顶部的Activity

    • 注释4处判断crash的App内部是否有crashHandler,如果有,则交给App内部的crashHandler去处理。这里也证明了《屏蔽Crash 提示框的两种方式》一文中第二种方案使用crashHandler屏蔽crash提示框的可行性。

    逻辑分析流程图:

    在这里插入图片描述

    其中注释2和注释3的流程就不跟踪了,如果有兴趣,自行查阅。后面我会给出一个函数调用链,可供参考。

    下面跟踪看下注释1处的处理流程。

    1.8 ActivityStackSupervisor.java—>handleAppCrashLocked方法

    代码路径:frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

    深入到handleAppCrashLocked方法中:

    void handleAppCrashLocked(ProcessRecord app) {
        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
            int stackNdx = stacks.size() - 1;
            while (stackNdx >= 0) {
    			//调用ActivityStack的handleAppCrashLocked方法
                stacks.get(stackNdx).handleAppCrashLocked(app);
                stackNdx--;
            }
        }
    }
    

    继续深入到ActivityStack.java的handleAppCrashLocked方法中。

    代码路径:frameworks/base/services/core/java/com/android/server/am/ActivityStack.java

    深入到handleAppCrashLocked方法中:

    void handleAppCrashLocked(ProcessRecord app) {
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                final ActivityRecord r = activities.get(activityNdx);
                if (r.app == app) {
                    Slog.w(TAG, "  Force finishing activity "
                            + r.intent.getComponent().flattenToShortString());
                    // Force the destroy to skip right to removal.
                    r.app = null;
    				//结束当前Activity
                    finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false);
                }
            }
        }
    }
    

    到这里AMS中的crashApplication方法中调用的makeAppCrashingLocked方法的流程(上面1.5中的注释1)就跟踪完毕!!!

    下面继续看下AMS中的crashApplication方法中UiHandler(上面1.5中的注释2)

    1.9 AMS—>内部类UiHandler

    从上面1.5中的注释2处可知是通过mUiHandler发送message,且消息的msg.what=SHOW_ERROR_MSG,下面来看下mUiHandler中的handleMessage处理过程。

    代码路径:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    深入内部类UiHandler中:

    final class UiHandler extends Handler {
        public UiHandler() {
            super(com.android.server.UiThread.get().getLooper(), null, true);
        }
    
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case SHOW_ERROR_MSG: {
                HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                        Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
                synchronized (ActivityManagerService.this) {
                    ProcessRecord proc = (ProcessRecord)data.get("app");
                    AppErrorResult res = (AppErrorResult) data.get("result");
                    if (proc != null && proc.crashDialog != null) {
                        Slog.e(TAG, "App already has crash dialog: " + proc);
                        if (res != null) {
                            res.set(0);
                        }
                        return;
                    }
                    boolean isBackground = (UserHandle.getAppId(proc.uid)
                            >= Process.FIRST_APPLICATION_UID
                            && proc.pid != MY_PID);
                    for (int userId : mCurrentProfileIds) {
                        isBackground &= (proc.userId != userId);
                    }
                    if (isBackground && !showBackground) {
                        Slog.w(TAG, "Skipping crash dialog of " + proc + ": background");
                        if (res != null) {
                            res.set(0);
                        }
                        return;
                    }
                    if (mShowDialogs && !mSleeping && !mShuttingDown) {
    					//创建crash提示框,等待用户选择,等待时间为5分钟
                        Dialog d = new AppErrorDialog(mContext,
                                ActivityManagerService.this, res, proc);
                        d.show();
                        proc.crashDialog = d;
                    } else {
                        //当处于sleep状态,则默认选择退出
                        if (res != null) {
                            res.set(0);
                        }
                    }
                }
    
                ensureBootCompleted();
            } break;
    
    		...省略...
        }
    }
    

    所以在发生crash时,系统会默认弹出crash提示框,并阻塞等待用户选择,当用户不做任何选择时,5min后,默认选择退出。

    最后调用上面1.2中注释4的逻辑,用来杀掉Crash进程。

    到此Crash处理流程就跟踪完毕!!!

    2. Android Crash流程总结

    当进程抛出未捕获的异常时,系统就会处理该异常并进入crash处理流程。

    2.1 Crash处理流程图

    在这里插入图片描述

    在流程图中,最重要的是AMS.handleAppCrashLocked中的功能,里面分了如下两种情况进行处理:

    1. 当同一个进程在1分钟内连续两次crash,则执行如下逻辑

      • 非persistent进程

        • 执行ASS.handleAppCrashLock方法,结束该应用所有activity

        • 执行AMS.removeProcessLocked方法,杀死该进程和同一个进程组下的所有进程

        • 执行ASS.resumeTopActivitiesLocked方法,恢复栈顶第一个没有finishing状态的activity

      • persistent进程

        • 执行ASS.resumeTopActivitiesLocked方法,恢复栈顶第一个没有finishing状态的activity
    2. 如果没有连续crash,则执行如下逻辑

      • 执行ASS.finishTopRunningActivityLocked方法,结束栈顶正在运行activity
    2.2 Crash处理流程函数调用链
    AMP.handleApplicationCrash
        AMS.handleApplicationCrash
            AMS.findAppProcess
            AMS.handleApplicationCrashInner
                AMS.addErrorToDropBox
                AMS.crashApplication
                    AMS.makeAppCrashingLocked
                        AMS.startAppProblemLocked
                        ProcessRecord.stopFreezingAllLocked
                            ActivityRecord.stopFreezingScreenLocked
                                WMS.stopFreezingScreenLocked
                                    WMS.stopFreezingDisplayLocked
    					AMS.handleAppCrashLocked
    					   ASS.handleAppCrashLocked
    						   AS.handleAppCrashLocked
    							   AS.finishCurrentActivityLocked
    					   AMS.removeProcessLocked
    						   ProcessRecord.kill
    						   AMS.handleAppDiedLocked
    							   ASS.handleAppDiedLocked
    								   AMS.cleanUpApplicationRecordLocked
    								   AS.handleAppDiedLocked
    									   AS.removeHistoryRecordsForAppLocked
    					   ASS.resumeTopActivitiesLocked
    						   AS.resumeTopActivityLocked
    							   AS.resumeTopActivityInnerLocked
    					   ASS.finishTopRunningActivityLocked
    						   AS.finishTopRunningActivityLocked
    							   AS.finishActivityLocked
                    mUiHandler.sendMessage(SHOW_ERROR_MSG)
    
    Process.killProcess(Process.myPid());
    System.exit(10);
    

    其中缩写对应关系如下:

    AMP--->ActivityManagerProxy
    
    AMS--->ActivityManagerService
    
    WMS--->WindowManagerService
    
    ASS--->ActivityStackSupervisor
    
    AS--->ActivityStack
    
    非常感谢您的耐心阅读,希望我的文章对您有帮助。欢迎点评、转发或分享给您的朋友或技术群。
    展开全文
  • Android Crash详解

    2021-10-22 16:52:16
    目前我们知晓的Android客户端上会出现的三种导致APP无法使用的现象是Java崩溃,Native崩溃以及ANR。以下内容从三种错误展开,均建立在自己自行调研以及实践的基础上。 Java崩溃 Java崩溃就是在Java/kotlin代码中,...

    目前我们知晓的Android客户端上会出现的三种导致APP无法使用的原因有Java崩溃,Native崩溃以及ANR。以下内容从三种错误展开,建立在自行调研以及实践的基础上。

    Java崩溃

    Java崩溃就是在Java/kotlin代码中,出现了未捕获异常,导致程序异常退出。通常是由我们自己的业务代码导致,例如空指针,索引越界等常见的崩溃。Java的崩溃日志相对于Native和ANR的堆栈日志,阅读和定位难度为最低。一般在配合mapping文件反混淆之后都可以直接定位错误。

    java崩溃捕获 

    class JavaCrashHandler implements UncaughtExceptionHandler{
        
        //在初始化的时候
        void initialize(){
        ......
            Thread.setDefaultUncaughtExceptionHandler(this);
    
        }
    
         @Override
        public void uncaughtException(Thread thread, Throwable throwable) {
    
            //处理异常 读取信息 上传或者其他处理
            handleException(thread, throwable);
        }
    
    }

    这部分异常捕捉由于有现成的接口提供所以很容易。只需要大家处理好读取部分的逻辑就没什么大问题,上报的时候采集一些附带信息即可。

    java崩溃日志解析

    解析java日志,首先需要的是mapping.txt文件。Grade 3.4版本之前,使用Proguard工具,之后Android 在新版中启用了 R8 编译器,没有使用 Proguard 工具,虽然兼容 Proguard 的配置和字典等,但是编译出来的 Mapping 文件格式还是有一点不同。如果开启混淆功能,则会产生Mapping 文件,用来逆向推出原始的堆栈信息,更快更方便的定位问题。位置路径:build/output/mapping/release/mapping.txt

    或者

    build/output/mapping/${flavorDir}/release/mapping.txt

    大家可以自行查看mapping文件的内容,可以发现就是一张映射表,利用不同的字母组合代表指定的类或者方法等。如下图展示:

    用来解析崩溃日志的工具是sdk中自带的retrace。路径为:sdk/tools/proguard/bin/retrace.sh。解析命令为:

    retrace (mapping文件路径) (java crash文件路径)

    其中java日志的格式如下,大家获取日志的时候可以自行搜索retrace解析日志格式,随着版本升级应该会有变动。

    *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    
    其他字段信息....
    ....
    java stacktrace:
        ....
    
    其他信息...
    
    +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
    

    举例如:

    *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    Build fingerprint: 'samsung/dreamqltezc/dreamqltechn:9/PPR1.180610.011/G9500ZCU4DSH2:user/release-keys'
    ABI: 'arm64'
    
    java stacktrace:
    java.lang.IllegalStateException: Could not execute method for android:onClick
    	at d.b.c.t$a.onClick(:2)
    	at android.view.View.performClick(View.java:7352)
    	at android.widget.TextView.performClick(TextView.java:14177)
    	at com.google.android.material.button.MaterialButton.performClick(Unknown Source:3)
    	at android.view.View.performClickInternal(View.java:7318)
    	at android.view.View.access$3200(View.java:846)
    	at android.view.View$PerformClick.run(View.java:27800)
    	at android.os.Handler.handleCallback(Handler.java:873)
    	at android.os.Handler.dispatchMessage(Handler.java:99)
    	at android.os.Looper.loop(Looper.java:214)
    	at android.app.ActivityThread.main(ActivityThread.java:7050)
    	at java.lang.reflect.Method.invoke(Native Method)
    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)
    Caused by: java.lang.reflect.InvocationTargetException
    	at java.lang.reflect.Method.invoke(Native Method)
    	... 14 more
    Caused by: java.lang.RuntimeException: test java exception
    	at j.n.b(Unknown Source:20)
    	at com.chinapnr.postbev2.SecondActivity.testJavaCrashInMainThread_onClick(Unknown Source:1)
    	... 15 more
    +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
    

    通过使用上面的命令行解析之后的结果即替换掉了混淆的部分,比如上面的日志,就是替换了

    	at d.b.c.t$a.onClick(:2)
    

    这一行为未混淆时候的代码。结果如下:

    至此java崩溃的捕捉到解析就完成了。

    Native崩溃

     Native崩溃一般都 是因为在Native代码中访问非法地址,也可能是地址对⻬出现了问题,或者发生了程序主动abort,这些都会产生相应的 signal信号,导致程序异常退出。通常我们常见的几种信号大致如下:

    #define SIGHUP 1  // 终端连接结束时发出(不管正常或非正常)
    #define SIGINT 2  // 程序终止(例如Ctrl-C)
    #define SIGQUIT 3 // 程序退出(Ctrl-\)
    #define SIGILL 4 // 执行了非法指令,或者试图执行数据段,堆栈溢出
    #define SIGTRAP 5 // 断点时产生,由debugger使用
    #define SIGABRT 6 // 调用abort函数生成的信号,表示程序异常
    #define SIGIOT 6 // 同上,更全,IO异常也会发出
    #define SIGBUS 7 // 非法地址,包括内存地址对齐出错,比如访问一个4字节的整数, 但其地址不是4的倍数
    #define SIGFPE 8 // 计算错误,比如除0、溢出
    #define SIGKILL 9 // 强制结束程序,具有最高优先级,本信号不能被阻塞、处理和忽略
    #define SIGUSR1 10 // 未使用,保留
    #define SIGSEGV 11 // 非法内存操作,与SIGBUS不同,他是对合法地址的非法访问,比如访问没有读权限的内存,向没有写权限的地址写数据
    #define SIGUSR2 12 // 未使用,保留
    #define SIGPIPE 13 // 管道破裂,通常在进程间通信产生
    #define SIGALRM 14 // 定时信号,
    #define SIGTERM 15 // 结束程序,类似温和的SIGKILL,可被阻塞和处理。通常程序如果终止不了,才会尝试SIGKILL
    #define SIGSTKFLT 16  // 协处理器堆栈错误
    #define SIGCHLD 17 // 子进程结束时, 父进程会收到这个信号。
    #define SIGCONT 18 // 让一个停止的进程继续执行
    #define SIGSTOP 19 // 停止进程,本信号不能被阻塞,处理或忽略
    #define SIGTSTP 20 // 停止进程,但该信号可以被处理和忽略
    #define SIGTTIN 21 // 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号
    #define SIGTTOU 22 // 类似于SIGTTIN, 但在写终端时收到
    #define SIGURG 23 // 有紧急数据或out-of-band数据到达socket时产生
    #define SIGXCPU 24 // 超过CPU时间资源限制时发出
    #define SIGXFSZ 25 // 当进程企图扩大文件以至于超过文件大小资源限制
    #define SIGVTALRM 26 // 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
    #define SIGPROF 27 // 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间
    #define SIGWINCH 28 // 窗口大小改变时发出
    #define SIGIO 29 // 文件描述符准备就绪, 可以开始进行输入/输出操作
    #define SIGPOLL SIGIO // 同上,别称
    #define SIGPWR 30 // 电源异常
    #define SIGSYS 31 // 非法的系统调用

    Native崩溃捕获 

    当一个动态库(native 程序)开始执行时,系统会注册一些连接到 debuggerd 的 signal handlers,当系统 crash 的时候,会保存一个 tombstone 文件到/data/tombstones目录下(Logcat中也会有相应的信息),文件就像墓碑一样记录了死亡了的进程的基本信息(例如进程的进程号,线程号),死亡的地址(在哪个地址上发生了 Crash),死亡时的现场是什么样的(记录了一系列的堆栈调用信息)等。

    大致的流程步骤如下:

    • 当Native进程发生了异常,比如NULL指针
    • 操作系统会去异常向量表的地址去处理异常,然后发送信号
    • 在debuggred_init注册的信号处理函数就会收到处理
    • 创建伪线程去启动crash_dump进程,crash_dump则会获取当前进程中各个线程的crash信息
    • tombstoned进程是开机就启动的,开机时注册好了socket等待监听
    • 当在crash_dump中去连接tombstoned进程的时候,根据传递的dump_type类型会返回一个/data/tombstones/下文件描述符
    • crash_dump进程后续通过engrave_tombstone函数将所有的线程的详细信息写入到tombstone文件中
    • 在/data/tombstones下生成了此次对应的tombstone_XX文件

    Native崩溃的捕捉重点就在于在C层替换信号处理函数,安装信号,进行信号处理,然后通过ptrace技术来获取线程的regs,backtrace等信息。以下的处理方案来源于Xcrash开源库的分析,大家可以去看源码。

    1、java层
         加载libscrash.so, nativeInit调用进行native层的初始化。
    2、native层
         nativeInit() 所映射的 jni 实现是 xc_jni_init()。xc_jni_init分3小步初始化:
         1)xc_common_init:初始化公共参数,初始化两个文件fd(非负整数,索引值,指向内核为每一个进程所维护的该进程打开文件的记录)。
         2)xc_crash_init:xc_crash_init_callback初始化 jni call back。初始化Ntaive线程通过eventfd(进程或者线程间的通信(如通知/等待机制的实现))阻塞等待native发生crash向上层java发出通知。
        

    Native崩溃解析

    同java崩溃一样,Native崩溃也需要一个映射文件和工具。Native的映射文件为带有调试符号信息的so包。

    一个完整的 so 由C代码加一些 debug 信息组成,这些debug信息会记录 so 中所有方法的对照表,就是方法名和其偏移地址的对应表,也叫做符号表,这种 so 也是未 strip 的,通常体积会比较大。

    IDE如果使用Android Sutdio+NDK,即项目中存在cpp项目,则在每次编译之后会生成对应的debug so文件(需保持最新最后一次编译产物),会按照对应的CPU指令架构集分类,较低版本的gradle插件使用ndk build的话,可能存在于如下路径(由于版本等,可能存在于其他路径,开发自行查找):

     如果gradle4.0以上和使用Cmake打包so文件的生成路径,开发者需要上传的就是下图路径下对应环境下的obj/下的内容:

     解析native崩溃信息。多了一步前提,需要一个匹配条件:cpu 架构指令集类型。即崩溃日志中统计到的abi的类型。

    //日志中信息
    ABI: 'arm64'
    
    //abi对应debug so所在文件夹名称
    arm64===》amr64-v8a
    armeabi===》armeabi
    armeabi-v7a===》armeabi-v7a
    x86===》x86
    x86_64===》x86_64
    
    

    确认好native对应崩溃的abi平台,之后可以进行对应的debug so包调用和解析,具体使用是使用sdkndk包中的工具,命令结构如下:

    工具:/sdk/ndk/21.1.6352462/ndk-stack
    
          ndk-stack: ndk-stack -sym (对应abi下面的符号表文件路径) -dump (日志文件)
    
    工具:/sdk/ndk/21.1.6352462/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line
    
          addr2line: addr2line -C -f -e (对应abi下面的符号表so包文件路径)(日志中显示的地址)

     同样,Native的崩溃日志格式也有要求:

    *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    Build fingerprint: 'samsung/dreamqltezc/dreamqltechn:9/PPR1.180610.011/G9500ZCU4DSH2:user/release-keys'
    ABI: 'arm64'
    .....报错信息等
    
    backtrace:
        内容
    

    使用上面的ndk-stack工具跑命令行解析输出结果如下:

    使用addr2line工具命令行解析单个地址输出结果如下:

     至此,Native的崩溃日志解析完成啦~

    ANR

    遇上ANR,一般都是个耗时的事儿。需要先学会如何看ANR和定位。Traces.txt系统自动生成的记录anr等异常的文件,只记录java代码产生的异常。我们通过使用adb工具USB连接手机在终端是可以导出系统生成的文件:

    adb bugreport

    导出的文件目录如下:

    这是我们在程序之外的操作,下面大致讲述一下如果在线上APP中捕获ANR。

    ANR的捕获 

    Android 7.0(可能是6.0)之前,可以通过监听 /data/anr 目录的变化。获取系统生成好的ANR日志。

    fileObserver = new FileObserver("/data/anr/", CLOSE_WRITE) {
                public void onEvent(int event, String path) {
                    try {
                        if (path != null) {
                            String filepath = "/data/anr/" + path;
                            if (filepath.contains("trace")) {
                                handleAnr(filepath);
                            }
                        }
                    } catch (Exception e) {
                        XCrash.getLogger().e(Util.TAG, "AnrHandler fileObserver onEvent failed", e);
                    }
                }
            };
    
            try {
                fileObserver.startWatching();
            } catch (Exception e) {
                fileObserver = null;
                XCrash.getLogger().e(Util.TAG, "AnrHandler fileObserver startWatching failed", e);
            }

    高版本的 Android 系统中,应用已经访问不到 /data/anr 了。C层面的方案就是捕获了 SIGQUIT 信号,这个是 Android App 发生 ANR 时由 ActivityMangerService 向 App 发送的信号。和处理Native Crash是一样的原理。大家可以去详细学习Xcrash的源码思路

    ANR部分主要还是Trace文件的详解,这个大家可以去自行搜索相关文档,很多大佬已经将如何一步一步定位ANR总结出来了。我这里就不做啰嗦啦,如何设计到解析,原理也同Native的崩溃日志解析一致。

    展开全文
  • Android Crash 监控

    2019-09-23 15:31:31
    发生 Crash 后需要重新启动应用(有些情况会自动重启),而且不管应用在开发阶段做得多么优秀,也无法避免 Crash 发生,特别是在 Android 系统中,系统碎片化严重、各 ROM 之间的差异,使 Android 在稳定性方面需要...
  • Android Crash类型程序员开发android应用程序时追逐的最终目标是要尽量避免程序Crash的发生。但是现实的情况是,在进行轮番测试和验证的时候,Android应用程序几乎不可能完全杜绝Crash的发生。Crash的类型主要有以下...
  • Crash率是衡量一个App好坏的重要指标之一。如果你忽略了它的存在,它就会得寸进尺,...通过团队的全力全策,美团外卖AndroidApp的平均Crash率从千分之三降到了万分之二,最优值万一左右(Crash率统计方式:Crash
  • Android Crash之Java Crash分析

    千次阅读 2016-05-28 00:10:21
    前言 小巫最近由于工作原因面临技术转型,从一个App开发者转变为SDK开发者,这两者的区别是非常明显的,从用户角度来讲,app开发主要面向...想了解更多内容,敬请关注下一篇『Android Crash之Native Crash分享』。
  • 本文将介绍Java异常及异常处理,以及Android的异常处理原理。 Android中异常(Crash)处理和捕获。 Android系统中Crash的处理、分发逻辑。 Crash优化建议。
  • Android Crash的防护与追踪

    千次阅读 2017-12-21 18:12:55
    Android系统中,抛出Exception 或者 Error都会导致Crash.进而导致App强制退出.简单的来说就是因为抛出异常的代码.并未被Try catch包围..就会导致进程被杀.二. 原理从Fork进程伊始,就已经存在的...
  • 深入理解Android Crash 流程

    千次阅读 2020-12-25 07:52:25
    和你一起终身学习,这里是程序员Android经典好文推荐,通过阅读本文,您将收获以下知识点:一、Crash 概述二、Crash处理流程三、handleApplicationCrash处理...
  • Android Crash 定位

    千次阅读 2015-11-19 13:40:26
    本文介绍了如何在 Android 手机发生 Crash 时进行 Log 分析的方法, 它可以帮助测试人员快速定位 Android 手机 Crash 发生的原因,同时给研发人员提供有效修改 Bug 的 Log 信息。用自动化测试工具对 Android 手机...
  • 崩溃日志的捕获有很多种方式,最直接的就是接入三方的捕获,但是由于某些原因或者说某些原因导致不能准确的定位到崩溃的位置,也为了使应用...import android.content.Context; import android.text.TextUtils; import a
  • android crash错误

    2018-01-31 15:40:03
    Android应用异常后崩溃不提示系统默认对话框,而显示一个友好的异常提示页面,用于向开发者反馈信息。该项目仅提供实现思路,实际项目中可以在此基础上进行扩展。
  • Android程序崩溃是一个严重的问题,但是原因大多情况下比较明了,搜索一下 Fatal,Crash 关键字就可以很快捷的定位到原因。 如果是fwk引起的崩溃(比如jar包通过反射调用一个不存在的接口),上述两个关键字会搜索...
  • Android Crash处理流程分析

    千次阅读 2017-05-17 11:14:34
    Androidcrash主要有3种,java层的force close,native层的crash和ANR。检查这三种crash的log方法也不相同:分别搜索“FATAL EXCEPTION”, “fault addr”和”ANR”。这三种crash的处理流程有不同,也有很多共性,...
  • 使用objdump进行Android crash 日志 分析

    千次阅读 2018-06-21 17:46:12
    转载请注明出处:http://blog.csdn.net/xyang81/article/details/42319789 在Android开发中,程序Crash分三种情况:未捕获的异常、ANR(Application Not Responding)和闪退(NDK引发错误)。其中未捕获的异常根据...
  • 首先,我们肯定要在Application里面注册一个CrashHandler,监听应用crash public class TestApplication extends MultiDexApplication { private static TestApplication mInstance; @Override public void ...
  • Android Crash 监控方案

    2021-10-20 10:25:12
    Crash 监控方案 ...import android.content.Context; import java.io.File; public class CrashReport { public static void init(Context context) { Context applicationContext = context.getApplicat
  • android crash定位以及捕获的源代码

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 37,050
精华内容 14,820
关键字:

androidcrash

友情链接: S3C44B0XVxWorksBSPNOTE.rar