问题
手上有一个问题,光大银行应用的界面大小屏转换,configuration改变后会重启,但是重启后界面就会crash。
这个是可以看到的enents日志
configuration_changed: 536874240
am_relaunch_resume_activity: [0,27273074,89,com.cebbank.mobile.cemb/.ui.activity.HomeActivity
反编译光大银行,查看界面的configuration的配置:
<activity
android:name="com.cebbank.mobile.cemb.ui.activity.HomeActivity"
......
android:configChanges="screenLayout"
/>
可以看到,由于其configChanges配置为screenLayout,那么大小屏转换时就是会重启此界面,导致小屏光大银行无法使用。
本来这是一个三方应用的问题的,但是参考华为mate mx,发现他的是正常的。why?
分析
(1)我们先定们到am_relaunch_resume_activity日志:
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void relaunchActivityLocked(boolean andResume, boolean preserveWindow) {
......
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
"Relaunching: " + this + " with results=" + pendingResults
+ " newIntents=" + pendingNewIntents + " andResume=" + andResume
+ " preserveWindow=" + preserveWindow);
EventLog.writeEvent(andResume ? AM_RELAUNCH_RESUME_ACTIVITY
: AM_RELAUNCH_ACTIVITY, mUserId, System.identityHashCode(this),
task.taskId, shortComponentName);
startFreezingScreenLocked(app, 0);
定位到了此重启activity的位置,那么下一步就打印出此方法的调用堆栈。
(2)此方法的调用堆栈
com.android.server.wm.ActivityRecord.relaunchActivityLocked(ActivityRecord.java:3571)
com.android.server.wm.ActivityRecord.ensureActivityConfiguration(ActivityRecord.java:3467)
com.android.server.wm.ActivityRecord.ensureActivityConfiguration(ActivityRecord.java:3302)
com.android.server.wm.ActivityTaskManagerService.ensureConfigAndVisibilityAfterUpdate(ActivityTaskManagerService.java:6131)
com.android.server.wm.ActivityTaskManagerService.updateDisplayOverrideConfigurationLocked(ActivityTaskManagerService.java:5660)
com.android.server.wm.ActivityTaskManagerService.updateDisplayOverrideConfiguration(ActivityTaskManagerService.java:4755)
com.android.server.wm.WindowManagerService.sendNewConfiguration(WindowManagerService.java:4491)
com.android.server.wm.WindowManagerService$H.handleMessage(WindowManagerService.java:4936)
android.os.Handler.dispatchMessage(Handler.java:107)
android.os.Looper.loop(Looper.java:221)
android.os.HandlerThread.run(HandlerThread.java:67)
com.android.server.ServiceThread.run(ServiceThread.java:44)
(3)简单的分析此调用流程
- WindowManagerService$H.handleMessage
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
public void handleMessage(Message msg) {
case SEND_NEW_CONFIGURATION: {//可以看出是configuration改变接收到消息SEND_NEW_CONFIGURATION
final DisplayContent displayContent = (DisplayContent) msg.obj;
removeMessages(SEND_NEW_CONFIGURATION, displayContent);
if (displayContent.isReady()) {
sendNewConfiguration(displayContent.getDisplayId());//调用sendNewConfiguration方法
......
- WindowManagerService.sendNewConfiguration
void sendNewConfiguration(int displayId) {
try {
final boolean configUpdated = mActivityTaskManager.updateDisplayOverrideConfiguration(
null /* values */, displayId);//调用此mActivityTaskManager.updateDisplayOverrideConfiguration
ActivityTaskManagerService.updateDisplayOverrideConfiguration
public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId) {
......
final long origId = Binder.clearCallingIdentity();
try {
if (values != null) {
Settings.System.clearConfiguration(values);
}
updateDisplayOverrideConfigurationLocked(values, null /* starting */,
false /* deferResume */, displayId, mTmpUpdateConfigurationResult);//调用接口updateDisplayOverrideConfigurationLocked
return mTmpUpdateConfigurationResult.changes != 0;
} finally {
Binder.restoreCallingIdentity(origId);
}
- ActivityTaskManagerService.updateDisplayOverrideConfigurationLocked
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
ActivityRecord starting, boolean deferResume, int displayId,
ActivityTaskManagerService.UpdateConfigurationResult result) {
......
kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
- ActivityTaskManagerService.ensureConfigAndVisibilityAfterUpdate
private boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
boolean kept = true;
final ActivityStack mainStack = mRootActivityContainer.getTopDisplayFocusedStack();
// mainStack is null during startup.
if (mainStack != null) {
if (changes != 0 && starting == null) {
// If the configuration changed, and the caller is not already
// in the process of starting an activity, then find the top
// activity to check if its configuration needs to change.
starting = mainStack.topRunningActivityLocked();
}
if (starting != null) {
kept = starting.ensureActivityConfiguration(changes,
false /* preserveWindow */);//请用starting.ensureActivityConfiguration
// And we need to make sure at this point that all other activities
// are made visible with the correct configuration.
mRootActivityContainer.ensureActivitiesVisible(starting, changes,
!PRESERVE_WINDOWS);
}
}
return kept;
}
- ActivityRecord.ensureActivityConfiguration
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
return ensureActivityConfiguration(globalChanges, preserveWindow,
false /* ignoreVisibility */);
}
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
boolean ignoreVisibility) {
......
} else if (mState == RESUMED) {
// Try to optimize this case: the configuration is changing and we need to restart
// the top, resumed activity. Instead of doing the normal handshaking, just say
// "restart!".
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Config is relaunching resumed " + this);
if (DEBUG_STATES && !visible) {
Slog.v(TAG_STATES, "Config is relaunching resumed invisible activity " + this
+ " called by " + Debug.getCallers(4));
}
relaunchActivityLocked(true /* andResume */, preserveWindow);//调用此接口relaunchActivityLocked
} else {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Config is relaunching non-resumed " + this);
relaunchActivityLocked(false /* andResume */, preserveWindow);
}
......
那么我们就有对应的解决思路了,简单的很,就是添加一个应用在configuration change时界面不重启的白名单,就可以解决此问题。
事实上,这是我们对比华为样机mate mx进行对比分析后得出的结论,他应该也是做了这样的一个名单名机制。
我们验证发现,我们的手机其行为和他一致。
至此,此问题解决,并且我们可以一窥应用界面configuration change是如何导致应用界面重启的。
进一步分析
再写一个demo:
其android:configChanges的配置如下:
<activity android:name=".MainActivity"
android:configChanges="screenLayout|orientation|fontScale|screenSize" >
在MainActivity中,我们重写onConfigurationChanged方法:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
那么此MainActivity–onConfigurationChanged在应用方向转换或字体大小改变时,其调用流程如何呢?
MainActivity–onConfigurationChanged调用堆栈:
com.firstdemo.MainActivity.onConfigurationChanged(MainActivity.java:66)
android.app.ActivityThread.performActivityConfigurationChanged(ActivityThread.java:5617)
android.app.ActivityThread.performConfigurationChangedForActivity(ActivityThread.java:5484)
android.app.ActivityThread.performConfigurationChangedForActivity(ActivityThread.java:5462)
android.app.ActivityThread.handleActivityConfigurationChanged(ActivityThread.java:5887)
android.app.servertransaction.ActivityConfigurationChangeItem.execute(ActivityConfigurationChangeItem.java:48)
android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
android.app.ActivityThread$H.handleMessage(ActivityThread.java:2027)
android.os.Handler.dispatchMessage(Handler.java:107)
android.os.Looper.loop(Looper.java:221)
android.app.ActivityThread.main(ActivityThread.java:7569)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:935)
我们简要分析一下此流程:
- ActivityThread$H.handleMessage
case EXECUTE_TRANSACTION://处理消息EXECUTE_TRANSACTION
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);//调用mTransactionExecutor.execute
- TransactionExecutor.execute
/**
* Resolve transaction.
* First all callbacks will be executed in the order they appear in the list. If a callback
* requires a certain pre- or post-execution state, the client will be transitioned accordingly.
* Then the client will cycle to the final lifecycle state if provided. Otherwise, it will
* either remain in the initial state, or last state needed by a callback.
*/
public void execute(ClientTransaction transaction) {
......
executeCallbacks(transaction);//调用其回调接口
- TransactionExecutor.executeCallbacks
/** Cycle through all states requested by callbacks and execute them at proper times. */
@VisibleForTesting
public void executeCallbacks(ClientTransaction transaction) {
......
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
final int postExecutionState = item.getPostExecutionState();
final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
item.getPostExecutionState());
if (closestPreExecutionState != UNDEFINED) {
cycleToPath(r, closestPreExecutionState, transaction);
}
item.execute(mTransactionHandler, token, mPendingActions);//调用其item.execute
- ActivityConfigurationChangeItem.execute
/**
* Activity configuration changed callback.
* @hide
*/
public class ActivityConfigurationChangeItem extends ClientTransactionItem {
......
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
// TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
client.handleActivityConfigurationChanged(token, mConfiguration, INVALID_DISPLAY);//调用接口client.handleActivityConfigurationChanged
- ActivityThread.handleActivityConfigurationChanged
/**
* Handle new activity configuration and/or move to a different display.
* @param activityToken Target activity token.
* @param overrideConfig Activity override config.
* @param displayId Id of the display where activity was moved to, -1 if there was no move and
* value didn't change.
*/
@Override
public void handleActivityConfigurationChanged(IBinder activityToken,
Configuration overrideConfig, int displayId) {
......
// Perform updates.
r.overrideConfig = overrideConfig;
final ViewRootImpl viewRoot = r.activity.mDecor != null
? r.activity.mDecor.getViewRootImpl() : null;
if (movedToDifferentDisplay) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:"
+ r.activityInfo.name + ", displayId=" + displayId
+ ", config=" + overrideConfig);
final Configuration reportedConfig = performConfigurationChangedForActivity(r,
mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
if (viewRoot != null) {
viewRoot.onMovedToDisplay(displayId, reportedConfig);//viewRoot.onMovedToDisplay
}
} else {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+ r.activityInfo.name + ", config=" + overrideConfig);
performConfigurationChangedForActivity(r, mCompatConfiguration);//调用performConfigurationChangedForActivity
}
// Notify the ViewRootImpl instance about configuration changes. It may have initiated this
// update to make sure that resources are updated before updating itself.
if (viewRoot != null) {
viewRoot.updateConfiguration(displayId);//ViewRootImpl 调用updateConfiguration
}
mSomeActivitiesChanged = true;
}
- ActivityThread.performConfigurationChangedForActivity
/**
* Updates the configuration for an Activity. The ActivityClientRecord's
* {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
* that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
* the updated Configuration.
* @param r ActivityClientRecord representing the Activity.
* @param newBaseConfig The new configuration to use. This may be augmented with
* {@link ActivityClientRecord#overrideConfig}.
*/
private void performConfigurationChangedForActivity(ActivityClientRecord r,
Configuration newBaseConfig) {
performConfigurationChangedForActivity(r, newBaseConfig,
r.activity.getDisplayId(), false /* movedToDifferentDisplay */);
}
/**
* Updates the configuration for an Activity. The ActivityClientRecord's
* {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
* that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
* the updated Configuration.
* @param r ActivityClientRecord representing the Activity.
* @param newBaseConfig The new configuration to use. This may be augmented with
* {@link ActivityClientRecord#overrideConfig}.
* @param displayId The id of the display where the Activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
* @return {@link Configuration} instance sent to client, null if not sent.
*/
private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
r.tmpConfig.setTo(newBaseConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay);//调用performActivityConfigurationChanged
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
return reportedConfig;
}
- ActivityThread.performActivityConfigurationChanged
/**
* Decides whether to update an Activity's configuration and whether to inform it.
* @param activity The activity to notify of configuration change.
* @param newConfig The new configuration.
* @param amOverrideConfig The override config that differentiates the Activity's configuration
* from the base global configuration. This is supplied by
* ActivityManager.
* @param displayId Id of the display where activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
* @return Configuration sent to client, null if no changes and not moved to different display.
*/
private Configuration performActivityConfigurationChanged(Activity activity,
Configuration newConfig, Configuration amOverrideConfig, int displayId,
boolean movedToDifferentDisplay) {
.....
if (shouldChangeConfig) {
activity.mCalled = false;
activity.onConfigurationChanged(configToReport);//调用 activity.onConfigurationChanged
.....
- MainActivity.onConfigurationChanged
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
字体configuration change的流程分析
我们修改字体,应用重启界面的流程如下:
com.android.server.wm.ActivityRecord.relaunchActivityLocked(ActivityRecord.java:3571)
com.android.server.wm.ActivityRecord.ensureActivityConfiguration(ActivityRecord.java:3467)
com.android.server.wm.ActivityRecord.ensureActivityConfiguration(ActivityRecord.java:3302)
com.android.server.wm.ActivityTaskManagerService.ensureConfigAndVisibilityAfterUpdate(ActivityTaskManagerService.java:6131)
com.android.server.wm.ActivityTaskManagerService.updateConfigurationLocked(ActivityTaskManagerService.java:5500)
com.android.server.wm.ActivityTaskManagerService.updateConfigurationLocked(ActivityTaskManagerService.java:5460)
com.android.server.wm.ActivityTaskManagerService.updatePersistentConfiguration(ActivityTaskManagerService.java:5450)
com.android.server.wm.ActivityTaskManagerService.updateFontScaleIfNeeded(ActivityTaskManagerService.java:5791)
com.android.server.wm.ActivityTaskManagerService.access$000(ActivityTaskManagerService.java:333)
com.android.server.wm.ActivityTaskManagerService$FontScaleSettingObserver.onChange(ActivityTaskManagerService.java:711)
android.database.ContentObserver$NotificationRunnable.run(ContentObserver.java:218)
android.os.Handler.handleCallback(Handler.java:883)
android.os.Handler.dispatchMessage(Handler.java:100)
android.os.Looper.loop(Looper.java:221)
android.os.HandlerThread.run(HandlerThread.java:67)
com.android.server.ServiceThread.run(ServiceThread.java:44)
我们可以根据此调用堆栈信息,自己来分析一下其调用流程。
设置界面方向的流程分析
我们设置界面方向,应用重启界面的流程如下:
com.android.server.wm.ActivityRecord.relaunchActivityLocked(ActivityRecord.java:3571)
com.android.server.wm.ActivityRecord.ensureActivityConfiguration(ActivityRecord.java:3467)
com.android.server.wm.ActivityStack.ensureActivitiesVisibleLocked(ActivityStack.java:2457)
com.android.server.wm.ActivityDisplay.ensureActivitiesVisible(ActivityDisplay.java:1540)
com.android.server.wm.RootActivityContainer.ensureActivitiesVisible(RootActivityContainer.java:825)
com.android.server.wm.RootActivityContainer.ensureActivitiesVisible(RootActivityContainer.java:812)
com.android.server.wm.ActivityTaskManagerService.ensureConfigAndVisibilityAfterUpdate(ActivityTaskManagerService.java:6135)
com.android.server.wm.ActivityTaskManagerService.updateDisplayOverrideConfigurationLocked(ActivityTaskManagerService.java:5660)
com.android.server.wm.ActivityTaskManagerService.updateDisplayOverrideConfigurationLocked(ActivityTaskManagerService.java:5630)
com.android.server.wm.DisplayContent.onDescendantOrientationChanged(DisplayContent.java:1274)
com.android.server.wm.WindowContainer.onDescendantOrientationChanged(WindowContainer.java:726)
com.android.server.wm.WindowContainer.onDescendantOrientationChanged(WindowContainer.java:726)
com.android.server.wm.Task.onDescendantOrientationChanged(Task.java:327)
com.android.server.wm.WindowContainer.onDescendantOrientationChanged(WindowContainer.java:726)
com.android.server.wm.WindowContainer.setOrientation(WindowContainer.java:777)
com.android.server.wm.ActivityRecord.setOrientation(ActivityRecord.java:2766)
com.android.server.wm.ActivityRecord.setRequestedOrientation(ActivityRecord.java:2752)
com.android.server.wm.ActivityTaskManagerService.setRequestedOrientation(ActivityTaskManagerService.java:2008)
android.app.IActivityTaskManager$Stub.onTransact(IActivityTaskManager.java:2635)
android.os.Binder.execTransactInternal(Binder.java:1021)
android.os.Binder.execTransact(Binder.java:994)
我们也可以根据此调用堆栈信息,自己来分析一下其调用流程。