Android 13 深色主题切换流程
学习笔记:Android小白,这位置网上没资料,通过自己打日志阅读代码走的流程,可能有理解错误的地方。欢迎指正,大家共同进步。
深色主题设置方法:两种设置方法流程是一样的。
- 通过下拉状态栏的快捷按钮
深色主题
切换;- 通过 设置→显示→深色主题开关 切换;
本文以下拉状态栏的快捷按钮深色主题
切换为例;
该快捷按钮类为 UiModeNightTile.java
,直接看点击事件:
// UiModeNightTile.java
@Override
protected void handleClick(@Nullable View view) {
if (getState().state == Tile.STATE_UNAVAILABLE) {
return;
}
boolean newState = !mState.value;
// 设置主题模式
mUiModeManager.setNightModeActivated(newState);
// 更新该开关的状态
refreshState(newState);
}
根据上面的我们直接找 UiModeManager.java 的 setNightModeActivated() 方法:
UiModeManager#setNightModeActivated()
// UiModeManager.java
@RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
public boolean setNightModeActivated(boolean active) {
if (mService != null) {
try {
// mService 为 UiModeManagerService 的对象
return mService.setNightModeActivated(active);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return false;
}
UiModeManagerService#setNightModeActivated()
// UiModeManagerService.java
@Override
public boolean setNightModeActivated(boolean active) {
return setNightModeActivatedForModeInternal(mNightModeCustomType, active);
}
private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) {
// 省略部分代码......
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
// 自动、自定义的主题切换
if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
unregisterScreenOffEventLocked();
mOverrideNightModeOff = !active;
mOverrideNightModeOn = active;
mOverrideNightModeUser = mCurrentUser;
persistNightModeOverrides(mCurrentUser);
} else if (mNightMode == UiModeManager.MODE_NIGHT_NO
&& active) {
// 夜间模式
mNightMode = UiModeManager.MODE_NIGHT_YES;
} else if (mNightMode == UiModeManager.MODE_NIGHT_YES
&& !active) {
// 日间模式
mNightMode = UiModeManager.MODE_NIGHT_NO;
}
// 更新 Configuration
updateConfigurationLocked();
// 应用 Configuration
applyConfigurationExternallyLocked();
// 为当前用户 Secure.putIntForUser 配置
persistNightMode(mCurrentUser);
return true;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
根据上述代码这里主要看下 applyConfigurationExternallyLocked()、persistNightMode() 两个方法;
persistNightMode() 方法简单,先看 UiModeManagerService#persistNightMode()
// UiModeManagerService.java
private void persistNightMode(int user) {
// 如果不处于汽车模式,才去设置
if (mCarModeEnabled || mCar) return;
// 这里主要是将 mNightMode 的值 put 到数据库。
Secure.putIntForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE, mNightMode, user);
// 夜间模式自定义类型
Secure.putLongForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user);
// 定义自动夜间模式启动毫秒数
Secure.putLongForUser(getContext().getContentResolver(),
Secure.DARK_THEME_CUSTOM_START_TIME,
mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000, user);
// 自定义自动夜间模式结束毫秒
Secure.putLongForUser(getContext().getContentResolver(),
Secure.DARK_THEME_CUSTOM_END_TIME,
mCustomAutoNightModeEndMilliseconds.toNanoOfDay() / 1000, user);
}
再接着看 UiModeManagerService#applyConfigurationExternallyLocked() 方法:
// UiModeManagerService.java
private void applyConfigurationExternallyLocked() {
if (mSetUiMode != mConfiguration.uiMode) {
mSetUiMode = mConfiguration.uiMode;
// 清除快照缓存,
mWindowManager.clearSnapshotCache();
try {
// 这位置很重要,如果把这里注释了 就无法开机了,估计是没用主题了
ActivityTaskManager.getService().updateConfiguration(mConfiguration);
} catch (RemoteException e) {
Slog.w(TAG, "Failure communicating with activity manager", e);
} catch (SecurityException e) {
Slog.e(TAG, "Activity does not have the ", e);
}
}
}
上述代码将调到 ActivityTaskManagerService#updateConfiguration()
// ActivityTaskManagerService.java
@Override
public boolean updateConfiguration(Configuration values) {
mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
synchronized (mGlobalLock) {
if (mWindowManager == null) {
Slog.w(TAG, "Skip updateConfiguration because mWindowManager isn't set");
return false;
}
if (values == null) {
// 从窗口管理器获取当前配置
values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
}
mH.sendMessage(PooledLambda.obtainMessage(
ActivityManagerInternal::updateOomLevelsForDisplay, mAmInternal,
DEFAULT_DISPLAY));
final long origId = Binder.clearCallingIdentity();
try {
if (values != null) {
Settings.System.clearConfiguration(values);
}
// 重点关注这里。更新配置
updateConfigurationLocked(values, null, false, false /* persistent */,
UserHandle.USER_NULL, false /* deferResume */,
mTmpUpdateConfigurationResult);
return mTmpUpdateConfigurationResult.changes != 0;
} finally {
Binder.restoreCallingIdentity(origId);
}
}
}
boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
boolean initLocale, boolean persistent, int userId, boolean deferResume,
ActivityTaskManagerService.UpdateConfigurationResult result) {
int changes = 0;
boolean kept = true;
deferWindowLayout();
try {
if (values != null) {
// 更新全局配置
changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
}
if (!deferResume) {
// 更新后确保配置和可见性
kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
continueWindowLayout();
}
if (result != null) {
result.changes = changes;
result.activityRelaunched = !kept;
}
return kept;
}
这里直接看更新 updateGlobalConfigurationLocked() 方法:
// ActivityTaskManagerService.java
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId) {
// 省略部分代码......
SparseArray<WindowProcessController> pidMap = mProcessMap.getPidMap();
for (int i = pidMap.size() - 1; i >= 0; i--) {
final int pid = pidMap.keyAt(i);
final WindowProcessController app = pidMap.get(pid);
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "
+ "config %s", app.mName, mTempConfig);
// 通知每个进程配置更改
// 这句话注释掉 将无法切换主题模式,不管用。
// 只需要关注这里就行
app.onConfigurationChanged(mTempConfig);
}
final Message msg = PooledLambda.obtainMessage(
ActivityManagerInternal::broadcastGlobalConfigurationChanged,
mAmInternal, changes, initLocale);
mH.sendMessage(msg);
// 更新存储的全局配置并通知所有人有关更改。
// 这里注释了将无法开机,没用任何主题。
mRootWindowContainer.onConfigurationChanged(mTempConfig);
return changes;
}
上述代码跟进去,将会回调 WindowProcessController#onConfigurationChanged()
// WindowProcessController.java
@Override
public void onConfigurationChanged(Configuration newGlobalConfig) {
// super 父类 ConfigurationContainer
super.onConfigurationChanged(newGlobalConfig);
updateConfiguration();
}
接着看 ConfigurationContainer#onConfigurationChanged()
// ConfigurationContainer.java
public void onConfigurationChanged(Configuration newParentConfig) {
//临时configuration变量,保存更新之前的mResolvedOverrideConfiguration
mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
//更新mResolvedOverrideConfiguration,可重写此方法增添特殊配置
resolveOverrideConfiguration(newParentConfig);
mFullConfiguration.setTo(newParentConfig);
// mFullConfiguration 是实际更新后 configuration 的最终状态
mFullConfiguration.windowConfiguration.unsetAlwaysOnTop();
//根据差异更新最终状态:mFullConfiguration
mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
//合并此次修改,保存至mMergedOverrideConfiguration
onMergedOverrideConfigurationChanged();
if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
//进入此分支,代表特殊配置修改,通知监听者,
// 当第三方应用设置和默认系统主题不一样的时候,该位置是应用主动请求
for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
mChangeListeners.get(i).onRequestedOverrideConfigurationChanged(
mResolvedOverrideConfiguration);
}
}
Log.d("yexiao","yexiao:"+mChangeListeners.size());
for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
Log.d("yexiao","yexiao:"+mChangeListeners.get(i));
//通知监听者,已经合并父配置修改
mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
mMergedOverrideConfiguration);
}
for (int i = getChildCount() - 1; i >= 0; --i) {
//传达configuration最终状态到子类
// 这个方法也很重要,但目前不清楚作用,注释掉将无法开机,
dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
}
}
这里看一个堆栈:
12-18 15:29:40.717 3118 3118 D yexiao : java.lang.Throwable
12-18 15:29:40.717 3118 3118 D yexiao : at android.content.res.AssetManager.rebaseTheme(AssetManager.java:1243)
12-18 15:29:40.717 3118 3118 D yexiao : at android.content.res.ResourcesImpl$ThemeImpl.rebase(ResourcesImpl.java:1457)
12-18 15:29:40.717 3118 3118 D yexiao : at android.content.res.Resources$Theme.rebase(Resources.java:1874)
12-18 15:29:40.717 3118 3118 D yexiao : at android.content.res.Resources.setImpl(Resources.java:372)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.ResourcesManager.updateResourcesForActivity(ResourcesManager.java:1224)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.ResourcesManager.createBaseTokenResources(ResourcesManager.java:867)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.ContextImpl.createActivityContext(ContextImpl.java:3148)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.ActivityThread.createBaseContextForActivity(ActivityThread.java:3799)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3605)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3853)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:5832)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:5723)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:71)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2345)
12-18 15:29:40.717 3118 3118 D yexiao : at android.os.Handler.dispatchMessage(Handler.java:106)
12-18 15:29:40.717 3118 3118 D yexiao : at android.os.Looper.loopOnce(Looper.java:208)
12-18 15:29:40.717 3118 3118 D yexiao : at android.os.Looper.loop(Looper.java:295)
12-18 15:29:40.717 3118 3118 D yexiao : at android.app.ActivityThread.main(ActivityThread.java:7941)
12-18 15:29:40.717 3118 3118 D yexiao : at java.lang.reflect.Method.invoke(Native Method)
12-18 15:29:40.717 3118 3118 D yexiao : at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:569)
12-18 15:29:40.717 3118 3118 D yexiao : at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1015)
配置改变后通过 binder 调用 IApplicationThread.scheduleTransaction() 方法。
留下问题:配置改变后通过 binder 调用,这中间的流程是怎样的?
ClientTransaction#schedule()
// ClientTransaction.java
public void schedule() throws RemoteException {
mClient.scheduleTransaction(this);
}
然后配置改变消息会进入应用进程,经过ActivityThread.H发送消息,执行 mTransactionExecutor.execute(transaction),进入 TransactionExecutor#executeCallbacks() 方法:
// TransactionExecutor.java
@VisibleForTesting
public void executeCallbacks(ClientTransaction transaction) {
// 省略部分代码......
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
// 省略部分代码......
// 重点关注这里,
item.execute(mTransactionHandler, token, mPendingActions);
item.postExecute(mTransactionHandler, token, mPendingActions);
// 省略部分代码......
}
}
ClientTransactionItem 的实现方法在 ActivityTransactionItem 中,所以进入到 ActivityTransactionItem#execute()
// ActivityTransactionItem.java
@Override
public final void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
final ActivityClientRecord r = getActivityClientRecord(client, token);
// 抽象方法,在 ActivityRelaunchItem.java 中被实现。
execute(client, r, pendingActions);
}
ActivityRelaunchItem#execute()
// ActivityRelaunchItem.java
@Override
public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
if (mActivityClientRecord == null) {
if (DEBUG_ORDER) Slog.d(TAG, "Activity relaunch cancelled");
return;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
// 重点关注这里
client.handleRelaunchActivity(mActivityClientRecord, pendingActions);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
后面就不详细分析了;
后续流程:ActivityThread#handleRelaunchActivity() → ActivityThread#handleRelaunchActivityInner() → ActivityThread#handleLaunchActivity() → ActivityThread#performLaunchActivity()→ ActivityThread#createBaseContextForActivity() → ContextImpl #createActivityContext() → ResourcesManager#createBaseTokenResources() → ResourcesManager#updateResourcesForActivity() → Resources #setImpl() → Resources#Theme.rebase() → AssetManager#rebaseTheme()
这里注意:
在ActivityThread.java
中有performActivityConfigurationChanged()
和performLaunchActivity()
两个方法,都可以更新资源主题,我个人认为一个是配置单独某个应用的,一个是配置全局的。
到此完成应用进程回调。
那么系统进程如何传送配置信息到应用进程?
这里回到 ActivityTaskManagerService.java。通过ensureConfigAndVisibilityAfterUpdate方法,确保目前启动的activity,重启来加载新的资源
ActivityTaskManagerService#ensureConfigAndVisibilityAfterUpdate()
// ActivityTaskManagerService.java
boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
boolean kept = true;
final Task mainRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
// mainRootTask is null during startup.
if (mainRootTask != null) {
if (changes != 0 && starting == null) {
// 如果配置改变了,并且调用者还没有在启动一个活动的过程中,
// 那么找到最上面的活动来检查它的配置是否需要改变
starting = mainRootTask.topRunningActivity();
}
if (starting != null) {
// 重点关注这里
kept = starting.ensureActivityConfiguration(changes,
false /* preserveWindow */);
mRootWindowContainer.ensureActivitiesVisible(starting, changes,
!PRESERVE_WINDOWS);
}
}
return kept;
}
starting 是 ActivityRecord 对象,所有看 ActivityRecord #ensureActivityConfiguration()
// ActivityRecord.java
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
boolean ignoreVisibility) {
// 省略部分代码......
if (changes == 0 && !forceNewConfig) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration no differences in %s",
this);
// 不relaunch 时需要去走 scheduleConfigurationChanged让Activity执行onConfiguration的流程
if (displayChanged) {
scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
} else {
scheduleConfigurationChanged(newMergedOverrideConfig);
}
return true;
}
// 省略部分代码......
return true;
}
displayChanged未改变的前提下,走 scheduleConfigurationChanged(),通知应用进程。
ActivityRecord#scheduleConfigurationChanged
// ActivityRecord.java
private void scheduleConfigurationChanged(Configuration config) {
if (!attachedToProcess()) {
ProtoLog.w(WM_DEBUG_CONFIGURATION, "Can't report activity configuration "
+ "update - client not running, activityRecord=%s", this);
return;
}
try {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
+ "config: %s", this, config);
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
ActivityConfigurationChangeItem.obtain(config));
} catch (RemoteException e) {
// If process died, whatever.
}
}
至此,应用进程可以根据新配置更新布局等信息。