美文网首页
Android 13 深色主题切换流程解析

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

作者: 龙之叶 | 来源:发表于2023-10-20 12:22 被阅读0次

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

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

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

本文以下拉状态栏的快捷按钮深色主题切换为例;
该快捷按钮类为 UiModeNightTile.java,直接看点击事件:

frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/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()
frameworks/base/core/java/android/app/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()
frameworks/base/services/core/java/com/android/server/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()
frameworks/base/services/core/java/com/android/server/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() 方法:
frameworks/base/services/core/java/com/android/server/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()
frameworks/base/services/core/java/com/android/server/wm/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() 方法:
frameworks/base/services/core/java/com/android/server/wm/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()
frameworks/base/services/core/java/com/android/server/wm/WindowProcessController.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

    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);
        }
    }

这里看一个堆栈:

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

    public void schedule() throws RemoteException {
        mClient.scheduleTransaction(this);
    }

然后配置改变消息会进入应用进程,经过ActivityThread.H发送消息,执行 mTransactionExecutor.execute(transaction),进入 TransactionExecutor#executeCallbacks() 方法:
frameworks/base/core/java/android/app/servertransaction/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()
frameworks/base/core/java/android/app/servertransaction/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()
frameworks/base/core/java/android/app/servertransaction/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()
frameworks/base/services/core/java/com/android/server/wm/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()
frameworks/base/services/core/java/com/android/server/wm/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
frameworks/base/services/core/java/com/android/server/wm/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.
        }
    }

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

相关文章

  • Android 13 深色主题切换流程

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

  • uni-app做iOS的夜间模式

    iOS13适配暗黑模式/夜间模式/深色模式/暗黑主题(DarkMode)问题:1.监听Android深色或浅色模式...

  • 2019-04-04 Android 切换主题 (二)

    Android 切换主题 (二) 背景 我原来写过一篇文章关于 android 切换主题的文章 --Android...

  • Android Q 深色主题举例

    Android Q 深色主题举例 了解深色主题如何应用,第一手资料是 官方文档[https://developer...

  • Android 深色主题适配

    Android10系统开始,谷歌引入深色主题的特性,适配方案有两种,第一种是自动强制适配方案(Force Dark...

  • Android — 深色主题适配

    前言 Google 在 Android 10 中加入了深色主题背景,妈妈再也不用担心我晚上玩手机变成瞎子了: 减少...

  • Android--Binder源码解析(二)

    Binder源码解析(二) ServiceManager的启动流程 分析Android启动流程可知,Android...

  • SAP ABAP Development Tool 如何设置黑色

    SAP ABAP Development Tool 默认是浅色主题,有朋友询问如何切换成深色主题。 其实步骤非常容...

  • Hexo博客实现深色主题切换

    需求背景 开源应用内嵌个人博客,由于应用做了深色主题兼容,然而当前博客不支持深色主题,导致夜间查看时屏幕很刺眼。遂...

  • Android切换主题

    values-v21/styles.xml 切换主题: 效果图: 源码地址:https://github.com/...

网友评论

      本文标题:Android 13 深色主题切换流程解析

      本文链接:https://www.haomeiwen.com/subject/pnmhidtx.html