简单分析App进程Crash机制

结论:App进程Crash,不是真正意义上的进程崩溃(对比native代码崩溃),是java代码运行抛出没人处理的异常后,App自己把自己Kill掉了。

工作中遇到后台Service挂掉后(弹出停止运行),很久没有重启,分析log发现进程抛出FATAL EXCEPTION后并没有被杀,很久后才被杀掉重启,迷惑,遂看看具体的App挂掉流程是什么样的。

表象

当一个Android App进程因为各种原因抛出异常而没有被catch处理的时候,在用户看来,就会看到一个“某某已停止运行”的对话框,之前我一般认为该app进程已经挂掉。

实际上

以前在看到“某某已停止运行”时,一直认为对应进程也同时结束,没有仔细分析过整个App停止运行的机制,其实,停止运行对话框弹出的时候,进程还没有完全退出,真正的退出是进程将自己kill掉的时候。下面就记录下从App抛出没有catch的异常到该进程真正灰飞烟灭的整个过程。

App进程的创建

要分析一个app进程是怎么没的,先看看app进程是怎么来的。

关键代码

App进程创建流程:

App进程启动流程.png

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java


startResult = Process.start(entryPoint,

                        app.processName, uid, uid, gids, debugFlags, mountExternal,

                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,

                        app.info.dataDir, invokeWith, entryPointArgs);

frameworks/base/core/java/android/os/ZygoteProcess.java


//ZygoteState维护了与Zygote进程通过Socket的连接

    private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {

        Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");

        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {

            try {

                primaryZygoteState = ZygoteState.connect(mSocket);

            } catch (IOException ioe) {

                throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);

            }

        }

        if (primaryZygoteState.matches(abi)) {

            return primaryZygoteState;

        }

        // The primary zygote didn't match. Try the secondary.

        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {

            try {

                secondaryZygoteState = ZygoteState.connect(mSecondarySocket);

            } catch (IOException ioe) {

                throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);

            }

        }

        if (secondaryZygoteState.matches(abi)) {

            return secondaryZygoteState;

        }

        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);

    }







    private static Process.ProcessStartResult zygoteSendArgsAndGetResult(

            ZygoteState zygoteState, ArrayList<String> args)

            throws ZygoteStartFailedEx {

        try {

            // Throw early if any of the arguments are malformed. This means we can

            // avoid writing a partial response to the zygote.

            int sz = args.size();

            for (int i = 0; i < sz; i++) {

                if (args.get(i).indexOf('\n') >= 0) {

                    throw new ZygoteStartFailedEx("embedded newlines not allowed");

                }

            }

            /**

            * See com.android.internal.os.SystemZygoteInit.readArgumentList()

            * Presently the wire format to the zygote process is:

            * a) a count of arguments (argc, in essence)

            * b) a number of newline-separated argument strings equal to count

            *

            * After the zygote process reads these it will write the pid of

            * the child or -1 on failure, followed by boolean to

            * indicate whether a wrapper process was used.

            */

            final BufferedWriter writer = zygoteState.writer;

            final DataInputStream inputStream = zygoteState.inputStream;

            writer.write(Integer.toString(args.size()));

            writer.newLine();

            for (int i = 0; i < sz; i++) {

                String arg = args.get(i);

                writer.write(arg);

                writer.newLine();

            }

            writer.flush();

            // Should there be a timeout on this?

            Process.ProcessStartResult result = new Process.ProcessStartResult();

            // Always read the entire result from the input stream to avoid leaving

            // bytes in the stream for future process starts to accidentally stumble

            // upon.

            result.pid = inputStream.readInt();

            result.usingWrapper = inputStream.readBoolean();

            if (result.pid < 0) {

                throw new ZygoteStartFailedEx("fork() failed");

            }

            return result;

        } catch (IOException ex) {

            zygoteState.close();

            throw new ZygoteStartFailedEx(ex);

        }

    }

zygoteSendArgsAndGetResult方法通过LocalSocket发送的命令被Zygote接收到:

frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java


pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,

                parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,

                parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,

                parsedArgs.appDataDir);

此处fork出真正的app进程,然后在fork出的子进程中执行命令:


ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,

                    null /* classLoader */);

执行的命令:

最终会从ActivityThread.java 的main函数进入,开始App的生命周期

RuntimeInit.commonInit()

上面流程中,App进程fork出来后,执行此函数:

RuntimeInit.commonInit()

其中:


    Thread.setUncaughtExceptionPreHandler(new LoggingHandler());

    Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler());


    /**

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

    * intended to be called only by the runtime and by tests.

    *

    * @hide

    */

    // @VisibleForTesting (would be private if not for tests)

    public final void dispatchUncaughtException(Throwable e) {

        Thread.UncaughtExceptionHandler initialUeh =

                Thread.getUncaughtExceptionPreHandler();

        if (initialUeh != null) {

            try {

                initialUeh.uncaughtException(this, e);

            } catch (RuntimeException | Error ignored) {

                // Throwables thrown by the initial handler are ignored

            }

        }

        getUncaughtExceptionHandler().uncaughtException(this, e);

    }

