android开发浅谈之configChanges理解

问题

手上有一个问题,光大银行应用的界面大小屏转换,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)

我们也可以根据此调用堆栈信息,自己来分析一下其调用流程。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hfreeman2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值