Android 13 深色主题切换流程解析

学习笔记:Android小白,这位置网上没资料,通过自己打日志阅读代码走的流程,可能有理解错误的地方。欢迎指正,大家共同进步。

深色主题设置方法:两种设置方法流程是一样的。

  • 通过下拉状态栏的快捷按钮深色主题切换;
  • 通过 设置→显示→深色主题开关 切换;

本文以下拉状态栏的快捷按钮深色主题切换为例;

该快捷按钮类为 UiModeNightTile.java,直接看点击事件:

frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java

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()
frameworks/base/core/java/android/app/UiModeManager.java

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()
frameworks/base/services/core/java/com/android/server/UiModeManagerService.java

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()
frameworks/base/services/core/java/com/android/server/UiModeManagerService.java

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() 方法:
frameworks/base/services/core/java/com/android/server/UiModeManagerService.java

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()
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

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() 方法:
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

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()
frameworks/base/services/core/java/com/android/server/wm/WindowProcessController.java

java 复制代码
    @Override
    public void onConfigurationChanged(Configuration newGlobalConfig) {
        // super 父类 ConfigurationContainer
        super.onConfigurationChanged(newGlobalConfig);
        updateConfiguration();
    }

接着看 ConfigurationContainer#onConfigurationChanged()
frameworks/base/services/core/java/com/android/server/wm/ConfigurationContainer.java

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("yeruilai","yeruilai:"+mChangeListeners.size());
        for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
            Log.d("yeruilai","yeruilai:"+mChangeListeners.get(i));
            //通知监听者,已经合并父配置修改
            mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
                    mMergedOverrideConfiguration);
        }
        for (int i = getChildCount() - 1; i >= 0; --i) {
            //传达configuration最终状态到子类
            // 这个方法也很重要,但目前不清楚作用,注释掉将无法开机,
            dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
        }
    }

这里看一个堆栈:

java 复制代码
09-24 16:35:18.717  3118  3118 D yeruilai  : java.lang.Throwable
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.content.res.AssetManager.rebaseTheme(AssetManager.java:1243)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.content.res.ResourcesImpl$ThemeImpl.rebase(ResourcesImpl.java:1457)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.content.res.Resources$Theme.rebase(Resources.java:1874)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.content.res.Resources.setImpl(Resources.java:372)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.ResourcesManager.updateResourcesForActivity(ResourcesManager.java:1224)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.ResourcesManager.createBaseTokenResources(ResourcesManager.java:867)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.ContextImpl.createActivityContext(ContextImpl.java:3148)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.ActivityThread.createBaseContextForActivity(ActivityThread.java:3799)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3605)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3853)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:5832)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:5723)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:71)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2345)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.os.Handler.dispatchMessage(Handler.java:106)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.os.Looper.loopOnce(Looper.java:208)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.os.Looper.loop(Looper.java:295)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at android.app.ActivityThread.main(ActivityThread.java:7941)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at java.lang.reflect.Method.invoke(Native Method)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:569)
09-24 16:35:18.717  3118  3118 D yeruilai  :      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1015)

配置改变后通过 binder 调用 IApplicationThread.scheduleTransaction() 方法。

留下问题:配置改变后通过 binder 调用,这中间的流程是怎样的?

ClientTransaction#schedule()
frameworks/base/core/java/android/app/servertransaction/ClientTransaction.java

java 复制代码
    public void schedule() throws RemoteException {
        mClient.scheduleTransaction(this);
    }

然后配置改变消息会进入应用进程,经过ActivityThread.H发送消息,执行 mTransactionExecutor.execute(transaction),进入 TransactionExecutor#executeCallbacks() 方法:
frameworks/base/core/java/android/app/servertransaction/TransactionExecutor.java

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()
frameworks/base/core/java/android/app/servertransaction/ActivityTransactionItem.java

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()
frameworks/base/core/java/android/app/servertransaction/ActivityRelaunchItem.java

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()
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

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()
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

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
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

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.
        }
    }

至此,应用进程可以根据新配置更新布局等信息。

👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀

相关推荐
*才华有限公司*29 分钟前
安卓前后端连接教程
android
氦客1 小时前
Android Compose中的附带效应
android·compose·effect·jetpack·composable·附带效应·side effect
雨白1 小时前
Kotlin 协程的灵魂:结构化并发详解
android·kotlin
我命由我123452 小时前
Android 开发问题:getLeft、getRight、getTop、getBottom 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Modu_MrLiu2 小时前
Android实战进阶 - 用户闲置超时自动退出登录功能详解
android·超时保护·实战进阶·长时间未操作超时保护·闲置超时
Jeled2 小时前
Android 网络层最佳实践:Retrofit + OkHttp 封装与实战
android·okhttp·kotlin·android studio·retrofit
信田君95272 小时前
瑞莎星瑞(Radxa Orion O6) 基于 Android OS 使用 NPU的图片模糊查找APP 开发
android·人工智能·深度学习·神经网络
tangweiguo030519872 小时前
Kotlin 实现 Android 网络状态检测工具类
android·网络·kotlin
nvvas4 小时前
Android Studio JAVA开发按钮跳转功能
android·java·android studio
怪兽20144 小时前
Android多进程通信机制
android·面试