setUncaughtExceptionPreHandler设置“未捕获异常预处理程序”为loggingHandler,setDefaultUncaughtExceptionHandler设置真正的“未捕获异常默认处理程序”为KillApplicationHandler,按字面意思以及函数dispatchUncaughtException理解,发生异常时,先调用loggingHandler处理异常,再调用KillApplicationHandler处理。loggingHandler就是用来打印FATAL EXCEPTION以及trace的:

E AndroidRuntime: FATAL EXCEPTION: main

KillApplicationHandler:


    /**

    * Handle application death from an uncaught exception.  The framework

    * catches these for the main threads, so this should only matter for

    * threads created by applications.  Before this method runs,

    * {@link LoggingHandler} will already have logged details.

    */

    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {

        public void uncaughtException(Thread t, Throwable e) {

            try {

                // Don't re-enter -- avoid infinite loops if crash-reporting crashes.

                if (mCrashing) return;

                mCrashing = true;

                // Try to end profiling. If a profiler is running at this point, and we kill the

                // process (below), the in-memory buffer will be lost. So try to stop, which will

                // flush the buffer. (This makes method trace profiling useful to debug crashes.)

                if (ActivityThread.currentActivityThread() != null) {

                    ActivityThread.currentActivityThread().stopProfiling();

                }

                final String processName = ActivityThread.currentProcessName();

                if (processName != null) {

                    if (Build.IS_USERDEBUG && processName.equals(SystemProperties.get("persist.debug.process")))  {

                        Log.w(TAG, "process: " + processName + " crash message is skip");

                        return;

                    }

                }

                // Bring up crash dialog, wait for it to be dismissed

                ActivityManager.getService().handleApplicationCrash(

                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));

            } catch (Throwable t2) {

                if (t2 instanceof DeadObjectException) {

                    // System process is dead; ignore

                } else {

                    try {

                        Clog_e(TAG, "Error reporting crash", t2);

                    } catch (Throwable t3) {

                        // Even Clog_e() fails!  Oh well.

                    }

                }

            } finally {

                // Try everything to make sure this process goes away.

                Process.killProcess(Process.myPid());

                System.exit(10);

            }

        }

    }

这里通过如下代码和ActivityManagerService交互弹出“停止运行”对话框,注意注释,对话框消失后才会继续往下执行。


// Bring up crash dialog, wait for it to be dismissed

ActivityManager.getService().handleApplicationCrash(

                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));

在ActivityManagerService,最终会停在如下代码处:

AppErrors.java crashApplicationInner():


synchronized (mService) {

            /**

            * If crash is handled by instance of {@link android.app.IActivityController},

            * finish now and don't show the app error dialog.

            */

            if (handleAppCrashInActivityController(r, crashInfo, shortMsg, longMsg, stackTrace,

                    timeMillis, callingPid, callingUid)) {

                return;

            }

            /**

            * If this process was running instrumentation, finish now - it will be handled in

            * {@link ActivityManagerService#handleAppDiedLocked}.

            */

            if (r != null && r.instr != null) {

                return;

            }

            // Log crash in battery stats.

            if (r != null) {

                mService.mBatteryStatsService.noteProcessCrash(r.processName, r.uid);

            }

            AppErrorDialog.Data data = new AppErrorDialog.Data();

            data.result = result;

            data.proc = r;

            // If we can't identify the process or it's already exceeded its crash quota,

            // quit right away without showing a crash dialog.

            if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, data)) {

                return;

            }

            final Message msg = Message.obtain();

            msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;

            task = data.task;

            msg.obj = data;

            mService.mUiHandler.sendMessage(msg);

        }

        int res = result.get();

result为AppErrorResult类型,result.get()会wait(),block当前Binder调用,等待对应的notify;前面的代码就是弹出“停止运行”的对话框:AppErrorDialog,result会随data传入AppErrorDialog,dismiss时调用result.set(),唤醒刚才Binder线程的wait:

AppErrorResult


final class AppErrorResult {

    public void set(int res) {

        synchronized (this) {

            mHasResult = true;

            mResult = res;

            notifyAll();

        }

    }

    public int get() {

        synchronized (this) {

            while (!mHasResult) {

                try {

                    wait();

                } catch (InterruptedException e) {

                }

            }

        }

        return mResult;

    }

    boolean mHasResult = false;

    int mResult;

}

然后进行后面的处理Binder调用返回后,App进程中才最终会杀死自己:


finally {

    // Try everything to make sure this process goes away.

    Process.killProcess(Process.myPid());

    System.exit(10);

}

注意到,在AppErrorDialog构造函数中:


// After the timeout, pretend the user clicked the quit button

mHandler.sendMessageDelayed(

        mHandler.obtainMessage(TIMEOUT),

        DISMISS_TIMEOUT)

如果用户一直没有理睬,会在5分钟后返回,可以注意如下log:


Slog.w(TAG, "handleApplicationStrictModeViolation; res=" + res);

在超时后才返回,就会导致app进程在crash状态下存在5分钟之久,除了异常的线程,其他线程还会努力工作,有可能会有些奇怪的事情发生。应该挂掉重启的,由于进程没有被杀死,ActivityManagerService收不到binderDied消息,也会在超时之前一直得不到重启。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容