在项目中遇到一个奇怪的Bug,插上带有升级包固件的U盘,选择升级框中的放弃按钮,Activity被onDestroy,随后又重新onCreate,相应的图片和日志如下:
【一】 现象和日志
1、升级框

2、点击放弃按钮

3.日志如下:
1528 D MainActivity: onCreate
1528 1528 D MainActivity: onResume
1528 1528 D MainActivity: surfaceChanged
1528 1528 D MainActivity: onStop
1528 1528 D MainActivity: onDestroy
1528 D MainActivity: onCreate
从日志中看,PID为1528,没有变化,说明进程还是在的,只是Activity被重新创建了,由于Activity重新创建,底层so资源释放存在一点问题,所以如图二所见,崩溃了。
【二】、原因分析
有一些Android开发经验的朋友都知道,如果Activity被销毁,一般ActivityManager会打印出一些比如,force stop - destroy XXActivity之类的,很可惜我打印出来的日志没有。
没办法,先提出几个可能导致的原因,然后逐一排查。
(1)、和升级框会不会有必然的关系?为什么点击放弃之后,Activity就创建呢?升级框是不是干掉我们呢?
(2)、是不是系统底层的某种机制或者策略导致了Activity重新创建?
好,顺着这个思路,先把RkUpdateService.apk pull出来,反编译,查看其到底做了什么,为了保护原作者的创作,这里我就不贴出来截图了,结果是他们 do Nothing !!
第一条思路排除了,那现在顺着第二条思路查找。
在frameworks/base/core/java/android/app/ActivityThread.java中找到了 重启相关的代码。
private void handleRelaunchActivity(ActivityClientRecord tmp) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
//省略了部分代码
// 销毁Activity
handleDestroyActivity(r.token, false, configChanges, true);
// 继续省略部分代码
// 创建Activity
handleLaunchActivity(r, currentIntent, "handleRelaunchActivity");
if (!tmp.onlyLocalRequest) {
try {
ActivityManagerNative.getDefault().activityRelaunched(r.token);
if (r.window != null) {
r.window.reportActivityRelaunched();
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
这段方法,和我们日志中展示的一样,那ActivityThread的行为必定是从AMS中触发的,走,去那边找找看,必定有所收获。
在 /framework/base/service/core/java/android/server/am/ActivityStack的中找到了relaunchActivityLocked 方法。
private void relaunchActivityLocked(
ActivityRecord r, int changes, boolean andResume, boolean preserveWindow) {
try {
if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH,
"Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + r
+ " callers=" + Debug.getCallers(6));
r.forceNewConfig = false;
mStackSupervisor.activityRelaunchingLocked(r);
// 让客户端进程重启Activity,这个是重点
r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes,
!andResume, new Configuration(mService.mConfiguration),
new Configuration(r.task.mOverrideConfig), preserveWindow);
// Note: don't need to call pauseIfSleepingLocked() here, because
// the caller will only pass in 'andResume' if this activity is
// currently resumed, which implies we aren't sleeping.
} catch (RemoteException e) {
if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH, "Relaunch failed", e);
}
}
这个方法又是被谁调用的,继续查找;

有两个地方调用,completePauseLocked方法和ensureActivityConfigurationLocked方法,第一个方法是完成了Pause,woc(语气词),这个不就是博文开始处点击 放弃按钮,之后又重新回到Activity之后,就重启了吗?
另外这两个主动调用的方法有没有什么一些联系呢?
先看 第一个主动调用的方法。
private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
ActivityRecord prev = mPausingActivity;
if (prev != null) {
// 此处省略大部分代码
//这里是关键,如果这里为true,调用重启
if (prev.deferRelaunchUntilPaused) {
relaunchActivityLocked(prev, prev.configChangeFlags, false,
prev.preserveWindowOnDeferredRelaunch);
} else if (wasStopping) {
}
从这个方法,如果deferRelaunchUntilPaused为true,就调用重启方法,这个标志位的名称就可以看出 等到pause状态完成重启。
这个标志位在哪里赋值的呢?我继续serach project.
找到了,就在刚才第二个调用的方法中 ensureActivityConfigurationLocked
boolean ensureActivityConfigurationLocked(
ActivityRecord r, int globalChanges, boolean preserveWindow) {
//此处省略了大量的代码
if (mConfigWillChange) {
if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
destroyActivityLocked(r, true, "config");
} else if (r.state == ActivityState.PAUSING) {
//如果Activity此时为Pause状态,先设置一个标志位
r.deferRelaunchUntilPaused = true;
r.preserveWindowOnDeferredRelaunch = preserveWindow;
return true;
} else if (r.state == ActivityState.RESUMED) {
//如果Activity的状态是resume,也就是页面没有被遮挡,
那么就立即重启。
relaunchActivityLocked(r, r.configChangeFlags, true, preserveWindow);
}
到目前为止,我理一下分析过程
1、插上了带有升级包的U盘之后,系统检测到了配置变化,调用了ensureActivityConfigurationLocked方法,由于此时Activity是Pause状态,没有立即重启,而是设置deferRelaunchUntilPaused 为true 。
2、我点击放弃按钮之后,Activity回到Pause——>Resume状态过渡,此时走到completePauseLocked方法,因为刚刚deferRelaunchUntilPaused 为true,此时是重启的时候了,通过进程间AIDL,调用ActivityThread方法,完成重启。
此时基本可以确认是由于configuration变化导致了AMS启动重启,我突然一想,日志中到底有没有蛛丝马迹呢?回过头再去找找,果然被我找到了。
ActivityManager: Config changes=30 {1.0 ?mcc?mnc [zh] ldltr sw811dp w1442dp h811dp 213dpi xlrg long land -touch qwerty/v/v dpad/v s.4}
打印出的是new Configuration的信息,我经过和old configuration对比,发现是由于键盘类型发生变化导致的,由于现有的系统和U盘中的固件的键盘configuration不一致导致这个原因。
好了,知道原因了,解决办法就比较简单了。
在 AndroidManifest.xml 中配置一下
android:configChanges="keyboard|keyboardHidden"
搞定。
本文记录了一起Android项目中因configChanges导致Activity被重新创建的问题。当用户点击升级框的放弃按钮时,Activity被销毁并重新创建,引发崩溃。通过对系统源码的分析,发现是键盘类型变化导致的configuration更新,从而触发了Activity的重启。解决方案是在AndroidManifest.xml中针对键盘变化进行配置处理,以避免不必要的Activity重建。
383

被折叠的 条评论
为什么被折叠?



