平台:
RK3288 + Android7.12
问题:
打开测试应用, 并在各个生命周期中加入LOG, 当接入USB外设后, 会导致Activity重走了一次生命周期.
LOG如下: 2017-01-02 11:35:20.189com.test.app I/EntranceActivity: ALog > LIEF[ -> onCreate] 2017-01-02 11:35:20.246com.test.app I/EntranceActivity: ALog > LIEF[onCreate -> onStart] 2017-01-02 11:35:20.249com.test.app I/EntranceActivity: ALog > LIEF[onStart -> onResume] 接上USB外设 2017-01-02 11:35:59.621com.test.app I/EntranceActivity: ALog > LIEF[onResume -> onPause] 2017-01-02 11:35:59.622com.test.app I/EntranceActivity: ALog > LIEF[onPause -> onSaveInstanceState] 2017-01-02 11:35:59.636com.test.app I/EntranceActivity: ALog > LIEF[onSaveInstanceState -> onStop] 2017-01-02 11:35:59.636com.test.app I/EntranceActivity: ALog > LIEF[onStop -> onDestroy] 2017-01-02 11:35:59.686com.test.app I/EntranceActivity: ALog > LIEF[ -> onCreate] 2017-01-02 11:35:59.704com.test.app I/EntranceActivity: ALog > LIEF[onCreate -> onStart] 2017-01-02 11:35:59.706com.test.app I/EntranceActivity: ALog > LIEF[onStart -> onRestoreInstanceState] 2017-01-02 11:35:59.708com.test.app I/EntranceActivity: ALog > LIEF[onRestoreInstanceState -> onResume]
解决方案:
在AndroidManifest.xml中, 指定activity的定义加上:
android:configChanges="screenSize|keyboard|keyboardHidden|navigation"
在对应的Activity中, 新增:
@Override public void onConfigurationChanged(Configuration newConfig) { //USB 拔插动作, 这个方法都会被调用. super.onConfigurationChanged(newConfig); }
修改后LOG: 2017-01-01 20:10:36.561com.test.app I/EntranceActivity: ALog > LIFE[ -> onCreate] 2017-01-01 20:10:36.627com.test.app I/EntranceActivity: ALog > LIFE[onCreate -> onStart] 2017-01-01 20:10:36.630com.test.app I/EntranceActivity: ALog > LIFE[onStart -> onResume] 拔插USB: 2017-01-01 20:13:15.329com.test.app I/EntranceActivity: ALog > LIFE[onResume -> onConfigurationChanged]
代码跟踪:
|–frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
mInputManager = new InputManager(eventHub, this, this);
|-- frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::doNotifyConfigurationChangedInterruptible( CommandEntry* commandEntry) { mLock.unlock(); #ifndef INPUT_BOX //-----------mPolicy来自 new InputManager(eventHub, this, this);--------- mPolicy->notifyConfigurationChanged(commandEntry->eventTime); #endif mLock.lock(); }
|–frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
void NativeInputManager::notifyConfigurationChanged(nsecs_t when) { #if DEBUG_INPUT_DISPATCHER_POLICY ALOGD("notifyConfigurationChanged - when=%lld", when); #endif JNIEnv* env = jniEnv(); env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyConfigurationChanged, when); checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged"); }
|–frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
// Native callback. private void notifyConfigurationChanged(long whenNanos) { mWindowManagerCallbacks.notifyConfigurationChanged(); }
|–frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
/* Notifies that the input device configuration has changed. */ @Override public void notifyConfigurationChanged() { mService.sendNewConfiguration(); synchronized (mInputDevicesReadyMonitor) { if (!mInputDevicesReady) { mInputDevicesReady = true; mInputDevicesReadyMonitor.notifyAll(); } } }
|-- frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
/* * Instruct the Activity Manager to fetch the current configuration and broadcast * that to config-changed listeners if appropriate. */ void sendNewConfiguration() { try { mActivityManager.updateConfiguration(null); } catch (RemoteException e) { } } public Configuration computeNewConfiguration() { synchronized (mWindowMap) { return computeNewConfigurationLocked(); } }
|-- frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void updateConfiguration(Configuration values) { enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, "updateConfiguration()"); synchronized(this) { if (values == null && mWindowManager != null) { // sentinel: fetch the current configuration from the window manager values = mWindowManager.computeNewConfiguration(); } if (mWindowManager != null) { mProcessList.applyDisplaySize(mWindowManager); } final long origId = Binder.clearCallingIdentity(); if (values != null) { Settings.System.clearConfiguration(values); } updateConfigurationLocked(values, null, false); Binder.restoreCallingIdentity(origId); } }
|-- frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public Configuration computeNewConfiguration() { synchronized (mWindowMap) { return computeNewConfigurationLocked(); } } private Configuration computeNewConfigurationLocked() { if (!mDisplayReady) { return null; } Configuration config = new Configuration(); config.fontScale = 0; computeScreenConfigurationLocked(config); return config; } /** Do not call if mDisplayReady == false */ void computeScreenConfigurationLocked(Configuration config) { final DisplayInfo displayInfo = updateDisplayAndOrientationLocked( config.uiMode); final int dw = displayInfo.logicalWidth; final int dh = displayInfo.logicalHeight; config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation, config.uiMode) / mDisplayMetrics.density); config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation, config.uiMode) / mDisplayMetrics.density); final boolean rotated = (mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270); computeSizeRangesAndScreenLayout(displayInfo, rotated, config.uiMode, dw, dh, mDisplayMetrics.density, config); config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK) | ((displayInfo.flags & Display.FLAG_ROUND) != 0 ? Configuration.SCREENLAYOUT_ROUND_YES : Configuration.SCREENLAYOUT_ROUND_NO); config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale); config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale); config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, config.uiMode, mDisplayMetrics, dw, dh); config.densityDpi = displayInfo.logicalDensityDpi; // Update the configuration based on available input devices, lid switch, // and platform configuration. config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; config.keyboard = Configuration.KEYBOARD_NOKEYS; config.navigation = Configuration.NAVIGATION_NONAV; int keyboardPresence = 0; int navigationPresence = 0; final InputDevice[] devices = mInputManager.getInputDevices(); final int len = devices.length; for (int i = 0; i < len; i++) { InputDevice device = devices[i]; if (!device.isVirtual()) { final int sources = device.getSources(); final int presenceFlag = device.isExternal() ? WindowManagerPolicy.PRESENCE_EXTERNAL : WindowManagerPolicy.PRESENCE_INTERNAL; if (mIsTouchDevice) { if ((sources & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) { config.touchscreen = Configuration.TOUCHSCREEN_FINGER; } } else { config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; } if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) { config.navigation = Configuration.NAVIGATION_TRACKBALL; navigationPresence |= presenceFlag; } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD && config.navigation == Configuration.NAVIGATION_NONAV) { config.navigation = Configuration.NAVIGATION_DPAD; navigationPresence |= presenceFlag; } if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { config.keyboard = Configuration.KEYBOARD_QWERTY; keyboardPresence |= presenceFlag; } } } if (config.navigation == Configuration.NAVIGATION_NONAV && mHasPermanentDpad) { config.navigation = Configuration.NAVIGATION_DPAD; navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL; } // Determine whether a hard keyboard is available and enabled. boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; if (hardKeyboardAvailable != mHardKeyboardAvailable) { mHardKeyboardAvailable = hardKeyboardAvailable; mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); } boolean dualscreenconfig = Settings.System.getInt(mContext.getContentResolver(),Settings.DUAL_SCREEN_MODE,0) != 0; config.dualscreenflag= dualscreenconfig ? Configuration.ENABLE_DUAL_SCREEN:Configuration.DISABLE_DUAL_SCREEN; // Let the policy update hidden states. config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES; config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence); }
未接入:I/ActivityManager: Config changes=60 {1.0 dualscreenflag=DISABLE ?mcc?mnc [zh_CN] ldltr sw1080dp w1920dp h1000dp 160dpi xlrg long land finger qwerty/v/h -nav/h s.9} 接入后:I/ActivityManager: Config changes=60 {1.0 dualscreenflag=DISABLE ?mcc?mnc [zh_CN] ldltr sw1080dp w1920dp h1000dp 160dpi xlrg long land finger qwerty/v/h dpad/v s.8} ActivityManager: Configuration changes for ActivityRecord{2f76229 u0com.test.app/.EntranceActivity t46} ; taskChanges={}, allChanges={CONFIG_KEYBOARD_HIDDEN, CONFIG_NAVIGATION}
|-- frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting, boolean initLocale, boolean persistent, int userId, boolean deferResume) { int changes = 0; ... for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); try { if (app.thread != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc " + app.processName + " new config " + mConfiguration); app.thread.scheduleConfigurationChanged(configCopy); } } catch (Exception e) { } } ... final ActivityStack mainStack = mStackSupervisor.getFocusedStack(); // 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 = mainStack.ensureActivityConfigurationLocked(starting, changes, false); // And we need to make sure at this point that all other activities // are made visible with the correct configuration. mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes, !PRESERVE_WINDOWS); } } ... }
调用 onConfigurationChanged
|-- frameworks/base/core/java/android/app/ActivityThread.java
private class ApplicationThread extends ApplicationThreadNative { public void scheduleConfigurationChanged(Configuration config) { updatePendingConfiguration(config); sendMessage(H.CONFIGURATION_CHANGED, config); } } private class H extends Handler { public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case CONFIGURATION_CHANGED: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi; mUpdatingSystemConfig = true; handleConfigurationChanged((Configuration)msg.obj, null); mUpdatingSystemConfig = false; Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; } final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { ... if (callbacks != null) { final int N = callbacks.size(); for (int i=0; i<N; i++) { ComponentCallbacks2 cb = callbacks.get(i); if (cb instanceof Activity) { // If callback is an Activity - call corresponding method to consider override // config and avoid onConfigurationChanged if it hasn't changed. Activity a = (Activity) cb; performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()), config, REPORT_TO_ACTIVITY); } else { performConfigurationChanged(cb, null, config, null, REPORT_TO_ACTIVITY); } } } } private void performConfigurationChangedForActivity(ActivityClientRecord r, Configuration newBaseConfig, boolean reportToActivity) { r.tmpConfig.setTo(newBaseConfig); if (r.overrideConfig != null) { r.tmpConfig.updateFrom(r.overrideConfig); } performConfigurationChanged(r.activity, r.token, r.tmpConfig, r.overrideConfig, reportToActivity); freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig)); } private void performConfigurationChanged(ComponentCallbacks2 cb, IBinder activityToken, Configuration newConfig, Configuration amOverrideConfig, boolean reportToActivity) { ... boolean shouldChangeConfig = false; if ((activity == null) || (activity.mCurrentConfig == null)) { shouldChangeConfig = true; } else { // If the new config is the same as the config this Activity is already // running with and the override config also didn't change, then don't // bother calling onConfigurationChanged. int diff = activity.mCurrentConfig.diff(newConfig); if (diff != 0 || !mResourcesManager.isSameResourcesOverrideConfig(activityToken, amOverrideConfig)) { // Always send the task-level config changes. For system-level configuration, if // this activity doesn't handle any of the config changes, then don't bother // calling onConfigurationChanged as we're going to destroy it. //---------------若AndroidManifest中, 已定义了对应的配置项, 则不重启, 并交由activity中的onConfigurationChanged去处理------------------------ if (!mUpdatingSystemConfig || (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0 || !reportToActivity) { shouldChangeConfig = true; } } } if (shouldChangeConfig) { ... cb.onConfigurationChanged(configToReport); ... } }
调用重启:
|-- frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
boolean ensureActivityConfigurationLocked( ActivityRecord r, int globalChanges, boolean preserveWindow) { if (mConfigWillChange) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Skipping config check (will change): " + r); return true; } if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Ensuring correct configuration: " + r); // Short circuit: if the two configurations are equal (the common case), then there is // nothing to do. final Configuration newConfig = mService.mConfiguration; r.task.sanitizeOverrideConfiguration(newConfig); final Configuration taskConfig = r.task.mOverrideConfig; if (r.