在Android设备上,Launcher(启动器)是用户与系统交互的第一入口,直接影响用户体验。Android 11.0系统默认搭载Launcher3作为系统启动器,但在实际产品开发中,厂商或开发者经常需要集成第三方Launcher以满足定制化需求。传统粗暴替换Launcher3的方案会导致原生功能丢失,而优雅共存+动态切换的设计既能保留系统完整性,又能实现深度定制。
我曾在多个智能硬件项目中处理过这类需求,发现要实现两者无缝共存,必须解决三个核心问题:如何让系统首次启动时自动选择第三方Launcher?如何避免切换时的任务栈冲突?如何确保设置菜单中的默认应用选项正常工作?这些都需要在系统层面进行改造,而非简单的应用层适配。
实现双Launcher共存的关键在于引入自定义系统属性作为控制开关。在/device/rockchip/rk356x/rk356x.prop中添加以下配置:
properties复制persist.sys.def_launcher=com.yjz.launcher # 指定默认第三方Launcher包名
persist.sys.def_launcher_enable=true # 启用自定义Launcher开关
这个设计有个坑需要注意:属性值必须使用persist前缀,否则重启后配置会丢失。我在早期项目中曾因此浪费两天排查时间。具体到产品线配置(如rk3568_r.mk),需要通过PRODUCT_PROPERTY_OVERRIDES覆盖默认值:
makefile复制PRODUCT_PROPERTY_OVERRIDES += \
persist.sys.def_launcher=com.yjz.launcher \
persist.sys.def_launcher_enable=true
当系统检测到多个Launcher时,会通过ResolverActivity让用户选择。我们需要修改frameworks/base/core/java/com/android/internal/app/ResolverActivity.java,在异步回调处插入拦截逻辑:
java复制private boolean hasSetDefLauncher() {
if (SystemProperties.getBoolean("persist.sys.def_launcher_enable", false)) {
String defLauncher = SystemProperties.get("persist.sys.def_launcher");
// 遍历匹配预设包名
for (int i = 0; i < mMultiProfilePagerAdapter.getActiveListAdapter().getDisplayResolveInfoCount(); i++) {
DisplayResolveInfo info = mMultiProfilePagerAdapter.getActiveListAdapter().getDisplayResolveInfo(i);
if (defLauncher.equals(info.getResolveInfo().activityInfo.packageName)) {
startSelected(i, true, false); // 自动选择预设Launcher
return true;
}
}
}
return false;
}
这个改造消除了选择弹窗的闪烁问题。实测发现,原始实现会先显示选择界面再回调,导致视觉上的不连贯。
在ResolverListAdapter.java中,我们需要修改查询逻辑,当启用自定义Launcher时只返回目标应用:
java复制if (SystemProperties.getBoolean("persist.sys.def_launcher_enable", false)) {
String targetPkg = SystemProperties.get("persist.sys.def_launcher");
currentResolveList.removeIf(info ->
!targetPkg.equals(info.getResolveInfoAt(0).activityInfo.packageName)
);
}
这个过滤操作要放在rebuildList()方法中,早于界面渲染阶段。有个细节需要注意:部分设备会预装多个Launcher(如Google Now Launcher),必须确保过滤时机在系统完成所有Resolver查询之后。
当用户在设置中切换默认Launcher时,旧Launcher的任务栈会残留在系统中。修改HomeRoleBehavior.java添加清理逻辑:
java复制@Override
public void onHolderSelectedAsUser(String packageName, Context context) {
// 发送广播触发任务清理
Intent intent = new Intent("com.yjz.ACTION_CLEAR_TASKSTACK");
intent.putExtra("newLauncher", packageName);
context.sendBroadcast(intent);
// 启动新Launcher
context.startActivity(new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
对应的广播接收器实现任务栈清理:
java复制private void clearOldLauncherTasks(String currentPkg) {
ActivityManager am = (ActivityManager)getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(100);
for (ActivityManager.RunningTaskInfo task : tasks) {
if (task.baseActivity.getPackageName().equals(currentPkg)
&& task.topActivity != null
&& task.topActivity.getClassName().contains("Launcher")) {
am.removeTask(task.id); // 移除匹配的任务
}
}
}
这里有个性能优化点:不要无差别清理所有HOME类型的任务,否则可能导致最近应用列表异常。我建议只清理顶部Activity是Launcher的特定任务。
将第三方Launcher放入vendor/yjz/launcher/目录,对应的Android.mk配置示例:
makefile复制LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := CustomLauncher
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED # 使用预签名证书
LOCAL_PRIVILEGED_MODULE := true # 声明为特权应用
include $(BUILD_PREBUILT)
在设备配置mk文件中添加模块依赖:
makefile复制PRODUCT_PACKAGES += \
CustomLauncher \
Launcher3 # 保留原生Launcher
第三方Launcher需要声明关键权限才能正常工作,在AndroidManifest.xml中必须包含:
xml复制<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
如果遇到权限问题,可以在系统privapp-permissions-platform.xml中添加白名单:
xml复制<privapp-permissions package="com.yjz.launcher">
<permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/>
<permission name="android.permission.MANAGE_ACTIVITY_STACKS"/>
</privapp-permissions>
问题1:切换Launcher后返回键失效
这通常是因为旧Launcher的任务栈未正确清理。可以通过adb shell dumpsys activity activities查看任务栈状态,确认是否有残留的Launcher实例。
问题2:首次启动时出现选择弹窗
检查以下几点:
adb shell getprop persist.sys.def_launcher_enable)性能优化建议
onPostListReady()中添加延迟判断,避免快速切换导致的ANRActivityTaskManager替代直接调用removeTask(),兼容性更好FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标志,避免任务栈混乱在最近的车机项目实践中,这套方案成功实现了第三方Launcher与原生Launcher3的分钟级切换,用户完全感知不到后台的复杂处理过程。关键是要处理好系统服务拦截的时机和任务栈的清理粒度,这需要结合具体业务场景反复调试。