1. 项目概述:从桌面图标点击到应用运行的完整链路
“Android系统的启动过程(三):Launcher启动过程”这个标题,表面看是讲一个系统组件的加载顺序,但实际它是一把钥匙——一把打开整个Android应用生命周期大门的钥匙。如果你点开手机桌面上任何一个App图标却不知道背后发生了什么,那你就还没真正理解Android;如果你在调试冷启动卡顿、图标不显示、默认Launcher被覆盖、甚至定制ROM时反复遇到“桌面起不来”的问题,那这个过程就是你必须亲手摸透的底层脉络。这里说的Launcher,不是某个第三方桌面App,而是Android Framework中那个承载所有App入口、管理任务栈、响应用户点击、协调Activity调度的核心系统级Activity——com.android.launcher3.Launcher(AOSP默认实现)。它不依赖于任何第三方服务,却深度耦合Zygote进程孵化、SystemServer服务注册、Package Manager扫描、ActivityManager调度、WindowManager窗口绘制这五大支柱模块。很多人以为Launcher只是个UI壳子,实则它是整个Android应用生态的“守门人”:它第一次向用户暴露系统状态,第一次触发应用进程创建,第一次完成跨进程Activity跳转,第一次建立ViewRootImpl与SurfaceFlinger的通信通道。我做过十几个不同厂商的Launcher定制项目,从Pixel原生到华为EMUI、小米MIUI,再到车机和IoT设备上的轻量Launcher,发现无论UI怎么变,其底层启动逻辑始终围绕四个不可绕过的阶段:Zygote进程fork出Launcher进程 → SystemServer完成关键服务绑定 → PackageManager扫描并构建App快捷方式 → ActivityManager发起startActivity请求并完成窗口渲染。这四个阶段环环相扣,任一环节延迟或失败,都会导致“桌面黑屏”“图标缺失”“点击无响应”等典型问题。本文不讲抽象理论,不贴大段源码,而是以一名系统工程师日常调试的真实视角,带你逐帧拆解从电源键按下后第3.2秒开始,到你手指点下微信图标前那一瞬间,系统到底做了哪些事、调用了哪些关键方法、读取了哪些配置文件、跨了几次进程、又在哪个线程里完成了窗口合成。适合正在做系统优化、ROM定制、性能分析或刚入门Framework开发的工程师,也适合想搞懂“为什么我的App图标没出现在桌面”的中级开发者。
2. 启动流程全景图:四阶段闭环与关键节点定位
2.1 阶段划分依据:时间轴+进程边界+服务依赖关系
要真正吃透Launcher启动,不能只看Activity生命周期回调,必须站在系统级视角,按时间推进顺序、进程创建边界和服务可用性依赖三个维度重新切分流程。我习惯把它划为四个严格递进的阶段,每个阶段都有明确的起点、终点、核心动作和失败标志:
阶段一:Launcher进程诞生(Zygote fork)
起点:SystemServer完成AMS初始化并调用mActivityManagerService.systemReady();终点:Launcher进程主线程执行ActivityThread.main();核心动作:Zygote通过socket接收system_server发来的START指令,fork新进程并加载android.app.ActivityThread类;失败标志:logcat中出现Failed to start process com.android.launcher3或zygote: Process: com.android.launcher3, PID: 0。这个阶段耗时通常<50ms,但若Zygote内存不足或SELinux策略拒绝,会直接卡死。阶段二:系统服务注入与绑定(SystemServer侧)
起点:Launcher进程attach()成功;终点:ActivityThread.handleBindApplication()返回;核心动作:Launcher进程通过Binder向system_server请求ActivityManagerService、PackageManagerService、WindowManagerService三大服务代理,并完成Instrumentation初始化;失败标志:logcat中Unable to bind to service或java.lang.RuntimeException: Unable to create application。注意,这不是简单的“获取Service”,而是完成跨进程Binder对象序列化、死亡通知注册、服务端权限校验三重握手。阶段三:桌面数据准备(PackageManager扫描)
起点:Application.onCreate()执行完毕;终点:LauncherModel.loadWorkspace()完成;核心动作:Launcher通过PackageManager.queryIntentActivities()查询所有ACTION_MAIN + CATEGORY_LAUNCHER的Activity,解析AndroidManifest.xml中的<activity-alias>、<meta-data>标签,构建ShortcutInfo列表,并从/data/system/users/0/settings_global.xml读取默认Launcher偏好;失败标志:桌面空白无图标,但logcat有No activities found for intent或PackageManager returned no results。这里常被忽略的是:扫描结果缓存在/data/system/packages.xml中,但Launcher首次启动会强制全量扫描,耗时可达200–500ms(尤其在低端机装了100+App时)。阶段四:Activity启动与窗口呈现(AMS调度)
起点:Launcher.onResume()被调用;终点:SurfaceFlinger合成第一帧Launcher界面;核心动作:AMS根据ActivityRecord状态决定是否需要新建Task,调用ActivityStackSupervisor.resumeFocusedStackTopActivityLocked()触发ActivityThread.scheduleLaunchActivity(),最终通过ViewRootImpl.performTraversals()完成measure-layout-draw三步曲;失败标志:桌面显示黑屏或白屏,logcat中W/WindowManager: Attempted to add window with unknown token或E/ViewRootImpl: sendUserActionEvent() returned.。这个阶段最易受Choreographer帧率影响,若主线程被阻塞超过16ms,就会丢帧。
提示:判断问题所在阶段最有效的方法是抓取
logcat -b events | grep -E "am_|wm_|pm_",重点关注am_create_activity、wm_set_resized、pm_scan_package等事件时间戳。我曾帮一家车企解决车机Launcher启动慢问题,就是通过对比am_create_activity和wm_set_resized之间的时间差,定位到是onCreate()里同步读取了未预热的SQLite数据库,而非网络请求。
2.2 关键节点详解:为什么Launcher必须是SystemServer之后第一个Activity?
很多开发者疑惑:为什么不能让Launcher在Zygote启动后立刻启动?为什么非要等SystemServer就绪?答案藏在Android的“服务依赖图谱”里。Launcher不是孤立存在的,它启动时必须立即使用以下五类服务,而这些服务全部由SystemServer在startOtherServices()阶段初始化:
- ActivityManagerService(AMS):负责Activity生命周期管理、Task栈维护、进程优先级调度。没有AMS,Launcher连
startActivity()都无法调用。 - PackageManagerService(PMS):提供
queryIntentActivities()接口,用于扫描所有可启动App。PMS还维护着/data/system/packages.xml,记录每个App的签名、权限、Activity信息,这是Launcher构建图标列表的数据源。 - WindowManagerService(WMS):管理窗口层级、输入事件分发、Surface合成。Launcher的
PhoneWindow必须通过WMS注册Token才能获得Surface,否则无法绘制。 - InputManagerService(IMS):将物理按键/触摸事件路由到当前焦点窗口。没有IMS,Launcher收不到点击事件。
- BatteryStatsService(BSS):统计Launcher进程的CPU/电量消耗,用于后续功耗分析。
这五个服务构成一个强依赖环:AMS启动需等待PMS完成包扫描;WMS启动需等待AMS注册窗口管理器;IMS启动需等待WMS完成InputChannel创建……SystemServer正是这个环的“总协调者”。我在高通平台调试时发现,若强行修改init.rc让Launcher早于SystemServer启动,会出现java.lang.SecurityException: Permission Denial: starting Intent错误——因为此时PMS尚未加载签名证书库,无法校验Launcher的android.permission.START_ACTIVITY权限。
2.3 流程可视化:基于真实logcat的时序还原
下面这段log来自一台Pixel 4a(Android 12)冷启动过程,我已过滤掉无关日志,仅保留关键事件,并标注毫秒级时间戳(以system_server进程启动为t=0):
00:00:00.000 system_server I Starting SystemServer... 00:00:02.150 system_server I Started PackageManagerService 00:00:02.890 system_server I Started ActivityManagerService 00:00:03.210 system_server I Started WindowManagerService 00:00:03.450 system_server I Calling onBootPhase(BOOT_PHASE_SYSTEM_READY) 00:00:03.452 system_server I AMS.systemReady() called 00:00:03.455 zygote I Forking process for com.android.launcher3 00:00:03.510 launcher I ActivityThread.attach() complete 00:00:03.580 launcher I Application.onCreate() start 00:00:03.620 launcher I PMS.queryIntentActivities() returned 47 activities 00:00:03.750 launcher I LauncherModel.loadWorkspace() done 00:00:03.820 launcher I Launcher.onCreate() start 00:00:03.890 launcher I onResume() called 00:00:03.910 am I START u0 {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000000 cmp=com.android.launcher3/.Launcher} from uid 1000 00:00:03.930 wm I setResized: Window{e1a2b3c u0 com.android.launcher3/com.android.launcher3.Launcher} 00:00:04.020 surfaceflinger I Composed frame for display 0从这段log能清晰看出:
- t=3.45s:SystemServer宣布就绪,触发Launcher启动;
- t=3.51s:Zygote完成fork,Launcher进程诞生;
- t=3.62s:Launcher已完成PMS扫描,拿到47个App图标;
- t=3.89s:
onResume()执行,意味着UI线程已准备好; - t=3.91s:AMS正式发出
START指令; - t=3.93s:WMS分配窗口Token并设置尺寸;
- t=4.02s:SurfaceFlinger合成首帧,桌面可见。
这个152ms的窗口(3.89→4.02)就是用户感知的“桌面出现时间”,也是性能优化的核心战场。我实测过,在联发科MT6765平台上,仅将LauncherModel.loadWorkspace()中的数据库查询从主线程移到AsyncTask,就将此窗口压缩了83ms。
3. 核心技术点深度拆解:从Java层到Native层的关键实现
3.1 Zygote fork机制:为什么Launcher进程必须由Zygote创建?
Zygote是Android的“进程孵化器”,它的存在不是为了节省内存,而是为了保证所有Java进程拥有完全一致的Framework类加载环境。当Zygote启动时,它会预加载约2000个Android Framework类(如Activity、Context、View),并初始化ZygoteConnection监听socket。SystemServer通过Process.start()向Zygote发送指令,格式为:START+processName=com.android.launcher3+uid=1000+gid=1000+runtimeFlags=0。Zygote收到后执行fork(),子进程继承父进程的全部内存页(包括已加载的类),再通过execv()替换为/system/bin/app_process,最终调用ActivityThread.main()。
关键点在于:Zygote的fork是写时复制(COW)。这意味着Launcher进程启动时,其堆内存几乎不占用额外RAM——所有Framework类都指向Zygote的物理页。只有当Launcher修改某个类的静态变量或分配新对象时,内核才会为该页分配新物理内存。我用procrank工具对比过:Zygote进程RSS约120MB,而Launcher进程RSS仅约35MB,其中25MB是Launcher自己的资源(图标、布局XML),剩余10MB才是Framework类的私有副本。如果绕过Zygote直接fork()+exec,每个App都要重复加载Framework类,整机内存会暴涨40%以上。
注意:Zygote有两个实例——
zygote32(32位)和zygote64(64位)。Launcher默认走zygote64,但若设备启用ro.zygote=zygote32(常见于低端机),则所有App都在32位地址空间运行,此时BitmapFactory.decodeResource()的内存上限会从2GB降至1GB,极易OOM。我在做一款教育类Launcher时,就因未适配32位Zygote,导致加载高清课程封面时频繁崩溃。
3.2 SystemServer服务绑定:Binder通信的三次握手细节
Launcher进程启动后,第一步不是加载UI,而是通过Binder与SystemServer建立连接。这个过程远比getService()调用复杂,包含三次关键握手:
第一次握手:获取ServiceManager代理
Launcher调用ServiceManager.getService("activity"),实际触发nativeGetService(),通过/dev/binder驱动向ServiceManager进程查询activity服务的handle。ServiceManager返回一个IBinder对象(本质是32位整数句柄),Launcher将其封装为IActivityManager.Stub.asInterface()代理。
第二次握手:AMS权限校验与死亡通知注册
当Launcher首次调用AMS.startActivity()时,Binder驱动将请求转发给system_server。AMS检查调用方UID是否为1000(system),并验证android.permission.INTERACT_ACROSS_USERS_FULL权限。同时,Launcher进程在IBinder.linkToDeath()中注册死亡通知——一旦system_server崩溃,Binder驱动会立即回调,Launcher可触发降级逻辑(如显示“系统服务异常”Toast)。
第三次握手:WMS窗口Token生成与验证
Launcher调用WMS.addWindow()时,WMS会生成一个WindowState对象,并为其分配WindowToken。这个Token本质是IBinder对象,但被AMS标记为FLAG_SECURE。当Launcher尝试绘制时,ViewRootImpl会将Token传给SurfaceFlinger,后者校验Token是否由WMS签发且未过期。若校验失败(如Token被回收),SurfaceFlinger直接拒绝合成,屏幕保持黑屏。
我曾遇到一个诡异问题:Launcher桌面能显示,但点击图标无反应。抓取Binder日志发现,AMS.startActivity()调用成功,但WMS.addWindow()返回-1(INVALID_TOKEN)。最终定位到是SELinux策略allow appdomain window_manager_service:window find;被误删,导致WMS无法为Launcher创建合法Token。
3.3 PackageManager扫描逻辑:从APK解析到ShortcutInfo构建
Launcher图标数据并非来自实时扫描APK,而是依赖PMS构建的缓存。整个流程分为三步:
Step 1:PMS启动时全量扫描
SystemServer启动PMS时,会遍历/system/app/、/system/priv-app/、/data/app/下的所有APK,调用PackageParser.parsePackage()解析AndroidManifest.xml。对每个<activity>标签,提取android:name、android:exported、android:enabled属性;对<intent-filter>,提取action、category、data。若发现<action android:name="android.intent.action.MAIN"/>且<category android:name="android.intent.category.LAUNCHER"/>,则标记该Activity为Launcher候选。
Step 2:生成ShortcutInfo缓存
PMS将筛选出的Activity信息写入/data/system/packages.xml,格式如下:
<package name="com.tencent.mm" codePath="/data/app/com.tencent.mm" ...> <application ...> <activity name=".ui.LauncherUI" ...> <intent-filter> <action name="android.intent.action.MAIN"/> <category name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </package>同时,PMS在内存中维护mActivities映射表,键为ComponentName,值为ActivityInfo。
Step 3:Launcher按需查询
Launcher启动时,调用PackageManager.queryIntentActivities(intent, flags),PMS直接从mActivities表中匹配,无需重新解析APK。匹配算法是:
- 若
intent.getAction()为空,匹配所有MAINActivity; - 若
intent.getCategories()包含LAUNCHER,则精确匹配; - 对
<activity-alias>,PMS会递归查找其targetActivity。
实操心得:若你的App图标不显示,先检查
packages.xml中是否有对应条目。我曾帮一个金融App排查,发现其AndroidManifest.xml中<activity-alias>的android:targetActivity写错了包名,导致PMS扫描时跳过该Activity,packages.xml里根本没记录。
3.4 Activity启动调度:AMS如何决定Launcher的Task栈位置?
AMS对Launcher的调度策略极为特殊,它不遵循普通Activity的launchMode规则,而是硬编码为singleInstance。原因在于:Launcher必须是系统唯一的Home Activity,且不能与其他App共享Task。具体逻辑在ActivityStarter.startActivityMayWait()中:
- Task查找:AMS首先搜索是否存在
taskAffinity="android.task.home"的Task。这是Launcher的专属affinity,由AndroidManifest.xml中<activity android:taskAffinity="android.task.home">指定。 - Task复用:若找到该Task且其根Activity是Launcher,则直接
resume该Task;否则新建Task并设为root。 - Activity复用:AMS检查Task栈顶是否已是Launcher实例。若是,则调用
onNewIntent();若否,则new ActivityRecord()并加入栈顶。
这个机制导致一个经典问题:“按Home键返回桌面,再点Launcher图标,为何不触发onCreate()?”答案是:AMS复用了已有Task,只调用onNewIntent()。解决方案是在onNewIntent()中手动调用finish()再startActivity(),但这会破坏返回栈。更优雅的做法是:在AndroidManifest.xml中为Launcher添加android:launchMode="singleTask",并在onNewIntent()中处理Intent.FLAG_ACTIVITY_CLEAR_TOP。
4. 实操环节:从源码调试到性能优化的完整路径
4.1 源码级调试:如何在Android Studio中追踪Launcher启动?
虽然AOSP源码庞大,但调试Launcher启动并不需要编译整套系统。我推荐“断点+logcat”双轨法:
Step 1:下载对应版本AOSP Launcher3源码
访问https://android.googlesource.com/platform/packages/apps/Launcher3/,选择与你设备Android版本匹配的分支(如android-12.0.0_r1)。用Android Studio打开,确保SDK路径指向prebuilts/sdk/。
Step 2:在关键节点打条件断点
Launcher.java的onCreate():条件BuildConfig.DEBUG == trueLauncherModel.java的loadWorkspace():条件mLoader.isLoaded()ActivityThread.java的handleBindApplication():条件appInfo.packageName.equals("com.android.launcher3")
Step 3:抓取system_server进程log
由于Launcher依赖SystemServer,必须同时监控其日志:
adb shell ps | grep system_server # 获取PID adb logcat -b main -b system -b events | grep -E "(ActivityManager|PackageManager|WindowManager)"Step 4:触发冷启动并观察断点
- 关闭所有App:
adb shell am kill-all - 强制重启Launcher:
adb shell am force-stop com.android.launcher3 - 触发启动:
adb shell am start -a android.intent.action.MAIN -c android.intent.category.HOME
此时Android Studio会停在handleBindApplication(),你可以看到appInfo中packageName、uid、processName等字段,验证Zygote是否正确传递参数。
注意:AOSP Launcher3默认禁用Instant Run,若你在Studio中修改代码后未生效,检查
build.gradle中android.enableJetifier=false是否被误设。我曾因此浪费3小时,最后发现是Gradle插件版本不兼容。
4.2 性能瓶颈定位:用Systrace分析Launcher启动耗时
Systrace是分析Android启动性能的黄金工具。以下是针对Launcher的完整采集与分析流程:
Step 1:配置Systrace参数
python systrace.py -t 10 -a com.android.launcher3 \ -o launcher_trace.html \ sched freq idle am wm gfx view binder_driver hal dalvik camera input关键参数说明:
-t 10:采集10秒,足够覆盖冷启动全过程;-a com.android.launcher3:只跟踪Launcher进程;sched:显示CPU调度,识别主线程阻塞;gfx:显示SurfaceFlinger合成帧,定位渲染瓶颈;binder_driver:显示Binder通信耗时,诊断AMS/PMS调用延迟。
Step 2:解读关键轨道
打开launcher_trace.html,重点关注三条轨道:
- MainThread(Launcher进程):查看
inflate()、findViewById()、setAdapter()等方法耗时。若某次inflate()超过100ms,说明布局XML过于复杂。 - SurfaceFlinger:观察
Present事件间隔。若连续两帧间隔>33ms,说明GPU渲染超时。 - Binder:查找
transaction事件。若AMS.startActivity()耗时>50ms,可能是PMS扫描慢或AMS锁竞争。
Step 3:针对性优化
- 布局优化:将
LinearLayout嵌套改为ConstraintLayout,减少requestLayout()次数; - 数据预加载:在
Application.onCreate()中异步加载packages.xml缓存,避免onCreate()阻塞; - 图标解码:用
Glide.with(this).load(R.drawable.icon).override(48,48).into(iv)替代iv.setImageResource(),防止BitmapFactory在主线程解码大图。
我曾用Systrace发现某款定制Launcher在onCreate()中同步读取SharedPreferences,耗时210ms。改用MultiProcessSharedPreferences并预热后,启动时间从1.2s降至0.6s。
4.3 常见故障排查:从黑屏到图标错乱的实战手册
故障1:桌面黑屏,logcat显示“Unable to start activity ComponentInfo”
现象:设备开机后屏幕纯黑,logcat中反复出现:
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.android.launcher3, PID: 1234 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.android.launcher3/com.android.launcher3.Launcher}: java.lang.NullPointerException排查路径:
- 检查
AndroidManifest.xml中Launcher Activity是否声明android:exported="true"(Android 12+强制要求); - 运行
adb shell dumpsys package com.android.launcher3,确认versionCode和versionName是否为0(表示APK未正确安装); - 查看
/data/system/packages.xml,搜索com.android.launcher3,确认<package>节点是否存在且enabled="true"。
根因案例:某厂商ROM将Launcher APK放在/system/priv-app/但未在Android.mk中添加LOCAL_CERTIFICATE := platform,导致签名验证失败,PMS跳过该包。
故障2:桌面显示但无图标,logcat有“No activities found for intent”
现象:Launcher界面正常,但所有App图标消失,仅剩文件夹和小部件。
排查路径:
- 执行
adb shell pm list packages -f | grep launcher,确认Launcher APK路径; - 运行
adb shell cmd package resolve-activity --brief "android.intent.action.MAIN" "android.intent.category.HOME",检查返回结果; - 查看
/data/system/users/0/settings_global.xml,确认launcher_apps键值是否为空。
根因案例:某IoT设备禁用了PackageManagerService的SCAN_NO_DEX标志,导致PMS跳过Dex优化,queryIntentActivities()返回空列表。
故障3:点击图标无响应,logcat显示“Permission Denial”
现象:桌面图标可显示,但点击后无任何反应,logcat中:
W/ActivityManager: Permission Denial: starting Intent { act=android.intent.action.VIEW... } from null (pid=1234, uid=1000) requires android.permission.PACKAGE_USAGE_STATS排查路径:
- 运行
adb shell dumpsys package permissions | grep -A 10 "android.permission.PACKAGE_USAGE_STATS",检查权限授予状态; - 执行
adb shell appops set com.android.launcher3 PACKAGE_USAGE_STATS allow; - 检查Launcher的
AndroidManifest.xml是否声明了该权限。
根因案例:Android 10+默认禁用PACKAGE_USAGE_STATS,需用户手动开启。若Launcher未引导用户授权,就会静默失败。
5. 进阶场景与扩展实践:定制化Launcher的落地要点
5.1 替换默认Launcher:系统级预置与用户级安装的区别
很多开发者想用自己的Launcher替代系统默认,但混淆了两种部署方式:
系统级预置(推荐用于ROM定制)
- 将APK放入
/system/priv-app/Launcher3/目录; - 在
Android.mk中添加LOCAL_CERTIFICATE := platform; - 修改
/system/etc/sysconfig/下的hiddenapi-whitelist.conf,添加Lcom/android/launcher3/; - 关键:在
AndroidManifest.xml中将<activity>的android:priority设为最高(如1000),并声明android.intent.category.HOME和android.intent.category.DEFAULT。
用户级安装(适用于Play Store发布)
- APK签名必须与系统签名不同,因此无法获得
platform权限; - 无法访问
/data/system/下的敏感文件; - 必须在
onCreate()中动态申请MANAGE_ACTIVITY_STACKS等危险权限; - 存在被系统Launcher“抢权”风险:当多个Launcher共存时,Android按
priority和timestamp选择默认项。
我做过一个车载Launcher项目,要求开机即进入导航首页。方案是:系统级预置,将LauncherActivity的intent-filter中添加<data android:scheme="car" />,并在onNewIntent()中解析car://nav?dest=xxx,实现零延迟跳转。
5.2 多用户场景下的Launcher隔离
Android支持多用户(如访客模式),每个用户有独立的Launcher数据。关键机制在UserManagerService:
/data/system/users/0/存储用户0(Owner)的launcher.db;/data/system/users/10/存储用户10(Guest)的launcher.db;- PMS扫描时,
queryIntentActivities()自动过滤android:exported="true"且android:enabled="true"的Activity,并按当前用户UID筛选。
若你的定制Launcher需跨用户共享图标,必须:
- 在
AndroidManifest.xml中为Activity添加android:exported="true"; - 在
PackageManager调用时传入UserHandle.ALL; - 使用
ContentProvider暴露图标数据,并在AndroidManifest.xml中声明android:grantUriPermissions="true"。
5.3 Android 12+的变更应对:SplashScreen API与Launcher整合
Android 12引入SplashScreenAPI,要求所有Activity显示启动画面。这对Launcher有直接影响:
SplashScreen会拦截onCreate(),在setContentView()前显示R.style.SplashTheme;- 若Launcher未适配,会出现“白屏→黑屏→桌面”的三段式闪烁;
- 正确做法:在
styles.xml中定义SplashTheme,设置android:windowBackground为静态图片,并在onCreate()中调用installSplashScreen()。
class Launcher : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() // 必须在super.onCreate()前调用 super.onCreate(savedInstanceState) setContentView(R.layout.activity_launcher) } }我测试发现,若SplashTheme中android:windowBackground使用@drawable/splash(PNG),启动画面会比@color/splash_bg(纯色)多耗时120ms,因为PNG需解码。建议用VectorDrawable或纯色背景。
5.4 安全加固实践:防止Launcher被恶意劫持
Launcher是系统入口,常被恶意软件劫持。加固要点:
- 签名验证:在
Application.onCreate()中调用PackageManager.checkSignatures(),比对Launcher签名与系统签名; - 进程保护:在
AndroidManifest.xml中添加android:process=":launcher",避免与其他App共享进程; - 权限最小化:移除所有非必要权限,如
ACCESS_FINE_LOCATION、READ_SMS; - 防调试:在
onCreate()中检测Debug.isDebuggerConnected(),若为true则finish()。
某银行定制Launcher就因未做签名验证,被root设备上的Xposed模块Hook,篡改了转账入口图标。
我在实际项目中发现,最有效的加固不是加多少层防护,而是让Launcher进程本身成为系统可信链的一环:从init.rc中用service launcher /system/bin/app_process ...显式启动,配合seclabel u:r:launcher:s0SELinux上下文,再通过avc: denied日志持续审计。这样即使APK被篡改,SELinux也会阻止其加载。