系统裁剪与定制:移除无用模块、精简ROM、定制Launcher

做嵌入式Android开发,有一件事我特别有感触:ROM体积就是硬成本。你想想看,一个智能音箱、一台POS机、一块车载中控,存储空间就那么点,预装一堆用不上的Google服务、壁纸、输入法,用户不骂你才怪。

说白了,系统裁剪就是给Android“瘦身”。把不需要的模块砍掉,把ROM体积压下来,再换上自己定制的Launcher。今天我就把这块的经验掰开揉碎讲给你听。

为什么必须做系统裁剪?

我刚开始做嵌入式项目时,犯过一个低级错误。直接把手机版的ROM刷到设备上,结果开机就卡在“正在优化应用”,等了十分钟还没进桌面。后来一查,系统里跑着几十个用不上的系统服务,内存直接爆了。

嵌入式设备的特点很明确:

  • 存储小:通常只有512MB~4GB的eMMC
  • 内存少:256MB~1GB的RAM很常见
  • 功能固定:不需要像手机那样“万能”
  • 启动要快:用户按了开关,3秒内必须看到画面

所以,裁剪不是“锦上添花”,而是“不得不做”。

裁剪的核心思路

我个人习惯把裁剪分成三个层次:

  1. 应用层裁剪:干掉预装APK
  2. 框架层裁剪:移除系统服务、库文件
  3. 内核层裁剪:精简驱动、模块

今天重点讲前两个,因为这是嵌入式工程师最常碰到的。

核心原则:能不动的别动,动了必须测。我曾经因为删了一个看起来“没用”的库,结果WiFi连不上了,排查了两天才发现那个库是WiFi HAL的依赖。

第一步:移除无用模块

Android系统的模块定义在 build/target/product/ 目录下。核心文件是 core.mkfull_base.mk。你要做的就是找到自己项目的 device.mk,然后通过 PRODUCT_PACKAGES 来控制。

举个例子,假设你的设备不需要蓝牙:

# 在 device/yourcompany/yourdevice/device.mk 中

# 移除蓝牙相关模块
PRODUCT_PACKAGES += \
    Bluetooth \          # 注释掉或删除
    bluetooth.default \  # 注释掉或删除
    libbluetooth_jni \   # 注释掉或删除

# 或者更彻底,直接不包含蓝牙的PRODUCT_PACKAGES

嗯,这里要注意:不要直接删文件。Android的构建系统有依赖检查,你删了A,B可能就编不过。正确做法是在 device.mk 里用 PRODUCT_PACKAGES 来控制。

我整理了一份常见的“可移除模块清单”,供你参考:

模块名称 说明 移除风险
Email 电子邮件客户端
Calendar 日历应用
Exchange Exchange服务
PrintSpooler 打印服务
WallpaperPicker 壁纸选择器
LiveWallpapers 动态壁纸
PicoTts 文字转语音引擎 中(如果设备需要语音)
CellBroadcastReceiver 小区广播(非手机设备)

