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);
}
}
...
}
|-- 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.configuration.equals(newConfig)
&& r.taskConfigOverride.equals(taskConfig)
&& !r.forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration unchanged in " + r);
return true;
}
// We don't worry about activities that are finishing.
if (r.finishing) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration doesn't matter in finishing " + r);
r.stopFreezingScreenLocked(false);
return true;
}
// Okay we now are going to make this activity have the new config.
// But then we need to figure out how it needs to deal with that.
final Configuration oldConfig = r.configuration;
final Configuration oldTaskOverride = r.taskConfigOverride;
r.configuration = newConfig;
r.taskConfigOverride = taskConfig;
int taskChanges = getTaskConfigurationChanges(r, taskConfig, oldTaskOverride);
final int changes = oldConfig.diff(newConfig) | taskChanges;
if (changes == 0 && !r.forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration no differences in " + r);
// There are no significant differences, so we won't relaunch but should still deliver
// the new configuration to the client process.
r.scheduleConfigurationChanged(taskConfig, true);
return true;
}
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration changes for " + r + " ; taskChanges="
+ Configuration.configurationDiffToString(taskChanges) + ", allChanges="
+ Configuration.configurationDiffToString(changes));
// If the activity isn't currently running, just leave the new
// configuration and it will pick that up next time it starts.
if (r.app == null || r.app.thread == null) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration doesn't matter not running " + r);
r.stopFreezingScreenLocked(false);
r.forceNewConfig = false;
return true;
}
// Figure out how to handle the changes between the configurations.
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Checking to restart " + r.info.name + ": changed=0x"
+ Integer.toHexString(changes) + ", handles=0x"
+ Integer.toHexString(r.info.getRealConfigChanged()) + ", newConfig=" + newConfig
+ ", taskConfig=" + taskConfig);
//---------------若AndroidManifest中, 已定义了对应的配置项, 则不重启, 并交由activity中的onConfigurationChanged去处理------------------------
if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
r.configChangeFlags |= changes;
r.startFreezingScreenLocked(r.app, globalChanges);
r.forceNewConfig = false;
preserveWindow &= isResizeOnlyChange(changes);
if (r.app == null || r.app.thread == null) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Config is destroying non-running " + r);
destroyActivityLocked(r, true, "config");
} else if (r.state == ActivityState.PAUSING) {
// A little annoying: we are waiting for this activity to finish pausing. Let's not
// do anything now, but just flag that it needs to be restarted when done pausing.
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Config is skipping already pausing " + r);
r.deferRelaunchUntilPaused = true;
r.preserveWindowOnDeferredRelaunch = preserveWindow;
return true;
} else if (r.state == ActivityState.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 " + r);
if (DEBUG_STATES && !r.visible) {
Slog.v(TAG_STATES, "Config is relaunching resumed invisible activity " + r
+ " called by " + Debug.getCallers(4));
}
//-------------重启------------
relaunchActivityLocked(r, r.configChangeFlags, true, preserveWindow);
} else {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Config is relaunching non-resumed " + r);
relaunchActivityLocked(r, r.configChangeFlags, false, preserveWindow);
}
// All done... tell the caller we weren't able to keep this activity around.
return false;
}
// Default case: the activity can handle this new configuration, so hand it over.
// NOTE: We only forward the task override configuration as the system level configuration
// changes is always sent to all processes when they happen so it can just use whatever
// system level configuration it last got.
r.scheduleConfigurationChanged(taskConfig, true);
r.stopFreezingScreenLocked(false);
return true;
}
private void relaunchActivityLocked(
ActivityRecord r, int changes, boolean andResume, boolean preserveWindow) {
if (mService.mSuppressResizeConfigChanges && preserveWindow) {
r.configChangeFlags = 0;
return;
}
List<ResultInfo> results = null;
List<ReferrerIntent> newIntents = null;
if (andResume) {
results = r.results;
newIntents = r.newIntents;
}
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
"Relaunching: " + r + " with results=" + results + " newIntents=" + newIntents
+ " andResume=" + andResume + " preserveWindow=" + preserveWindow);
EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY
: EventLogTags.AM_RELAUNCH_ACTIVITY, r.userId, System.identityHashCode(r),
r.task.taskId, r.shortComponentName);
r.startFreezingScreenLocked(r.app, 0);
mStackSupervisor.removeChildActivityContainers(r);
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);
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);
}
if (andResume) {
if (DEBUG_STATES) {
Slog.d(TAG_STATES, "Resumed after relaunch " + r);
}
r.state = ActivityState.RESUMED;
// Relaunch-resume could happen either when the app is already in the front,
// or while it's being brought to front. In the latter case, it's marked RESUMED
// but not yet visible (or stopped). We need to complete the resume here as the
// code in resumeTopActivityInnerLocked to complete the resume might be skipped.
if (!r.visible || r.stopped) {
mWindowManager.setAppVisibility(r.appToken, true);
completeResumeLocked(r);
} else {
r.results = null;
r.newIntents = null;
}
mService.showUnsupportedZoomDialogIfNeededLocked(r);
mService.showAskCompatModeDialogLocked(r);
} else {
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
r.state = ActivityState.PAUSED;
}
r.configChangeFlags = 0;
r.deferRelaunchUntilPaused = false;
r.preserveWindowOnDeferredRelaunch = false;
}