定制 Launcher:SystemUI 修改、默认应用设置、开机向导定制
好,咱们今天聊点真正能让你手里的 ROM 变得「不一样」的东西。Launcher、SystemUI、开机向导——这三个东西,说白了就是用户对一台 Android 设备的第一印象和日常交互的全部。我当年第一次给一款教育平板做定制 ROM 时,甲方就提了一个要求:「开机后不能看到任何 Google 的影子,桌面要像一本翻开的书。」嗯,那会儿我才意识到,光会编译 AOSP 远远不够,你得会「动刀子」。
Launcher 定制:从换图标到改布局
Launcher 就是桌面。AOSP 自带的 Launcher3 其实挺干净的,但你要做定制 ROM,肯定得改。改什么?
- 默认图标包:替换
res/drawable下的图标资源 - 桌面网格:修改
InvariantDeviceProfile.java中的行列数 - Dock 栏行为:是否允许添加应用、是否显示搜索框
- 手势与动画:比如上滑抽屉、下拉通知栏的联动
我个人习惯先把 Launcher3 的源码单独拉出来,放到 packages/apps/ 下,然后直接改。举个例子,你想把默认桌面改成 5x6 的网格:
// InvariantDeviceProfile.java
public static final InvariantDeviceProfile[] DEVICE_PROFILES = {
new InvariantDeviceProfile("Nexus 5", 5, 6, 4, 4, 3, 2, 2, 480, 800, 320, 2),
// ...
};
改完重新编译 make Launcher3,刷进去就能看到效果。我在项目中遇到过一个问题:改了网格后,某些小部件显示不全。后来发现是 GridSize 和 IconSize 没同步调整。你想想看,图标大小不变,格子却变小了,肯定要出问题。
config.xml 中的 grid_size_x 和 grid_size_y,比直接改 Java 代码更安全,也更容易维护。
SystemUI 修改:状态栏、快捷开关、锁屏
SystemUI 是 Android 系统的「脸面」。状态栏、通知栏、快捷开关、锁屏、音量面板……全在这里。我建议你把它当成一个独立的 App 来理解——它其实就是一个系统级应用,只不过权限高一些。
常见的定制点:
- 状态栏图标:替换
SystemUI/res/drawable下的电池、信号、WiFi 图标 - 快捷开关布局:修改
QuickQSPanel.java和QSPanel.java中的 tile 数量 - 锁屏界面:去掉默认的相机快捷方式、修改时钟样式
- 音量面板:改成垂直布局或加入媒体输出选择
举个例子,你想在状态栏右侧显示实时网速。这个功能在 AOSP 里默认是关闭的,但代码其实已经写好了。你只需要在 SystemUI/res/values/config.xml 里把 config_showNetworkSpeed 改成 true:
<bool name="config_showNetworkSpeed">true</bool>
然后重新编译 make SystemUI。嗯,就这么简单。但要注意,有些厂商的 ROM 会把这个功能删掉,你得自己加回去。我曾经在某个项目里找了半天没找到这个配置,最后发现是厂商把整个 NetworkSpeedController 类都移除了……
SystemUI/res/values/strings.xml 中的字符串资源,否则可能出现英文和中文混排的情况。
默认应用设置:让用户「无脑」使用
定制 ROM 的一大目标就是让用户开机后直接能用,不需要手动设置默认应用。比如:默认浏览器、默认短信、默认桌面、默认拨号器。
这些默认值定义在 frameworks/base/core/res/res/values/config.xml 中:
<string-array name="config_defaultLauncher">
<item>com.android.launcher3/com.android.launcher3.Launcher</item>
</string-array>
<string-array name="config_defaultBrowser">
<item>com.android.browser/com.android.browser.BrowserActivity</item>
</string-array>
如果你想把自己的 Launcher 设为默认,就把包名和 Activity 路径填进去。注意,这里填的是 ComponentName 格式,不是单纯的包名。
我遇到过一个问题:明明改了配置,但开机后还是弹出选择对话框。后来发现是 PackageManager 在第一次启动时会扫描所有应用,如果发现多个 Launcher,就会让用户选。解决办法是在 DevicePolicyManager 中设置 setApplicationHidden 把其他 Launcher 隐藏掉,或者干脆只保留一个。
开机向导定制:让第一次开机变得优雅
开机向导(SetupWizard)是用户拿到设备后看到的第一个界面。AOSP 自带的 SetupWizard 功能很基础:选语言、连 WiFi、登录账号、设置指纹。但定制 ROM 往往需要加入自己的逻辑,比如:
- 显示隐私协议和用户条款
- 引导用户注册账号
- 选择设备使用场景(家庭/办公/教育)
- 跳过某些步骤(比如不强制登录)
SetupWizard 的源码在 packages/apps/Settings 中,但它的入口是一个单独的 SetupWizardActivity。你可以通过修改 AndroidManifest.xml 中的 intent-filter 来控制它是否作为开机向导启动。
举个例子,你想在开机向导中加入一个「选择主题」的页面:
// ThemeSelectionFragment.java
public class ThemeSelectionFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_theme_selection, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
Button btnLight = view.findViewById(R.id.btn_light);
Button btnDark = view.findViewById(R.id.btn_dark);
btnLight.setOnClickListener(v -> {
// 保存主题设置
Settings.Global.putInt(getContext().getContentResolver(),
"theme_mode", 0);
// 跳转到下一步
((SetupWizardActivity) getActivity()).onNext();
});
btnDark.setOnClickListener(v -> {
Settings.Global.putInt(getContext().getContentResolver(),
"theme_mode", 1);
((SetupWizardActivity) getActivity()).onNext();
});
}
}
然后把这个 Fragment 添加到 SetupWizardActivity 的页面序列中。注意,开机向导的页面跳转逻辑通常是用 ViewPager 或 FragmentPagerAdapter 实现的,你只需要在 getItem() 方法里返回新的 Fragment 即可。
知识体系总览
下面这张图帮你理清 Launcher、SystemUI、SetupWizard 三者的关系,以及它们在 AOSP 中的位置:
总结一下
定制 Launcher、SystemUI 和开机向导,是嵌入式 Android 系统开发中最常见的三个任务。它们分别对应了用户的「桌面体验」、「系统交互」和「首次启动体验」。说白了,你把这三点做好了,用户就会觉得这个 ROM「很用心」。
我个人建议你从 Launcher 开始练手,因为它相对独立,改坏了也不影响系统稳定性。然后逐步挑战 SystemUI 和 SetupWizard。记住,每次修改后都要做完整的回归测试,尤其是开机向导——它出问题,用户连系统都进不去。
公众号:蓝海资料掘金营,微信deep3321