我的经验:先编译一个全量ROM,然后用 du -sh /system/* 看看每个目录占多大。找到最大的那几个“胖子”,再决定要不要砍。别凭感觉删,要看数据。

第二步:精简ROM体积

移除模块只是第一步。真正压体积,还得靠下面几招。

2.1 删除无用的语言包

Android默认带了几十种语言。你的设备只卖国内?那保留中文和英文就够了。

# 在 device.mk 中设置
PRODUCT_LOCALES := zh_CN en_US

# 或者更狠一点,直接修改 build/target/product/locales_full.mk
# 只保留需要的语言

这一刀下去,能砍掉30~50MB。我做过一个项目,光语言包就占了80MB,砍完之后系统启动速度都变快了。

2.2 移除Google服务

嵌入式设备通常不需要Google Play、GMS。把这些去掉,ROM能瘦一大圈。

# 在 device.mk 中
# 不包含 Google 服务
WITH_GMS := false

# 或者直接注释掉
# $(call inherit-product, vendor/google/products/gms.mk)

注意:如果你的应用依赖Google Maps API或Firebase,那GMS就不能全删。这时候可以考虑用“微G”方案,只保留必要的服务框架。

2.3 压缩APK和资源

我习惯在编译完成后,用 zipalignpngcrush 再压一遍APK。虽然编译脚本里已经做了,但手动压一下还能再省5%~10%。

# 对APK进行对齐优化
zipalign -f -v 4 input.apk output.apk

# 压缩PNG图片
pngcrush -brute -reduce input.png output.png

第三步:定制Launcher

Launcher就是桌面。嵌入式设备不需要像手机那样“滑动翻页、放Widget”。我们要的是:开机直接进应用,用户点不了别的地方

我做过一个智能家居中控,Launcher就一个页面,上面几个大按钮:灯光、空调、窗帘。用户点进去就是控制界面,按返回键也回不到桌面——因为桌面就是控制界面。

3.1 写一个最简单的Launcher

其实Launcher就是一个普通的APK,只不过它的Intent Filter要声明成桌面启动器。

<!-- AndroidManifest.xml -->
<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"
    android:stateNotNeeded="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.HOME" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

然后把这个APK放到 device/yourcompany/yourdevice/app/ 目录下,在 device.mk 里加上:

PRODUCT_PACKAGES += \
    MyLauncher

3.2 替换系统默认Launcher

编译时,系统会默认包含 Launcher3Launcher2。你要做的就是把它们从 PRODUCT_PACKAGES 里移除,换成自己的。

# 移除系统Launcher
# PRODUCT_PACKAGES += Launcher3  # 注释掉

# 添加自己的Launcher
PRODUCT_PACKAGES += MyLauncher

嗯,这里有个坑:如果系统里没有Launcher,开机后会黑屏。因为Android找不到可以启动的桌面Activity。所以一定要确保自己的Launcher编译进去了,并且权限正确。

3.3 锁定Launcher(防止被替换)

嵌入式设备上,我们不希望用户安装第三方桌面。可以在Launcher的代码里做限制:

// 在Launcher的onResume中,检查自己是不是默认桌面
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
ComponentName defaultLauncher = getPackageManager().resolveActivity(homeIntent, PackageManager.MATCH_DEFAULT_ONLY).activityInfo;
if (!defaultLauncher.packageName.equals(getPackageName())) {
    // 如果不是默认桌面,强制设置自己为默认
    getPackageManager().clearPackagePreferredActivities(getPackageName());
    // 或者直接弹窗提示用户
}

避坑指南:我曾经在Launcher里加了一个“退出”按钮,结果测试人员一点,系统直接回到选择桌面的界面。后来我把那个按钮改成了“重启Launcher”,才解决问题。记住:嵌入式设备的Launcher,不要给用户退出的选项

知识体系总览

下面这张图,是我对系统裁剪与定制整个流程的总结。你可以把它当作操作手册的目录。

系统裁剪与定制知识体系 目标:精简ROM + 定制体验 移除无用模块 精简ROM体积 定制Launcher PRODUCT_PACKAGES控制 移除系统服务/APK 删除语言包/资源 移除GMS/Google服务 APK对齐/资源压缩 编写自定义Launcher 替换系统默认桌面 锁定Launcher防替换 裁剪不是删东西,而是做减法——只留对的,不留多的

验证与测试

裁剪完了,别急着烧录。我吃过亏,所以现在养成了几个习惯:

  • 对比ROM大小:裁剪前后,用 ls -lh 看看 system.img 少了多少
  • 检查启动日志:用 adb logcat | grep -i error 看看有没有服务起不来
  • 功能冒烟测试:WiFi、蓝牙、音频、显示,每个功能都点一遍
  • 压力测试:连续开关机100次,看会不会出问题

血的教训:有一次我删了 libstagefright 相关模块,ROM小了20MB,结果视频播放全挂了。后来才知道,那个库被好几个系统服务依赖。所以,删之前一定要 grep 一下依赖关系

好了,系统裁剪和定制Launcher的核心内容就这些。说白了就是:知道要什么,更要知道不要什么。动手试试吧,从你的设备.mk文件开始,一步步把ROM瘦下来。


公众号:蓝海资料掘金营,微信deep3321