简介:一份面向安卓桌面定制爱好者的完整资源包,聚焦启动器更换、图标包、壁纸、小部件、过渡动画与手势操作等个性化场景,适合想深度改造手机界面或学习桌面开发技巧的用户。压缩包共收录一万零九百七十四个文件,以PNG图片和XML配置为主,另有Java源码、jar库及说明文档,整体大小约24MB,内容紧凑便于快速获取。包内含可安装的APK与编译资源,既能直接安装体验自定义桌面效果,也能提取图片素材和布局配置,或通过源码与类文件分析启动器、桌面网格、手势设置等实现逻辑。这种结构很适合从零自制启动器、制作图标包或研究安卓桌面定制机制的开发者借鉴。目前已有255人学习浏览,可对照目录中的工程结构快速定位所需模块。
1. android 桌面定制到底在定什么:先分清系统桌面和换肤
「android 桌面定制」常被人误以为就是换壁纸、换图标包,真做过的人才会告诉你,它指的是把 Android 系统里的 Home 界面,也就是 Launcher,在工程层面整体替换或改造。第一次接这类需求的人往往被一句话带偏:「默认桌面改改就行」。真动手才会发现背后全是系统边角问题:默认布局、预装位置、系统签名、开机向导、手势冲突,哪一个都能把人卡住好几个小时。
这类定制能解决的实际问题很明确:某公司内部定制的终端和平板,开机后不能看到第三方应用铺满桌面,需要只显示业务应用、固定网格和默认壁纸;再比如数字标牌、自助终端需要锁死桌面入口,防止用户退到桌面乱点。适合的读者是准备接手 Launcher 二次开发、ROM 集成或者自研桌面的工程师,也包括想知道「为什么改个桌面这么费劲」的产品和技术负责人。这篇按我习惯的做法,从路线选型讲到本地跑起来,再到实际改布局、避坑和验证,全部按可复现的顺序写。
2. 技术路线选型:为什么大多数定制项目从 Launcher3 二次开发起步
2.1 三条路线,只有一条适合长期维护
做桌面定制第一步不是拉代码,是先把路线选了。常见三条路:从零写一个 Launcher,自己接管 HOME intent,自己画工作区、抽屉、小组件容器;基于 AOSP Launcher3 二次开发;叠加一个第三方开源桌面做改造。
从零写的好处是代码干净,坏处是小组件、壁纸、图标归一化、手势导航全要自己接系统服务,工作量不是一个桌面,是半个桌面系统,周期通常以月计,而且做出来的体验还不一定有系统级桌面顺手。第三方桌面适合试原型,但到了预装和系统签名的环节就会卡住:第三方桌面往往做了自己的私有逻辑,改它的私有逻辑和解锁全部桌面能力,时间不比改 Launcher3 少。剩下那条路就是 Launcher3 二次开发,这也是绝大多数定制项目的实际起点,因为它是 AOSP 维护的系统桌面标准实现,预装、权限、桌面组件、壁纸服务都被系统默认支持,改起来不用和厂商私货较劲。
我的选择稳定落在第二条。说个反直觉的事:从零写桌面看着自己代码少,实际系统对接的活一点不少。相反,从 Launcher3 起步,前期要处理的是「把代码拆出来、跑起来、看懂结构」,后面真正的定制点反而都被官方做成了配置项或模块,你只需要按顺序把参数调对。
2.2 Launcher3 工程结构:桌面定制的改动主要集中在这几块
Launcher3 的工程结构核心围绕这几个概念:Workspace 桌面工作区、Hotseat 底部常驻栏、AllApps 应用抽屉、Folder 文件夹、Widget 小组件容器。做定制碰得最多的是四块内容。
第一块是设备配置描述文件,它描述桌面在什么屏幕密度、什么网格下渲染,包括列数、行数、图标尺寸、桌面间距。多数「桌面太挤」或者「桌面太散」的投诉都改这里。第二块是布局数据,决定默认工作区放哪些快捷方式、文件夹、小组件。系统预置桌面时,首次开机默认布局基本由这里决定,不是用户在桌面上摆出来的那套。
第三块是应用列表过滤,控制抽屉里展示哪些应用、隐藏哪些。定制终端最常见的需求就是只留业务应用,系统预装工具全部收起来。第四块是壁纸和主题资源,包括默认壁纸、图标包、界面主题色。这里容易踩坑的地方是壁纸尺寸要和屏幕密度匹配,改完容易出现拉伸或留黑边。实际操作时我一般先在源码里做一次全局搜索,按「配置描述文件 → 布局工程 → 应用过滤 → 资源替换」的顺序来改,每改一处都做一次构建,这样出问题能很快定位是哪一层。
2.3 Launcher3 与系统默认桌面不是一回事,决定你能不能预装替换
这里要先澄清一个关键区别:AOSP 的 Launcher3 和系统 ROM 里默认的那套桌面,不是一个东西。很多公司手上的设备是厂商 ROM,厂商会在自己的 ROM 里把默认桌面换掉,只留一个叫「桌面」的壳。而 Launcher3 是 AOSP 维护的开源版本,功能上偏基础,但没有厂商私有逻辑。我做过的一个某跨平台系统项目里,就遇到过厂商桌面带有自定义手势和负一屏,如果直接拿它改,等于把别人家的业务逻辑也一并维护了,后面每升级一次系统都要重新适配。
预装替换的系统机制是这样:Android 会按 Intent Category 为 HOME 的应用来决定哪个是桌面。你定制的 APK 如果不用系统签名就装进 /system,系统可能优先解析厂商桌面而不是你的桌面,结果就是「改了没生效」。所以预装替换的路线通常是:先在 Manifest 声明android.intent.action.MAIN加android.intent.category.HOME,再通过系统签名把 APK 放到 /system/app 或 /product/overlay,最后用命令把默认桌面切到自己,这样厂商桌面即使还在系统中也不会被优先拉起。下面一章就把「改出来并跑起来」的最小步骤走通。
3. 本地环境把 Launcher3 跑通:工程导入与最小改动
3.1 拉取源码与导入工程:用稳定分支而不是最新主干
Launcher3 的代码引用了大量系统编译期库,单独拉下来用 Android Studio 直接跑,通常会报缺 SystemUI 相关符号。我这边习惯的最小流程是:先拉 Launcher3 源码到工作区,然后用 Gradle 把它当独立 Android 工程导入,并注释掉仅系统编译期需要的依赖,遇到缺符号的类先用最小实现顶住。这个「顶住」不是为了骗过构建,是为了尽早跑起来看到桌面本体,等桌面能启动再逐步接回系统依赖。
# 拉取 Launcher3 模块,以对应 Android 版本的分支为准 mkdir -p ~/workspace/launcher3-dev && cd ~/workspace/launcher3-dev repo init -u https://android.googlesource.com/platform/manifest -b android-xx 2>/dev/null # 只同步 Launcher3 这一块,避免把整个 AOSP 都拉回来 repo sync -j8 platform/packages/apps/Launcher3说明:repo init这里只是为了拉取模块目录,-b android-xx需要根据你手上 ROM 的 Android 版本替换,比如开发目标机的系统版本是 Android 10,就选对应的分支。repo sync后面的路径参数只同步 Launcher3 这一个模块,不要在这里直接同步整个 AOSP,那会把你机器磁盘和网络都拖垮。如果网络条件不允许用 repo,也可以直接在浏览器里进入 AOSP 的 Git 页面下载 Launcher3 的 tar 包,效果一样。
拉下来之后第一步不是着急导入 Android Studio,而是先看目录里有没有AndroidManifest.xml,确认这是个完整的应用工程而不是纯系统模块。Launcher3 在 AOSP 里既可以被 Soong 编译成系统应用,也可以被改造成独立 Gradle 工程,后者才是我们本地快速迭代的路径。
3.2 构建脚本调整:给自己一个能反复产出的 APK 环境
Launcher3 在 AOSP 里是系统模块,独立编译时常用 Soong,但本地验证不建议碰 Soong,直接切到 Gradle 更顺手。需要注意 Gradle 版本和 AGP 版本要和你本地的 Android SDK 版本匹配,否则会出现 AGP 版本冲突。这类构建问题说到底是版本对齐的事,属于构建玄学,但几乎每个人都有一次翻车经历,解法就是把 Gradle 版本降到你 SDK 平台版本对应的官方推荐值。
一个最常用的做法是建一个local.properties指向本地 SDK,然后再建一个build.gradle片段把桌面的 appId 改掉。改 appId 不是随便改,它直接决定你能不能和系统桌面共存:如果你的桌面包名和厂商桌面重复,签名又不一样,安装时会被拒绝。
android { namespace "com.example.custom.launcher" defaultConfig { applicationId "com.example.custom.launcher" versionCode 1 versionName "1.0" minSdk 26 targetSdk 33 } signingConfigs { release { // 使用你公司的签名或 AOSP platform 签名,二选一 storeFile file("../keys/platform.keystore") storePassword "android" keyAlias "platform" keyPassword "android" } } buildTypes { release { signingConfig signingConfigs.release } } }说明:namespace和applicationId我通常保持一致,避免在代码引用时出现R类包名错乱的经典问题。signingConfigs.release这里不是必须用 platform 签名,如果你只是本地体验,用 debug 签名也行;但你要预装到系统分区,就必须用和 ROM 一致的 platform 签名,否则开机后 PackageManager 会把你的桌面当作普通应用处理,很多系统级接口会拒绝工作。minSdk 26是为了兼容 Android 8.0 及以上的设备,如果你的目标机是 Android 11 以上,可以适当调高。
3.3 最小验证:把改过名字的桌面装进模拟器
构建完成后,安装到模拟器或真机的命令很简单,但验证的时候要清楚一件事:Android 桌面不是一个普通的 Activity,它是 HOME。测试时如果直接am start启动,桌面可能没有把自己当作 HOME,也不会出现在系统设置里的「默认桌面」选项中。正确做法是用带 HOME Category 的 Intent 拉起,再确认前台确实是你的包名。
# 安装定制桌面 adb install -r out/custom-launcher.apk # 拉起 HOME 界面 adb shell am start -a android.intent.action.MAIN -c android.intent.category.HOME # 确认当前前台桌面包名 adb shell dumpsys activity activities | grep -i mResumedActivity | grep custom.launcher这里第三行命令是我每次验证「桌面有没有真正生效」的第一检查项。如果前台不是你的包名,说明你装上的 APK 没有被系统当成 HOME,那问题多半在 Manifest 的 intent-filter 上。Launcher3 默认的 Manifest 里是有这个过滤的,但如果你在改造过程中动过 Activity 节点,很容易把这行删掉。
如果命令没有输出,把最后一段grep去掉,看完整的dumpsys activity activities输出,找到当前前台 Activity 的完整包名和类名,自己判断是不是桌面的入口 Activity。这一步做好了,后面改布局、改隐藏应用才有稳定的验证环境。
4. 正式动手改桌面:网格、默认布局、隐藏应用与壁纸参数
4.1 改默认网格列数:InvariantDeviceProfile 是绕不开的入口
桌面格子数不是 UI 上拿尺子丈量的,而是由 Launcher3 的设备配置描述文件决定的。桌面网格列数改动最常见的位置是这个配置描述文件里的numColumns字段,你改了它,桌面宽屏下才会按你的新列数排布。这个文件的路径一般在res/xml/device_profiles.xml,Launcher3 会根据设备屏幕的宽高和密度去匹配一组最合适的 profile。
<!-- res/xml/device_profiles.xml 中的最小片段 --> <profile launcher:name="CustomTablet" launcher:minWidthDps="600" launcher:minHeightDps="960" launcher:numRows="5" launcher:numColumns="7" launcher:iconSizeDps="48" launcher:hotseatIconSizeDps="44" />说明:numColumns决定桌面横向格子数,numRows决定纵向,iconSizeDps影响图标显示大小和点按热区,hotseatIconSizeDps单独控制底部常驻栏的图标大小。这个文件里的minWidthDps和minHeightDps是一组匹配条件,Launcher3 会按设备屏幕的 dp 值去匹配最适合的 profile,这也是很多人改了配置不生效的原因:你改的 profile 匹配条件根本没覆盖目标设备。改完后最好在多个屏幕尺寸的模拟器上验证一遍,避免出现「平板改了,手机没变」的现象。
如果项目里存在多个 profile,注意每个 profile 的name不要一样,否则系统会取到第一个同名配置,后面的全部被忽略。我在一次实际改动中把CustomTablet写成了Tablet,和系统默认 profile 重名,结果桌面一直用默认值,排查了大半天才在日志里看到 profile 解析告警。
4.2 改默认布局:让开机后的桌面不是你手动拖出来的
定制终端最常见的需求是「开机后桌面就只有业务应用」。手动拖快捷方式可以做到,但每次恢复出厂设置就回到原样。真正可靠的方案是把默认布局直接写进工程资源,Launcher3 在首次创建 workspace 时会从这里读数据生成默认页面,用户之后的操作不会影响这个初始状态。
<!-- res/xml/default_workspace.xml 中的单个页签示例 --> <favorites> <!-- 固定快捷方式:component 用包名加 Activity 名拼出 --> <shortcut title="业务客户端" iconPackage="com.example.biz" iconResource="com.example.biz:drawable/ic_launcher" packageName="com.example.biz" className="com.example.biz.MainActivity" screen="0" x="0" y="0" /> <!-- 固定一个文件夹,里面放两个应用 --> <folder title="工具集合" screen="0" x="1" y="0" > <favorite packageName="com.example.toola" className="com.example.toola.MainActivity" /> <favorite packageName="com.example.toolb" className="com.example.toolb.MainActivity" /> </folder> </favorites>说明:screen是第几屏,x和y是格子坐标,都从 0 开始。这里最容易翻车的是className写错:Activity 的真名要用包名加类名完整写出,而且必须和 APK 里 AndroidManifest.xml 的 activity name 完全一致。有些应用启用了 activity-alias,这时要用别名对应的类名,否则快捷方式会变成「点击无反应」的摆设。
还有一个细节是iconResource的写法:它不是本地资源,而是目标应用里的资源。如果目标应用没有公开图标资源,就省略这一项,让 Launcher3 安装后通过 PackageManager 自己去取。硬写一个不存在的资源,图标会直接变成空白占位符,开机后桌面上是一片灰色的小机器人。
4.3 隐藏应用:不是卸载,而是从桌面入口拿掉
在定制终端上,用户不该看到系统设置、浏览器等应用的图标。卸载不可行,它们可能是系统组件;正确的做法是把它们从桌面数据源过滤掉。Launcher3 里 AllApps 的数据源默认会把所有可启动应用都放进抽屉和搜索,所以在数据加载处做一次按包名排除就能收口。
// AllAppsList.java 的 add 方法里加一个过滤逻辑(示意) public void add(AppInfo app, boolean isMain) { if (isMain) { // 黑名单里的包名一律不进入桌面应用列表 if (sHiddenPackages.contains(app.componentName.getPackageName())) { return; } } // 原有逻辑继续执行 ... }说明:sHiddenPackages建议做成一个静态集合,从res/values/config.xml读取,这样不重新编译就能通过资源覆盖调整隐藏名单,比写死在代码里好维护得多。我在一个政企终端项目里就是这么做的:客户的隐藏名单每周都在变,业务方只需要改一个 xml 里的字符串数组,重新构建桌面 APK 就走。注意:过滤桌面数据源不等于应用不能运行,用户仍然可以通过设置里的应用列表打开它。如果连入口都要彻底封死,那是另一层权限控制的事,不在桌面定制范畴内。
如果你还需要在桌面工作区隐藏预置图标,只过滤 AllApps 不够,还要在解析默认布局时把对应 item 丢掉,或者在生成快捷方式时对包名做一次拦截,否则 Launcher3 会在开机时把系统应用自动加回工作区。
4.4 壁纸与图标大小:最容易「看着不对劲」的两个参数
壁纸替换的坑在于:尺寸不对,系统会自动缩放,结果边缘模糊或主体被裁掉。我一般会先用wm size和wm density读当前设备的分辨率与密度,再准备对应尺寸的壁纸资源。Launcher3 默认壁纸路径一般在res/drawable-*下,同一张壁纸要按资源限定符放多份,不然在平板上会看到明显的颗粒感。
图标大小就是前面 device_profiles.xml 里的iconSizeDps。调大图标会同时影响图标显示与点按热区,调小了用户在桌面很难点准。我的习惯是先从 48dp 起步,按终端屏幕尺寸迭代两次,而不是一次性调到 56dp 以上——一次改太大,栅格和文字会一起失衡,看起来就像把整个 UI 放大镜怼到脸上。
5. 桌面定制必踩的坑:从签名冲突到厂商 ROM 的桌面接管
5.1 现象:改了默认桌面,但开机只闪一下黑屏又回到原桌面
原因:你的 APK 没有用 platform 签名,系统不认它是系统桌面;或者 Manifest 里少了category.HOME的 intent-filter。还有一个隐藏原因:Launcher3 引用了系统隐藏 API,普通签名下这些调用被拒绝,桌面进程起来后直接异常退出。
解决:先确认签名,再抓崩溃日志,重点看SecurityException和ClassNotFoundException。我一般直接用 logcat 过滤桌面进程的崩溃堆栈,比一个个猜原因快很多。
adb logcat -d | grep -E "AndroidRuntime|FATAL EXCEPTION" | tail -50这一步几乎能覆盖 80% 的「黑屏闪退」问题。注意别同时加太多过滤词,日志量一大反而什么都看不出来。如果是签名问题,日志里会反复出现Permission Denial字样,这时候检查 signingConfig 是否真的指向 platform 签名的 keystore,而不是 debug.keystore。
5.2 现象:改了 device_profiles.xml,桌面格子没变化
原因:Launcher3 对设备配置做了缓存,或者你改的 profile 匹配条件没覆盖当前设备。我第一次做的时候改的是numColumns,但当前设备实际匹配到另一组 profile,根本没走我改的那条分支。
解决:先确认设备匹配到哪个 profile。常见做法是在启动日志里加一行输出 profile 名称,构建后看启动日志确认。这一行我一般放在 workspace 初始化位置,打印当前设备宽高 dp 值和匹配到的 profile 名。确认走对了之后,再核对minWidthDps和minHeightDps是否覆盖了设备分辨率。如果你改了参数但日志显示还在用旧值,清掉应用数据重新启动一次,Launcher3 的 profile 缓存有时候比想象中顽固。
5.3 现象:预装进 /system/app 后,桌面被厂商桌面盖住
原因:厂商 ROM 自带的桌面在系统分区,解析优先级不低;部分 ROM 还会在开机广播里主动把默认桌面拨回自家。这在某跨平台系统项目里遇到过,桌面 APK 预装进去了,但dumpsys里 HOME 状态没变,用户开机看到的还是厂商桌面。
解决:不只预装,还要在开机完成后显式调用切换默认桌面的逻辑,把默认桌面切到自己。具体做法是在设置存储里写入桌面包名的默认 HOME 配置,必要时在系统设置里关掉厂商桌面的「设为默认桌面」能力。这一条属于系统集成的活,不是只改桌面代码能解决的。如果你是做 ROM 的,建议在 frameworks 的RoleManager或PackageManager里把默认桌面角色直接绑定到你的包名,这样最稳。
5.4 现象:手势导航和桌面边缘返回手势冲突
原因:全面屏手势的返回区域覆盖了桌面底部,用户在 hotseat 上左右滑动容易误触,滑着滑着就退出了桌面,体验非常割裂。
解决:在桌面代码里监听系统手势导航区域,把 hotseat 的触摸区域抬高,或在定制 ROM 里把桌面的导航区域排除。我这边习惯是在桌面加一个底部 inset padding,手势条高度由系统接口读取,hotseat 的下边距跟着这个值走,避免热点区域被手势吞掉一半。真机上验证时重点测横屏,横屏下 inset 的方向会变,很多桌面做竖屏没问题,一横屏手势区就打架。
5.5 现象:应用名是有了,图标是个空白小机器人
原因:应用没有提供自适应图标,系统按旧版图标处理;或你的iconResource引用的包名和资源名写错,Launcher3 拉不到图就画占位图。还有一个容易被忽略的情况是目标应用在开机时还没挂载,Launcher3 加载时拿不到图标资源,只能画默认占位。
解决:在default_workspace.xml里省略iconResource,让 Launcher3 安装后自己从 PackageManager 拉。如果还是空白,看日志里有没有Unable to load icon的告警。这里没有黑匣子,纯粹是资源引用链不对。排查时先确认包名没写错,再用adb shell dumpsys package查目标应用的真实 Activity 名和图标资源名。
6. 进阶:用命令行验证桌面定制结果,上线前做一遍桌面回归
桌面定制改到最后,最怕的不是功能不对,是「这次改动把上次好的地方弄坏了」。我固定会用一组命令做回归:确认首页、确认抽屉、确认隐藏名单、确认壁纸没有拉伸,全部可以脚本化。这一套命令在每次改动后跑五分钟,能减少一大半人工点按测试的时间。
# 检查当前桌面是不是定制包 adb shell cmd package resolve-activity --brief -a android.intent.action.MAIN -c android.intent.category.HOME | tail -1 # 打开抽屉界面,包名按你的实际命名替换 adb shell am start -a android.intent.action.MAIN -c com.example.custom.launcher.ALLAPPS # 抓当前界面布局,确认图标数量不是一堆默认工具 adb shell uiautomator dump /sdcard/layout.xml && adb shell cat /sdcard/layout.xml | grep -c "com.example.custom.launcher"进阶技巧是「把默认桌面切换做成一个模块」:在系统定制里加一个 setting 值,由它决定开机后默认桌面是谁,这样即使厂商桌面还在,也能在设置层压住。我在某公司内部定制的项目里就是这么做的:桌面包名写在config.xml里,系统开机后用一个小工具读这个值并调用RoleManager.addRoleHolderAsUser完成绑定,不需要每次都在 ROM 里硬编码。
上线前再用dumpsys launcher看一下桌面内部状态的加载情况,确认 workspace 没有异常。这个命令不同版本输出格式有差异,统一做法是抓 Activity 的mResumedActivity是不是你的桌面包名。如果设备支持,建议再跑一遍「开机后自动进桌面」的验证:重启设备,等开机动画结束,用adb shell getprop sys.boot_completed确认系统就绪后,直接检查前台 Activity。
我在桌面定制上趟过最大的坑,就是以为自己把代码改完了,结果预装到厂商 ROM 上被系统桌面盖住。后来养成的习惯是每次改完都先跑一遍上面的 resolve-activity 确认 HOME 归属,再进 UI截图比对。这个习惯救了我很多次。另一个长期受益的习惯是每次改动前对 device_profiles.xml 和 default_workspace.xml 打一个 git 标签,改崩了能瞬间回退,不用靠记忆恢复配置。希望帮到你。
本文还有配套的精品资源,点击获取