Android8.0系统对异常的处理流程

本文详细解析了Android8.0系统中的异常处理流程,包括未捕获异常的处理机制、crash对话框的显示逻辑及用户交互过程,以及进程crash后的清理工作。

Android8.0 系统异常处理流程

异常处理流程

Java处理未捕获异常有个Thread.UncaughtExceptionHandler,在Android系统中当然也是通过实现其来进行未捕获异常处理。

Android 默认系统异常处理是在启动SystemServer进程时设置的。

Zygote进程启动SystemServer时会调用ZygoteInit的forkSystemServer()方法,该方法中又通过handleSystemServerProcess()方法来对SystemServer进程做一些处理,最后会调用到RuntimeInit.commonInit()方法

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

protected static final void commonInit() {
    Thread.setUncaughtExceptionPreHandler(new LoggingHandler());
    // 该出就设置了默认未捕获异常的处理Handler-KillApplicationHandler
    Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler());
   ...
}

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

KillApplicationHandler代码如下

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

private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
    public void uncaughtException(Thread t, Throwable e) {
        try {
            ...
            // 1. mApplicationObject标识当前应用
            ActivityManager.getService().handleApplicationCrash(
                    mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
        } ...
        finally {
            // 无论如何都要保证出现crash的进程不存活
            Process.killProcess(Process.myPid());
            System.exit(10);
        }
    }
}

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

注释1处的ActivityManager.getService()得到的就是ActivityManagerService的服务端代理对象,实现是通过Binder机制。看看AMS在handleApplicationCrash方法中是如何处理的

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

