模仿系统设置Settings实现深色主题开关切换功能。
文章目录
- 前言
- [一、 需求](#一、 需求)
- [二、 实现方案](#二、 实现方案)
-
- [UI 分析](#UI 分析)
- [控制器 UiModeManager - 控制深色主体模式-控制api - UiModeManager-setNightModeActivated](#控制器 UiModeManager - 控制深色主体模式-控制api - UiModeManager-setNightModeActivated)
- 三、实现方案
- 四、知识点扩展
-
- [1、找到- 控制模式对应的服务-UiModeManagerService](#1、找到- 控制模式对应的服务-UiModeManagerService)
- 2、控制模式对应的服务-UiModeManagerService-控制模式
-
- [setNightMode 控制](#setNightMode 控制)
- [setNightModeInternal(int mode, int customModeType) 控制](#setNightModeInternal(int mode, int customModeType) 控制)
-
- [方法 resetNightModeOverrideLocked()-persistNightModeOverrides()](#方法 resetNightModeOverrideLocked()-persistNightModeOverrides())
- [方法 persistNightMode(user);](#方法 persistNightMode(user);)
- 小结-数据库属性-控制-persistNightModeOverrides-persistNightMode
- 3、第一次开机追踪设置模块SettingProvider-系统初始化-默认属性值
-
- [定义 Secure.UI_NIGHT_MODE 定义到数据库 属性](#定义 Secure.UI_NIGHT_MODE 定义到数据库 属性)
- [相关设置Secure.UI_NIGHT_MODE 定义](#相关设置Secure.UI_NIGHT_MODE 定义)
- [SettingProvider-系统属性Secure.UI_NIGHT_MODE - 小结](#SettingProvider-系统属性Secure.UI_NIGHT_MODE - 小结)
- [4、思考-UI_NIGHT_MODE -默认值到底在哪里设置的](#4、思考-UI_NIGHT_MODE -默认值到底在哪里设置的)
-
- [再看UiModeManagerService-framework 层服务](#再看UiModeManagerService-framework 层服务)
- framework层查看默认配置文件:frameworks/base/core/res/res/values/config.xml
- 总结
前言
应用层实现系统设置深色主体切换功能。
如下,系统设置里面是有一个深色主题开关的功能。 如下:


当开关打开时候,也就是在使用深色主题时候,界面样子:


为什么会有深色主体一说,有人会问 实际显示出来并不友好:它存在主要用于 夜间模式+色温调节 来实现光感友好,特别是夜间减轻用户眼睛疲劳。
一、 需求
在自己开发的应用中,实现深色主体切换方案,自己开发的应用能够模拟系统设置来实现 是否 深色主题开关切换的功能。
二、 实现方案
UI 分析
UI界面
进入系统设置界面,进入深色主体界面,如下:

查看 界面:

开关控制器-DarkModeActivationPreferenceController
如上通过日志 大概 已经分析到了控制器源码类,看到了深色主题开关类的控制部分:mUiModeManager.setNightModeActivated

控制器 UiModeManager - 控制深色主体模式-控制api - UiModeManager-setNightModeActivated
java
/**
* Activating night mode for the current user
*
* @return {@code true} if the change is successful
* @hide
*/
@RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
public boolean setNightModeActivated(boolean active) {
if (mService != null) {
try {
return mService.setNightModeActivated(active);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return false;
}
所以最终通过 frameworks//base/core/java/android/app/UiModeManager.java 来调用api 实现开关状态切换。
三、实现方案
如上已经分析了控制代码,找到了 UiModeManager.java 的 setNightModeActivated 方法,它是一个 hide 类型修饰的方法,那么直接反射不就实现了嘛。
权限相关配置
在自己应用中 填写相关权限,并配置system.uid 属性
java
android:sharedUserId="android.uid.system"
<uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
反射设置深色主题开关
java
viewBinding.dartMode.setOnClickListener {
Log.d(TAG,"============dartMode=======")
// LightModeHelper.trySetConfiguration(true)
try {
val uiModeManager = getSystemService(Context.UI_MODE_SERVICE)
val method = UiModeManager::class.java.getMethod("setNightModeActivated", Boolean::class.java)
method.invoke(uiModeManager, true)
} catch (e: Exception) {
e.printStackTrace()
}
}
viewBinding.noDartMode.setOnClickListener {
Log.d(TAG,"============noDartMode=======")
try {
val uiModeManager = getSystemService(Context.UI_MODE_SERVICE)
val method = UiModeManager::class.java.getMethod("setNightModeActivated", Boolean::class.java)
method.invoke(uiModeManager, false)
} catch (e: Exception) {
e.printStackTrace()
}
}
反射获取深色主题开关状态-getNightMode
如上,既然能够设置深色主体开关,那么一定有获取当前深色主题开关状态的api.

这里 看 api 是对外公开的,可以不用反射,直接拿着对象,调用即可:

这里我们还是用反射也行,如下:
java
fun getSystemNightMode(context: Context): Int {
return try {
val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
val method = UiModeManager::class.java.getDeclaredMethod("getNightMode")
method.isAccessible = true
method.invoke(uiModeManager) as Int
} catch (e: Exception) {
e.printStackTrace()
-1
}
}
四、知识点扩展
如上已经分析了相关api ,调用,如下我们进一步看看系统源码。
1、找到- 控制模式对应的服务-UiModeManagerService
从安卓系统架构来说App->ServiceManager(framework 层服务对外公开的接口)->UiModeManagerService(framework 层系统服务)->aidl (底层aidl 调用)
从UiModeManager.java中查看api 调用如下:





2、控制模式对应的服务-UiModeManagerService-控制模式
路径:frameorks/base/services/core/java/com/android/server/UiModeManagerService.java
setNightMode 控制
java
@Override
public void setNightMode(int mode) {
// MODE_NIGHT_CUSTOM_TYPE_SCHEDULE is the default for MODE_NIGHT_CUSTOM.
int customModeType = mode == MODE_NIGHT_CUSTOM
? MODE_NIGHT_CUSTOM_TYPE_SCHEDULE
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
setNightModeInternal(mode, customModeType);
}
setNightModeInternal(int mode, int customModeType) 控制
java
private void setNightModeInternal(int mode, int customModeType) {
if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
!= PackageManager.PERMISSION_GRANTED)) {
Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission");
return;
}
switch (mode) {
case UiModeManager.MODE_NIGHT_NO:
case UiModeManager.MODE_NIGHT_YES:
case MODE_NIGHT_AUTO:
break;
case MODE_NIGHT_CUSTOM:
if (SUPPORTED_NIGHT_MODE_CUSTOM_TYPES.contains(customModeType)) {
break;
}
throw new IllegalArgumentException(
"Can't set the custom type to " + customModeType);
default:
throw new IllegalArgumentException("Unknown mode: " + mode);
}
final int user = UserHandle.getCallingUserId();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
if (mNightMode != mode || mNightModeCustomType != customModeType) {
if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
unregisterScreenOffEventLocked();
cancelCustomAlarm();
}
mNightModeCustomType = mode == MODE_NIGHT_CUSTOM
? customModeType
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
mNightMode = mode;
resetNightModeOverrideLocked();
persistNightMode(user);
// on screen off will update configuration instead
if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM)
|| shouldApplyAutomaticChangesImmediately()) {
unregisterScreenOffEventLocked();
updateLocked(0, 0);
} else {
registerScreenOffEventLocked();
}
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
方法 resetNightModeOverrideLocked()-persistNightModeOverrides()
java
private boolean resetNightModeOverrideLocked() {
if (mOverrideNightModeOff || mOverrideNightModeOn) {
mOverrideNightModeOff = false;
mOverrideNightModeOn = false;
persistNightModeOverrides(mOverrideNightModeUser);
mOverrideNightModeUser = USER_SYSTEM;
return true;
}
return false;
}
java
private void persistNightModeOverrides(int user) {
// Only persist setting if not in car mode
if (mCarModeEnabled || mCar) return;
Secure.putIntForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE_OVERRIDE_ON, mOverrideNightModeOn ? 1 : 0, user);
Secure.putIntForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE_OVERRIDE_OFF, mOverrideNightModeOff ? 1 : 0, user);
}
方法 persistNightMode(user);
java
private void persistNightMode(int user) {
// Only persist setting if not in car mode
if (mCarModeEnabled || mCar) return;
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);
}
小结-数据库属性-控制-persistNightModeOverrides-persistNightMode
如上方法,已经分析到了,最终是控制属性:UI_NIGHT_MODE_OVERRIDE_ON UI_NIGHT_MODE
路径:frameworks/base/core/java/android/provider/Settings.java

3、第一次开机追踪设置模块SettingProvider-系统初始化-默认属性值
如上已经找到了framework层 设置 深色主体模式开关源码分析。 结论就是 通过 Secure.UI_NIGHT_MODE 数据库属性字段值来控制,那么接下来就是找到默认值,进而找到系统默认配置值地方。
定义 Secure.UI_NIGHT_MODE 定义到数据库 属性

路径:\vendor\mediatek\proprietary\packages\apps\SettingsProvider\src\android\provider\settings\backup


相关设置Secure.UI_NIGHT_MODE 定义
路径:\vendor\mediatek\proprietary\packages\apps\SettingsProvider\src\com\android\providers\settings\SettingsProtoDumpUtil.java

SettingProvider-系统属性Secure.UI_NIGHT_MODE - 小结
追踪源码发现,其实系统设置里面只是做了定义属性key ,并没有默认初始值。
4、思考-UI_NIGHT_MODE -默认值到底在哪里设置的
再看UiModeManagerService-framework 层服务
在服务中启动时候,获取了一次值,这个值是framework层的默认值。

更新这个值 然后设置到系统数据库属性里面去:
java
private void updateSystemProperties() {
int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
mNightMode, 0);
if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
mode = MODE_NIGHT_YES;
}
SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
}
所以,直接修改framework 层默认值即可:
framework层查看默认配置文件:frameworks/base/core/res/res/values/config.xml
路径:frameworks/base/core/res/res/values/config.xml

java
<!-- Control the default night mode to use when there is no other mode override set.
One of the following values (see UiModeManager.java):
0 - MODE_NIGHT_AUTO
1 - MODE_NIGHT_NO
2 - MODE_NIGHT_YES
-->
<integer name="config_defaultNightMode">1</integer>
总结
- 这里其实就是分析了设置 深色主题开关 - 分析源码-分析流程
- 透过流程:类比基本上其它很多很多大量的案例来说,
Settings数据库定义属性数据库、framework层config.xml默认数据初始化值。 默认获取config.xml文件中配置的初始化值,再动态设置到系统数据库Settings中去。 最后整个数据库系统通过获取数据库的值来判断当前状态。 - 当然,很多时候我们会默认系统数据库的值, 这里深色主题并没有在系统数据库中去设置。
- 所以:修改默认值 我们有两个思路:直接更改framework 层数据默认值;或者 在系统设置数据中去初始化系统数据库属性值。