Android 13 深色主题切换流程

简介: 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.
        }
    }

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

相关文章
|
15天前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
66 6
|
14天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
16天前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
Android面试高频知识点(4) 详解Activity的启动流程
22 3
|
17天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
19 2
|
30天前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
33 3
|
16天前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
13 0
|
2月前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
讲解Activity的启动流程了,Activity的启动流程相对复杂一下,涉及到了Activity中的生命周期方法,涉及到了Android体系的CS模式,涉及到了Android中进程通讯Binder机制等等, 首先介绍一下Activity,这里引用一下Android guide中对Activity的介绍:
49 4
|
2月前
|
Android开发 开发者
Android面试之Activity启动流程简述
每个Android开发者都熟悉的Activity,但你是否了解它的启动流程呢?本文将带你深入了解。启动流程涉及四个关键角色:Launcher进程、SystemServer的AMS、应用程序的ActivityThread及Zygote进程。核心在于AMS与ActivityThread间的通信。文章详细解析了从Launcher启动Activity的过程,包括通过AIDL获取AMS、Zygote进程启动以及ActivityThread与AMS的通信机制。接着介绍了如何创建Application及Activity的具体步骤。整体流程清晰明了,帮助你更深入理解Activity的工作原理。
46 0
|
3月前
|
Android开发
我的Android进阶修炼:安卓启动流程之init(1)
本文深入分析了Android系统中的init进程,包括其源码结构、主要功能以及启动流程的详细注解,旨在帮助读者理解init作为用户空间的1号进程在Android启动过程中的关键作用。
56 1
|
2月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
147 0