public void handleApplicationCrash(IBinder app,
        ApplicationErrorReport.ParcelableCrashInfo crashInfo) {
    ProcessRecord r = findAppProcess(app, "Crash");
    final String processName = app == null ? "system_server"
            : (r == null ? "unknown" : r.processName);
<span class="token function">handleApplicationCrashInner</span><span class="token punctuation">(</span><span class="token string">"crash"</span><span class="token punctuation">,</span> r<span class="token punctuation">,</span> processName<span class="token punctuation">,</span> crashInfo<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
ApplicationErrorReport.CrashInfo crashInfo) {
// 1. 将crash信息写入event log中
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);

<span class="token function">addErrorToDropBox</span><span class="token punctuation">(</span>eventType<span class="token punctuation">,</span> r<span class="token punctuation">,</span> processName<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">,</span> crashInfo<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 2. </span>
mAppErrors<span class="token punctuation">.</span><span class="token function">crashApplication</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> crashInfo<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

注释1处将log记录在event log中。注释2处调用AppError的crashApplication方法

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

void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
    final int callingPid = Binder.getCallingPid();
    final int callingUid = Binder.getCallingUid();
<span class="token keyword">final</span> <span class="token keyword">long</span> origId <span class="token operator">=</span> Binder<span class="token punctuation">.</span><span class="token function">clearCallingIdentity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token comment">// 调用内部的crashApplicationInner</span>
    <span class="token function">crashApplicationInner</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> crashInfo<span class="token punctuation">,</span> callingPid<span class="token punctuation">,</span> callingUid<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
    Binder<span class="token punctuation">.</span><span class="token function">restoreCallingIdentity</span><span class="token punctuation">(</span>origId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

继续看crashApplicationInner方法

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

void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
        int callingPid, int callingUid) {
    ...
    synchronized (mService) {
        // 1. 处理有IActivityController的情况,如果Controller已经处理错误,则不会显示错误框
        if (handleAppCrashInActivityController(r, crashInfo, shortMsg, longMsg, stackTrace,
                timeMillis, callingPid, callingUid)) {
            return;
        }
        ...
        AppErrorDialog.Data data = new AppErrorDialog.Data();
        data.result = result;
        data.proc = r;
        ...
        // 2. 发送SHOW_ERROR_UI_MSG给AMS的mUiHandler,将弹出一个错误对话框,提示用户某进程crash
        final Message msg = Message.obtain();
        msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;
    task = data.task;
    msg.obj = data;
    mService.mUiHandler.sendMessage(msg);
}
// 3. 调用AppErrorResult的get方法,该方法内部调用了wait方法,故为阻塞状态,当用户处理了对话框后会调用AppErrorResult的set方法,该方法内部调用了notifyAll()方法来唤醒线程。
// 注意此处涉及了两个线程的工作,crashApplicationInner函数工作在Binder调用所在的线程;对话框工作于AMS的Ui线程

int res = result.get();

Intent appErrorIntent = null;
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_CRASH, res);
// 4. 判断用户操作结果,然后根据结果做不同处理
if (res == AppErrorDialog.TIMEOUT || res == AppErrorDialog.CANCEL) {
    res = AppErrorDialog.FORCE_QUIT;
}
synchronized (mService) {
    // 不在提示错误
    if (res == AppErrorDialog.MUTE) {
        stopReportingCrashesLocked(r);
    }
    // 尝试重启进程
    if (res == AppErrorDialog.RESTART) {
        mService.removeProcessLocked(r, false, true, "crash");
        if (task != null) {
            try {
                mService.startActivityFromRecents(task.taskId,
                        ActivityOptions.makeBasic().toBundle());
            } ...
        }
    }
    // 强行结束进程
    if (res == AppErrorDialog.FORCE_QUIT) {
        long orig = Binder.clearCallingIdentity();
        try {
            // Kill it with fire!
            mService.mStackSupervisor.handleAppCrashLocked(r);
            if (!r.persistent) {
                mService.removeProcessLocked(r, false, false, "crash");
                mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
            }
        } finally {
            Binder.restoreCallingIdentity(orig);
        }
    }
    // 停止进程并报告错误
    if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
        appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
    }
    ...
}

if (appErrorIntent != null) {
    try {
        // 启动报告错误界面
        mContext.startActivityAsUser(appErrorIntent, new UserHandle(r.userId));
    } catch (ActivityNotFoundException e) {
        Slog.w(TAG, "bug report receiver dissappeared", e);
    }
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

注释1会优先让crash观察者进行crash处理,crash观察者通过AMS的setActivityController()方法进行设置,如果已经处理则不会再弹出错误对话框。注释2会发送SHOW_ERROR_UI_MSG消息给AMS的mUIHandler处理来请求弹出错误对话框。注释3通过调用AppErrorResult中的get()方法来使线程阻塞。需要注意的是此处涉及到两个线程,crashApplicationInner工作在Binder调用所在的线程,对话框显示则处于AMS的UI线程。具体AppErrorResult的工作后面会说到。待用户操作对话框后或者超时时间到时get()方法就会被唤醒,并且返回处理结果。注释4则根据用户操作结果进行不同的处理,例如强制停止进程,重启进程等。

这里看下注释2处是如何显示错误对话框的,AMS的UiHandler接收到了消息就会进行显示操作

crash对话框的显示和用户行为

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

final class UiHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        // 显示错误对话框
        case SHOW_ERROR_UI_MSG: {
            mAppErrors.handleShowAppErrorUi(msg);
            ensureBootCompleted();
        } break;
        // 显示ANR对话框
        case SHOW_NOT_RESPONDING_UI_MSG: {
            mAppErrors.handleShowAnrUi(msg);
            ensureBootCompleted();
        } break;
        ...
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可以看到UiHandler对错误和ANR对话框显示的处理,这里看错误对话框的显示,其还是通过AppErrors类进行处理。

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

void handleShowAppErrorUi(Message msg) {
    ...
    synchronized (mService) {
        ProcessRecord proc = data.proc;
        AppErrorResult res = data.result;
        // 1. crash 对话框已显示,故无需再显示
        if (proc != null && proc.crashDialog != null) {
            if (res != null) {
                res.set(AppErrorDialog.ALREADY_SHOWING);
            }
            return;
        }
   <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
    <span class="token keyword">final</span> <span class="token keyword">boolean</span> crashSilenced <span class="token operator">=</span> mAppsNotReportingCrashes <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span>
            mAppsNotReportingCrashes<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>proc<span class="token punctuation">.</span>info<span class="token punctuation">.</span>packageName<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>mService<span class="token punctuation">.</span><span class="token function">canShowErrorDialogs</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">||</span> showBackground<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>crashSilenced<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 2. 创建crash对话框</span>
        proc<span class="token punctuation">.</span>crashDialog <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AppErrorDialog</span><span class="token punctuation">(</span>mContext<span class="token punctuation">,</span> mService<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token comment">// 3. 如果AMS禁止显示错误对话框,或者当前设备处于睡眠模式则不会让显示对话框</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>res <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            res<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>AppErrorDialog<span class="token punctuation">.</span>CANT_SHOW<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">// 4. 调用Dialog show方法显示crash对话框</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>proc<span class="token punctuation">.</span>crashDialog <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    data<span class="token punctuation">.</span>proc<span class="token punctuation">.</span>crashDialog<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

注释1先对crash进程是否已经显示对话框做了判断,如果已经显示则无需显示。注释2处,手机没有息屏,AMS也允许显示crash对话框,则创建对话框,否则走注释3处,直接说明不显示。如果走到注释4则需要显示crash对话框,故直接调用Dialog的show()方法。这里对注释1和注释3处的res.set()方法做以解释,这res就是AppErrorResult,也就是在crashApplicationInner方法中创建的,该方法在请求AMS显示对话框时调用了result.get()使其阻塞,调用set方法后则会唤醒Binder调用线程,接着走下面代码,进而对结果进行判断。

看下AppErrorResult get()和set()的实现

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

final class AppErrorResult {
    public void set(int res) {
        synchronized (this) {
            mHasResult = true;
            // 1. set方法设置mResult的值
            mResult = res;
            // 2.  调用notifyAll唤醒持有当前对象锁且处于阻塞状态的所有线程
            notifyAll();
        }
    }
<span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>mHasResult<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">try</span> <span class="token punctuation">{</span>
                <span class="token comment">//3. 实质通过wait()使当前线程阻塞</span>
                <span class="token function">wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 4. 返回mResult</span>
    <span class="token keyword">return</span> mResult<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">boolean</span> mHasResult <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token keyword">int</span> mResult<span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

通过get()方法线程阻塞,通过set方法更新mResult的值并唤醒处于等待队列的线程,此时接着get()方法wait后面的代码执行,将set()方法中更新的mResult值作为返回值。

当错误对话框弹出后,用户操作或者超时时间到时又是怎样的?我们一起看下AppErrorDialog

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

@Override
public void onClick(View v) {
    // 1. 判断点击控件,来决定操作
    switch (v.getId()) {
        // 请求重启进程
        case com.android.internal.R.id.aerr_restart:
            mHandler.obtainMessage(RESTART).sendToTarget();
            break;
        // 请求反馈报错问题
        case com.android.internal.R.id.aerr_report:
            mHandler.obtainMessage(FORCE_QUIT_AND_REPORT).sendToTarget();
            break;
        // 请求关闭crash Dialog并杀死进程
        case com.android.internal.R.id.aerr_close:
            mHandler.obtainMessage(FORCE_QUIT).sendToTarget();
            break;
        // 请求不再提示对话框
        case com.android.internal.R.id.aerr_mute:
            mHandler.obtainMessage(MUTE).sendToTarget();
            break;
        default:
            break;
    }
}

// 2. 受到请求信息后调用setResult()方法并关闭对话框
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
setResult(msg.what);
dismiss();
}
};
private void setResult(int result) {
synchronized (mService) {
if (mProc != null && mProc.crashDialog == AppErrorDialog.this) {
mProc.crashDialog = null;
}
}
// 3. 调用AppErrorResult的set方法使阻塞线程运行,并将用户点击结果告知
mResult.set(result);

mHandler<span class="token punctuation">.</span><span class="token function">removeMessages</span><span class="token punctuation">(</span>TIMEOUT<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

注释的步骤写的已经很清楚了,最终通过mResult.set()方法唤线程,是线程代码接着执行

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

void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
        int callingPid, int callingUid) {
    ...
    // 3. 阻塞线程直至超时或者用户操作对话框
    int res = result.get();
    // 4. 判断用户操作结果,然后根据结果做不同处理
    ...
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

后续清理工作

根据前面的流程,我们知道当进程crash后,最终将被kill掉,此时AMS还需要完成后续的清理工作。

我们先来回忆一下进程启动后,注册到AMS的部分流程

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

// 进程启动后,对应的ActivityThread会attach到AMS上
private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid) {
    ...
    final String processName = app.processName;
    try {
        // 1.  创建“讣告”接收者
        AppDeathRecipient adr = new AppDeathRecipient(
                app, pid, thread);
        thread.asBinder().linkToDeath(adr, 0);
        app.deathRecipient = adr;
    } 
    ...
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

当进程注册到AMS时,AMS注册了一个“讣告”接收者注册到进程中。
因此,当crash进程被kill后,AppDeathRecipient中的binderDied方法将被回调。看源码知道bindDied()方法中又会调用到appDiedLocked()方法

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

final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,
        boolean fromBinderDied) {
    ...
    // 1. 该进程没有杀死,则杀死进程
    if (!app.killed) {
        if (!fromBinderDied) {
            killProcessQuiet(pid);
        }
        killProcessGroup(app.uid, pid);
        app.killed = true;
    }
<span class="token keyword">if</span> <span class="token punctuation">(</span>app<span class="token punctuation">.</span>pid <span class="token operator">==</span> pid <span class="token operator">&amp;&amp;</span> app<span class="token punctuation">.</span>thread <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span>
        app<span class="token punctuation">.</span>thread<span class="token punctuation">.</span><span class="token function">asBinder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> thread<span class="token punctuation">.</span><span class="token function">asBinder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
    <span class="token comment">// 2. </span>
    <span class="token function">handleAppDiedLocked</span><span class="token punctuation">(</span>app<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token punctuation">}</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

注释1会将进程杀死,注释2处为app死亡的关键处理

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

private final void handleAppDiedLocked(ProcessRecord app,
        boolean restarting, boolean allowRestart) {
    int pid = app.pid;
    // 1. 进行进程中service、ContentProvider、BroadcastReceiver等的收尾工作
    boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1,
            false /*replacingPid*/);
    if (!kept && !restarting) {
        removeLruProcessLocked(app);
        if (pid > 0) {
            ProcessList.remove(pid);
        }
    }
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token comment">// 2. 判断是否还存在可见的Activity</span>
<span class="token keyword">boolean</span> hasVisibleActivities <span class="token operator">=</span> mStackSupervisor<span class="token punctuation">.</span><span class="token function">handleAppDiedLocked</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 清除activity列表</span>
app<span class="token punctuation">.</span>activities<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>restarting <span class="token operator">&amp;&amp;</span> hasVisibleActivities
            <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>mStackSupervisor<span class="token punctuation">.</span><span class="token function">resumeFocusedStackTopActivityLocked</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 3. 若当前crash进程中存在可视Activity,那么AMS还是会确保所有可见Activity正常运行,故会重启该进程</span>
        mStackSupervisor<span class="token punctuation">.</span><span class="token function">ensureActivitiesVisibleLocked</span><span class="token punctuation">(</span>null<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token operator">!</span>PRESERVE_WINDOWS<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
    mWindowManager<span class="token punctuation">.</span><span class="token function">continueSurfaceLayout</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

注释1比较重要的是对于crash进程中的Bounded Service而言,会清理掉service与客户端之间的联系,此外若service的客户端重要性过低,还会被直接kill掉。注释2处判断是否应用还存在可见的Activity,注释3处对于可见的Activity系统要保证其正常运行,还会重新启动进程。

总结

app停止原来如此啊,当然app停止不可完全避免,但是一旦出现实在太难看了,而且没法收集到log,下篇就看看作为开发者自己如何处理这种未捕获异常。

                                </div>
<think>我们讨论的是Android 8.0系统下,当用户手动选择时区后,如何更新时间和日期。 在Android中,时区和时间的设置通常是分开的,但时区的改变可能会影响时间的显示(因为时间是基于时区的UTC偏移量计算的)。 用户手动选择时区后,系统会处理时区的更新,并可能触发时间的重新计算。但是,日期和时间本身可能不会改变,因为系统内部使用UTC时间存储。 不过,有时候用户可能希望同时设置时区和时间(例如,当用户旅行到一个新的时区并希望手动调整时间时)。 在Android 8.0中,我们可以通过以下方式实现手动选择时区并更新时间和日期: 1. 设置时区:使用`AlarmManager`的`setTimeZone`方法(需要权限`android.permission.SET_TIME_ZONE`)。 2. 设置时间:使用`AlarmManager`的`setTime`方法(需要权限`android.permission.SET_TIME`)。注意,这两个权限都是系统权限,普通应用无法使用。因此,通常这个操作只能由系统应用或具有系统签名的应用执行。 但是,如果我们的应用是系统应用,或者运行在具有root权限的设备上,则可以尝试执行这些操作。 另外,在Android 8.0中,用户手动选择时区通常是通过系统设置应用完成的。我们可以通过监听时区变化来更新我们的应用内时间显示,但不能直接设置时区(除非有系统权限)。 以下是一个示例,展示如何在一个具有系统权限的应用中设置时区和时间: 假设我们需要实现一个功能:用户选择一个时区,并输入一个日期时间,然后我们更新系统时区和系统时间。 步骤: 1. 获取用户选择的时区ID(如"America/New_York")和要设置的日期时间(例如,用户选择了一个日期和时间,我们将其转换为毫秒时间戳,注意这个时间戳应该是基于新时区的本地时间,但我们要将其转换为UTC时间戳)。 2. 设置系统时区。 3. 设置系统时间(UTC时间)。 注意:设置系统时间需要将用户输入的本地时间(在所选时区下)转换为UTC时间戳。 代码示例: 以下是具有系统权限的应用代码(需要在AndroidManifest.xml中声明系统权限,并且应用需要签名为系统应用): ```java import android.app.AlarmManager; import android.content.Context; import android.os.SystemClock; import android.util.Log; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; public class SystemTimeZoneUpdater { private static final String TAG = "SystemTimeZoneUpdater"; // 设置系统时区 public static void setSystemTimeZone(Context context, String timeZoneId) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (alarmManager != null) { alarmManager.setTimeZone(timeZoneId); Log.i(TAG, "System time zone set to: " + timeZoneId); } else { Log.e(TAG, "Could not get AlarmManager service"); } } // 设置系统时间(UTC时间) public static void setSystemTime(Context context, long utcTimeMillis) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (alarmManager != null) { alarmManager.setTime(utcTimeMillis); Log.i(TAG, "System time set to: " + utcTimeMillis); } else { Log.e(TAG, "Could not get AlarmManager service"); } } // 将用户输入的本地时间字符串(在指定时区)转换为UTC时间戳 public static long convertLocalTimeToUTC(String localTime, String pattern, String timeZoneId) { SimpleDateFormat sdf = new SimpleDateFormat(pattern); sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId)); try { Date date = sdf.parse(localTime); return date.getTime(); } catch (ParseException e) { e.printStackTrace(); return -1; } } // 示例:用户选择时区为"Asia/Shanghai",并输入时间字符串"2023-01-01 12:00:00" public static void updateSystemDateTime(Context context, String timeZoneId, String localDateTime) { // 步骤1:设置时区 setSystemTimeZone(context, timeZoneId); // 步骤2:将用户输入的时间字符串转换为UTC时间戳 // 注意:用户输入的时间字符串是在timeZoneId时区下的本地时间 String pattern = "yyyy-MM-dd HH:mm:ss"; long utcTime = convertLocalTimeToUTC(localDateTime, pattern, timeZoneId); if (utcTime != -1) { setSystemTime(context, utcTime); } else { Log.e(TAG, "Failed to parse local time string"); } } } ``` 权限声明(在AndroidManifest.xml中): ```xml <uses-permission android:name="android.permission.SET_TIME"/> <uses-permission android:name="android.permission.SET_TIME_ZONE"/> ``` 注意: 1. 这两个权限是系统权限,普通应用无法获取。因此,此代码只适用于系统应用。 2. 在设置时间时,我们使用的是`AlarmManager.setTime(long millis)`,它设置的是系统实时时钟(RTC)的时间。这个时间是以毫秒为单位的UTC时间。 3. 在转换用户输入的时间时,我们使用了SimpleDateFormat,注意解析异常。 对于普通应用,无法直接设置系统时区和时间,但可以监听时区变化(通过广播`Intent.ACTION_TIMEZONE_CHANGED`)并更新应用内的时间显示。 监听时区变化的广播接收器示例: ```java import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class TimeZoneChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) { String newTimeZone = intent.getStringExtra("time-zone"); Log.i("TimeZoneChange", "Time zone changed to: " + newTimeZone); // 在这里更新应用内的时间显示 } } } ``` 注册广播接收器(在AndroidManifest.xml中): ```xml <receiver android:name=".TimeZoneChangeReceiver"> <intent-filter> <action android:name="android.intent.action.TIMEZONE_CHANGED"/> </intent-filter> </receiver> ``` 总结: - 系统应用可以通过AlarmManager设置系统时区和时间。 - 普通应用只能监听时区变化,但不能修改系统时区或时间。 §§相关问题§§: 1. 如何在没有系统权限的情况下在应用中模拟不同的时区? 2. Android 10 及以上版本在时区和时间设置上有哪些变化? 3. 如何获取Android设备支持的所有时区列表? 4. 如何在应用内根据时区变化自动调整时间显示? </think>### Android 8.0 手动选择时区并更新日期时间的实现 在 Android 8.0 中,当用户手动选择时区后,系统需要正确处理时区更新并确保日期时间的准确性。以下是完整的实现方案: #### 核心实现代码 ```java import android.app.AlarmManager; import android.content.ContentResolver; import android.content.Context; import android.provider.Settings; import android.util.Log; import java.util.TimeZone; public class TimeZoneUpdater { private static final String TAG = "TimeZoneUpdater"; /** * 更新系统时区并重新计算日期时间 * @param context 上下文对象 * @param newTimeZoneId 新时区ID (如 "Asia/Shanghai") */ public static void updateSystemTimeZone(Context context, String newTimeZoneId) { // 1. 获取当前AlarmManager实例 AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); // 2. 设置新时区 if (alarmManager != null) { alarmManager.setTimeZone(newTimeZoneId); Log.i(TAG, "时区已设置为: " + newTimeZoneId); } else { Log.e(TAG, "无法获取AlarmManager服务"); } // 3. 更新系统设置中的自动时区选项 disableAutoTimeZone(context); // 4. 重新计算日期时间 adjustDateTimeForNewTimeZone(); } /** * 禁用自动时区更新(防止系统自动覆盖手动设置) */ private static void disableAutoTimeZone(Context context) { try { ContentResolver contentResolver = context.getContentResolver(); // 设置自动时区更新为关闭 (0=关闭, 1=开启) Settings.Global.putInt(contentResolver, Settings.Global.AUTO_TIME_ZONE, 0); Log.i(TAG, "已禁用自动时区更新"); } catch (SecurityException e) { Log.e(TAG, "权限不足,需要WRITE_SETTINGS权限: " + e.getMessage()); } } /** * 根据新时区调整日期时间 */ private static void adjustDateTimeForNewTimeZone() { // 获取新旧时区的偏移量差异 TimeZone oldTimeZone = TimeZone.getDefault(); TimeZone newTimeZone = TimeZone.getTimeZone(newTimeZoneId); int oldOffset = oldTimeZone.getOffset(System.currentTimeMillis()); int newOffset = newTimeZone.getOffset(System.currentTimeMillis()); int offsetDiff = newOffset - oldOffset; Log.d(TAG, "时区偏移差异: " + offsetDiff + " 毫秒"); // 如果需要保持本地时间不变(仅时区改变) if (shouldKeepLocalTimeUnchanged(context)) { // 调整系统时间来补偿时区变化 long currentTime = System.currentTimeMillis(); alarmManager.setTime(currentTime - offsetDiff); Log.i(TAG, "已调整系统时间以保持本地时间不变"); } } /** * 检查用户是否希望保持本地时间不变 */ private static boolean shouldKeepLocalTimeUnchanged(Context context) { // 实际实现中应读取用户设置 return true; // 默认行为:仅改变时区,不改变本地时间 } } ``` #### 用户界面实现示例(Activity) ```java import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.Spinner; import androidx.appcompat.app.AppCompatActivity; import java.util.Arrays; import java.util.TimeZone; public class TimeZoneSelectorActivity extends AppCompatActivity { private Spinner timeZoneSpinner; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_time_zone_selector); // 初始化时区下拉菜单 timeZoneSpinner = findViewById(R.id.timeZoneSpinner); setupTimeZoneSpinner(); // 设置确认按钮点击事件 findViewById(R.id.btnConfirm).setOnClickListener(v -> { String selectedTimeZone = (String) timeZoneSpinner.getSelectedItem(); TimeZoneUpdater.updateSystemTimeZone(this, selectedTimeZone); }); } private void setupTimeZoneSpinner() { // 获取所有可用时区ID String[] availableTimeZones = TimeZone.getAvailableIDs(); Arrays.sort(availableTimeZones); // 设置适配器 ArrayAdapter<String> adapter = new ArrayAdapter<>( this, android.R.layout.simple_spinner_item, availableTimeZones ); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); timeZoneSpinner.setAdapter(adapter); // 设置当前时区为默认选项 String currentTimeZone = TimeZone.getDefault().getID(); int position = Arrays.asList(availableTimeZones).indexOf(currentTimeZone); if (position >= 0) { timeZoneSpinner.setSelection(position); } } } ``` #### 布局文件 (res/layout/activity_time_zone_selector.xml) ```xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="选择时区:" android:textSize="18sp"/> <Spinner android:id="@+id/timeZoneSpinner" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp"/> <Button android:id="@+id/btnConfirm" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end" android:layout_marginTop="16dp" android:text="确认"/> </LinearLayout> ``` #### AndroidManifest.xml 所需权限 ```xml <uses-permission android:name="android.permission.SET_TIME_ZONE"/> <uses-permission android:name="android.permission.WRITE_SETTINGS"/> ``` ### 关键机制解释 1. **时区更新流程**: - 通过 `AlarmManager.setTimeZone()` 方法设置新时区 - 系统自动广播 `Intent.ACTION_TIMEZONE_CHANGED` 通知所有应用 - `Calendar` 和 `Date` 对象会自动使用新时区 2. **时间保持策略**: - 默认行为:仅改变时区,不改变本地时间(例如从UTC+8改为UTC+9,时间显示自动加1小时) - 可选行为:补偿时间偏移以保持UTC时间不变 3. **禁用自动更新**: - 手动设置时区后关闭 `AUTO_TIME_ZONE` 设置 - 防止NITZ/NTP自动覆盖用户选择 4. **广播通知**: 监听时区变化的广播接收器: ```java public class TimeZoneChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) { String newTimeZone = intent.getStringExtra("time-zone"); Log.i("TimeZoneReceiver", "时区已更改为: " + newTimeZone); // 更新应用内所有时间显示 } } } ``` ### 不同场景处理策略 | 场景 | 处理方式 | |------|----------| | 仅改变时区 | 系统自动调整本地时间显示 | | 改变时区并保持UTC时间 | 手动补偿时间偏移 | | 改变时区并保持本地时间 | 无需额外操作(默认行为) | | 跨时区旅行 | 建议启用位置服务自动更新 | ### 兼容性注意事项 1. **Android 8.0+限制**: - 普通应用无法直接设置时区(需要系统权限) - 解决方法:仅系统应用使用此功能,或引导用户到系统设置 2. **签名权限**: ```xml <!-- 在AndroidManifest.xml中声明 --> <permission android:name="android.permission.SET_TIME_ZONE" android:protectionLevel="signature|system"/> ``` 3. **测试方法**: ```bash adb shell date [MMDDhhmm[[CC]YY][.ss]] adb shell setprop persist.sys.timezone "Asia/Shanghai" ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值