16、设置与系统偏好:Settings Panel、CarSetting、系统偏好存储、用户配置管理
说实话,做车载系统开发这么多年,我见过太多团队在「设置」这个模块上栽跟头。有人觉得不就是个开关列表嘛,随便写写就行。结果呢?用户配置一多,存储乱套,多用户切换时数据打架,最后不得不返工重写。
今天我们就来聊聊 Android Automotive OS 里的设置体系。我会把我在项目中踩过的坑、总结的经验,都摊开来讲。
16.1 设置的整体架构
Android Automotive 的设置,说白了分三层:
- Settings Panel:系统级快捷面板,下拉就能调亮度、音量、WiFi 开关
- CarSetting:深度设置应用,类似手机里的「设置」App
- 系统偏好存储:底层数据怎么存、怎么读
这三层的关系,我画了张图,你看一眼就明白了。
16.2 Settings Panel:快捷面板的秘密
Settings Panel 是用户最先接触到的设置入口。下拉状态栏,那些开关按钮就是它。
我记得第一次看 Automotive 的 Settings Panel 代码时,发现它跟手机上的完全不一样。手机上是 SystemUI 的一部分,车载上却是独立的 Service。
核心类就两个:
SettingsPanelService:管理面板的显示与隐藏SettingsPanelView:实际的 UI 布局
启动方式很简单:
// 显示快捷面板
Intent intent = new Intent(SettingsPanelService.ACTION_SHOW_PANEL);
intent.putExtra(SettingsPanelService.EXTRA_PANEL_TYPE,
SettingsPanelService.PANEL_TYPE_QUICK_SETTINGS);
startService(intent);
16.3 CarSetting:深度设置应用
CarSetting 才是真正的设置主战场。它继承自 SettingsActivity,但做了大量车载定制。
你打开 CarSetting,看到的每个分类其实都是一个 PreferenceFragment。比如:
| Fragment 类名 | 对应设置项 |
|---|---|
DisplaySettingsFragment |
亮度、字体大小、深色模式 |
SoundSettingsFragment |
音量、均衡器、音效 |
NetworkSettingsFragment |
WiFi、蓝牙、移动网络 |
AccountSettingsFragment |
用户账户、同步 |
VehicleSettingsFragment |
车门锁、灯光、空调 |
每个 Fragment 里用 PreferenceScreen 组织 UI。举个例子:
public class DisplaySettingsFragment extends PreferenceFragment {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.display_preferences, rootKey);
// 亮度调节
SeekBarPreference brightnessPref = findPreference("brightness");
brightnessPref.setOnPreferenceChangeListener((pref, newValue) -> {
int brightness = (int) newValue;
// 写入系统设置
Settings.System.putInt(getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS, brightness);
return true;
});
}
}
16.4 系统偏好存储:数据到底放哪?
设置数据存哪里?这是个老生常谈的问题。Android Automotive 提供了三种方式:
- SharedPreferences:适合小量、应用私有的配置
- SettingsProvider:系统级设置,跨应用共享
- CarPropertyManager:车辆硬件属性,比如车门状态、空调温度
我个人的习惯是:
- 用户偏好(比如主题、字体大小)→
SharedPreferences - 系统级开关(WiFi、蓝牙)→
Settings.Global或Settings.System - 车辆硬件状态 →
CarPropertyManager
举个例子,保存用户选择的深色模式:
// 保存
SharedPreferences prefs = getSharedPreferences("user_prefs", Context.MODE_PRIVATE);
prefs.edit().putBoolean("dark_mode", true).apply();
// 读取
boolean isDarkMode = prefs.getBoolean("dark_mode", false);
如果是系统级设置,用 SettingsProvider:
// 写入
Settings.Global.putInt(getContentResolver(),
"car_dark_mode", 1);
// 读取
int value = Settings.Global.getInt(getContentResolver(),
"car_dark_mode", 0);
16.5 用户配置管理:多用户场景的坑
车载系统跟手机最大的不同,就是多用户。一辆车可能被家庭多人使用,每个人都有自己的设置偏好。
Android Automotive 用 UserManager 管理用户。每个用户有自己的数据目录,SharedPreferences 天然隔离。但 SettingsProvider 是全局的,需要手动处理。
我曾经在项目里遇到一个问题:用户 A 调整了座椅位置,用户 B 上车后座椅还是 A 的位置。原因就是座椅数据存到了全局 SettingsProvider,没有按用户隔离。
解决方案是这样的:
// 获取当前用户
UserHandle user = Process.myUserHandle();
// 按用户存储
String key = "seat_position_" + user.getIdentifier();
Settings.System.putInt(getContentResolver(), key, position);
// 读取时也按用户
int savedPosition = Settings.System.getInt(getContentResolver(),
"seat_position_" + user.getIdentifier(), 0);
更规范的做法是使用 Settings.System 的 putStringForUser 方法:
Settings.System.putStringForUser(
getContentResolver(),
"seat_position",
String.valueOf(position),
user.getIdentifier());
Settings.Global 存用户偏好,结果用户切换后数据全乱了。后来改成 Settings.System + 按用户隔离,才解决问题。记住:Settings.Global 是所有用户共享的,Settings.System 可以按用户隔离。
16.6 实战:实现一个用户配置切换
说了这么多,我们来写个实际例子。假设我们要实现用户切换时,自动加载该用户的座椅位置和空调温度。
public class UserConfigManager {
private Context context;
private ContentResolver resolver;
public UserConfigManager(Context context) {
this.context = context;
this.resolver = context.getContentResolver();
}
// 保存当前用户配置
public void saveUserConfig(UserHandle user, int seatPos, int acTemp) {
Settings.System.putStringForUser(resolver,
"seat_position", String.valueOf(seatPos), user.getIdentifier());
Settings.System.putStringForUser(resolver,
"ac_temperature", String.valueOf(acTemp), user.getIdentifier());
}
// 加载指定用户配置
public UserConfig loadUserConfig(UserHandle user) {
String seatStr = Settings.System.getStringForUser(resolver,
"seat_position", user.getIdentifier());
String acStr = Settings.System.getStringForUser(resolver,
"ac_temperature", user.getIdentifier());
UserConfig config = new UserConfig();
config.seatPosition = seatStr != null ? Integer.parseInt(seatStr) : 0;
config.acTemperature = acStr != null ? Integer.parseInt(acStr) : 24;
return config;
}
static class UserConfig {
int seatPosition;
int acTemperature;
}
}
当用户切换时,监听 ACTION_USER_SWITCHED 广播:
BroadcastReceiver userSwitchReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
UserHandle newUser = UserHandle.of(newUserId);
UserConfig config = userConfigManager.loadUserConfig(newUser);
// 应用配置到车辆硬件
applySeatPosition(config.seatPosition);
applyAcTemperature(config.acTemperature);
}
}
};
16.7 总结一下
设置与系统偏好这块,说白了就三件事:
- 快捷面板给用户快速操作
- CarSetting 提供深度配置
- 存储层保证数据不丢、不乱
多用户场景下,一定要做好数据隔离。别问我怎么知道的,问就是我改过太多这种 Bug。
嗯,今天就到这里。代码都在上面了,你拿去用,有问题随时交流。