提示: 需求很简单就是 在多Launcher-HOME程序下,要默认一个主HOME程序
文章目录
- 前言-需求
- 一、实际遇到问题
- 二、参考资料
- 三、修改-相关文件
- 四、实现方案
-
- A、默认主屏幕
- B、解决手势导航失效问题
-
- 思路-踩坑
- 解决方案
-
- 1、新增广播-模拟设置主屏幕应用
- 2、配置文件中-配置广播
- 3、调用广播-实现业务逻辑
- 4、同步手势导航-彻底解决手势无效问题
-
- [让系统重新走一遍 "桌面启动流程"](#让系统重新走一遍 “桌面启动流程”)
- [系统内部会悄悄自动执行 3 个动作](#系统内部会悄悄自动执行 3 个动作)
- 小结
- C、解决上划崩溃问题
- [D、解决MTK 平台,开机总是默认打开 权限授权界面](#D、解决MTK 平台,开机总是默认打开 权限授权界面)
- 总结
前言-需求
1、实际场景
- 平台-版本:MTK平台,Android12
- 需求:产品有多个Launcher/HOME 程序,又不能去掉他们的HOME属性,保证默认Home 程序
- 开机默认的是手势导航,而不是底部虚拟按键
2、遇到问题-需要实现的技术点
- 1、默认Home程序,在多HOME程序下如何开机默认一个HOME程序,又不能改HOME属性的优先级
- 2、系统默认的是手势导航,如何解决手势导航无效问题
一、实际遇到问题
这里列举遇到的实际问题,然后后面逐一给出解决方案。
多HOME程序如何开机默认一个主屏程序
这里直接提供截图说明,多个HOME程序,是需要默认一个的,不然会存在选择HOEM程序一说,比如是需要进入系统设置去设置主屏幕才不会有开机HOME程序选择一说。

手势导航不可用-设置了默认Launcher后手势不可用,必须上划一次才能够用手势导航
已经开机实现了设置默认的 HOME 主屏程序,但是手势导航左右滑动无用,必须上划一次才能够正常使用。
涉及到的知识点广而深
在解决实际问题时候发现涉及到蛮多问题的,小结如下:
Launcher,手势滑动相关,在Android12 及以后版本,手势相关业务逻辑都移动到Launcher3里面去了SystemUI,手势分发、开机就在SystemUIService里面设置默认主屏应用Role,角色设置问题,比如默认HOME,系统是有角色一说的。- Permission,权限一说,Android体系作为Linux 子体系一种,各个方面都涉及到权限一说的。系统里面有一个app 就是处理权限的
PermissionController
所以 ,当我们一个一个去解决实际问题时候,会不断暴漏新的问题,如果需要解决实际问题,仅仅AI 编程是无法帮你解决的,还是需要理解并掌握各个模块基本内容,这里面知识点广且深,需要慢慢理解并掌握。
二、参考资料
备注-提醒:
- 平台差异性: 针对具体功能,很多时候跟平台相关,比如 我这里是MTK平台解决HOME 默认主屏幕,上划、开机启动等跳转到指定HOME程序界面时候,尽量以具体平台资料为准,其它仅为参考可自行验证,切不可混为一谈。比如 大量RK 平台相关资料,其实实际并不适用MTK平台;
- 版本差异性: 针对不同的Android版本,可能有不同的API、方法、业务流程、模块可能都不一样,尽量多思索,多看源码,整理思路, 然后具体实现。
这里仅仅提供以前关联的相关文章,仅供参考,补充技能,知识点理解:
Android 动态设置默认Launcher(默认应用 电话-短信-浏览器-主屏幕应用)
MTK平台-内置应用作为系统Launcher
Android11-Android15-Launcher3去抽屉功能-定制应用-另辟蹊径方案
MTK平台-去除第一次开机-默认权限提示框
三、修改-相关文件
如下文件修改后即可完成项目需求:这些文件都是和需求相关的 修改或者新增
java
/vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/SystemUIService.java [修改]
vendor/mediatek/proprietary/packages/apps/SystemUI/AndroidManifest.xml [修改]
/vendor/mediatek/proprietary/packages/apps/PermissionController/AndroidManifest.xml [修改]
/vendor/mediatek/proprietary/packages/apps/PermissionController/src/com/android/permissioncontroller/SetDefaultHomeBroadcastReceiver.java [新增]
/vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/AndroidManifest-launcher.xml [修改-也可不改]
/vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java [修改]
四、实现方案
A、默认主屏幕
项目中产品本身有多个Home 程序,客户各种原因多个Home程序的App 不去更改,但是Home 程序有优先级,
为什么要默认主屏幕
- 客户本身有多个Home 应用,用优先级区分:
android:priority="1" - 客户又是可以进入系统设置的,导致用户可以进行主屏幕切换导致bug
- 客户有应用市场,可以下载各种软件,导致存在多个Home 程序,会造成Launcher 选择
- 多个Home 程序下,机器本身是没有设置主屏,存在一定的稳定性风险
为什么没有参考RK平台默认主屏幕方案
首先个人并没有验证成功, RK平台以工控为主 平板产品 ;我用的MTK平台 手机方案为主,所以在修改部分业务逻辑时候存在各种问题。
比如: 网上很多参考资料,默认主屏,大家也可以参考,基本思路:
- 主屏选择界面直接跳转到自己的HOME 程序
- 在AMS、PMS 里面设置 进入HOME程序时候、查询HOME程序时候直接返回到自己的 HOME主程序
我就在想,那何必不直接设置主屏程序不就可以了嘛。
默认主屏幕实现方案
思路
参考资料:Android 动态设置默认Launcher(默认应用 电话-短信-浏览器-主屏幕应用) 之前自己写过相关的DEMO和相关源码分析,可参考,直接用。 之前分析过业务流程和源码,可实战参考:


具体实现方案
我选择在SystemUIService 里面去实现设置主屏幕操作,或者在其它系统服务中的合适位置即可,核心代码如下:
涉及到源码修改:
java
/vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/SystemUIService.java [修改]
vendor/mediatek/proprietary/packages/apps/SystemUI/AndroidManifest.xml [修改]
在onCreate 方法中用Handler 延时处理
java
// modify by fangchen start
Log.d(TAG,"=======SystemUIService zhunbei setDefaultHome ======");
// setDefaultHome(getApplicationContext());
homeHandler.removeCallbacksAndMessages(null);
homeHandler.postDelayed(setHomeRunable,10000);
// modify by fangchen end
开启线程,处理设置 Home 程序逻辑
java
// modify by fangchen start
Runnable setHomeRunable=new Runnable() {
@Override
public void run() {
String isFristStart= SystemProperties.get("persist.sys.firststart","");
Log.d(TAG,"======setHomeRunable=======isFristStart:"+isFristStart);
if(!"1".equals(isFristStart)){
Log.d(TAG," diyici kaiji setDefaultHome ");
setDefaultHome(getApplicationContext());
}else{
Log.d(TAG," has set setDefaultHome ");
}
}
};
// modify by fangchen end
最后,设置HOME 程序,业务逻辑
java
// modify by fangchen start
private void setDefaultHome(Context mContext) {
Log.d(TAG,"=============setDefaultHome====");
Log.d(TAG,"=============setDefaultHome====");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return;
}
RoleManager roleManager = mContext.getSystemService(RoleManager.class);
Executor executor = mContext.getMainExecutor();
UserHandle userHandle = UserHandle.of(0);
Consumer<Boolean> callback = successful -> {
if (successful) {
Log.i(TAG, "set home success ");
SystemProperties.set("persist.sys.firststart","1");
/*try {
Intent intent = new Intent("com.android.systemui.action.RESTART");
intent.setPackage("com.android.systemui");
mContext.sendBroadcast(intent);
Log.i(TAG, "==========com.android.systemui.action.RESTART");
} catch (Exception e) {
Log.e(TAG, " SystemUI restart error ..." );
}*/
Log.d(TAG," ========================to startActivity homeIntent ");
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(homeIntent);
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
/*try {
Log.i(TAG, "Restart TouchInteractionService start ==============");
Intent touchIntent = new Intent("android.intent.action.QUICKSTEP_SERVICE");
touchIntent.setPackage("com.komect.ajlauncher");
mContext.stopService(touchIntent);
mContext.startService(touchIntent);
Intent restartIntent = new Intent("com.android.systemui.action.RESTART");
restartIntent.setPackage("com.android.systemui");
mContext.sendBroadcast(restartIntent);
Log.i(TAG, "Restart TouchInteractionService ok");
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "TouchInteractionService restart error ");
}*/
Log.d(TAG,"========send BroadCast to PermissionController================");
try {
Intent intent = new Intent("com.android.action.SET_DEFAULT_HOME");
intent.setPackage("com.android.permissioncontroller");
intent.putExtra("home_package", "com.komect.ajlauncher");
mContext.sendBroadcast(intent);
} catch (Exception e) {
e.printStackTrace();
}
}, 5000);
// =================================================
} else {
Log.e(TAG, "set home failed ");
}
};
try {
Method addRoleHolderAsUser = roleManager.getClass().getMethod(
"addRoleHolderAsUser",
String.class,
String.class,
int.class, // flags = 0 / 1
UserHandle.class,
Executor.class,
Consumer.class
);
addRoleHolderAsUser.setAccessible(true);
addRoleHolderAsUser.invoke(
roleManager,
RoleManager.ROLE_HOME,
"com.komect.ajlauncher",
0,
userHandle,
executor,
callback
);
// ================ to sync role ================
try {
Method sync = roleManager.getClass().getMethod("syncRolesAsync");
sync.setAccessible(true);
sync.invoke(roleManager);
} catch (Exception ignored) {}
// ==============================================
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "setHomeError:" + e.getMessage());
}
}
// modify by fangchen end
最后,提供SystemUI App 修改Role ,即设置HOME程序的权限
java
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
B、解决手势导航失效问题
实际实现项目需求,发现:
- 开机主屏幕设置成功了的,但是手势导航失效。
- 一定要去系统设置,在默认主屏幕中去点击一次后,手势导航才有效果;或者上划滑动一次,进入历史任务后手势导航左右滑动手势就有效果的。
经过反复验证和实践发现,就是手势导航没有关联到HOME程序,手势失效了,哪怕重启机器也没有用的。
思路-踩坑
这里有两个思路:
- 直接在SystemUIService 里面去绑定不就可以了嘛
- 直接模拟在 默认主屏幕应用的App 中,去模拟一个后台的服务或者广播,去设置主屏幕、去绑定手势导航不就可以了嘛。
踩坑:
- 网上资料少,AI解决方案太多,都是无效的,比如如下:重启 SystemUI 服务、绑定主屏幕应用、反射调用AMS、PMS、ROLE 方法,都是无效的,比如如下: 具体原因:刷新的核心原理根部不是网上说的这些,必须了解机制才行,不然都是无用功。
java
/*try {
Log.i(TAG, "Restart TouchInteractionService start ==============");
Intent touchIntent = new Intent("android.intent.action.QUICKSTEP_SERVICE");
touchIntent.setPackage("com.komect.ajlauncher");
mContext.stopService(touchIntent);
mContext.startService(touchIntent);
Intent restartIntent = new Intent("com.android.systemui.action.RESTART");
restartIntent.setPackage("com.android.systemui");
mContext.sendBroadcast(restartIntent);
Log.i(TAG, "Restart TouchInteractionService ok");
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "TouchInteractionService restart error ");
}*/

解决方案
涉及到修改文件:
java
/vendor/mediatek/proprietary/packages/apps/PermissionController/AndroidManifest.xml [修改]
/vendor/mediatek/proprietary/packages/apps/PermissionController/src/com/android/permissioncontroller/SetDefaultHomeBroadcastReceiver.java [新增]
参考资料:Android 动态设置默认Launcher(默认应用 电话-短信-浏览器-主屏幕应用) 之前自己写过相关的DEMO和相关源码分析,可参考,直接用。 之前分析过业务流程和源码,可实战参考:
还是去搞清楚 流程、系统是如何实现的,扣源码,找到具体方法:

1、新增广播-模拟设置主屏幕应用
java
package com.android.permissioncontroller;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import com.android.permissioncontroller.role.model.Role;
import com.android.permissioncontroller.role.model.Roles;
import com.android.permissioncontroller.role.ui.DefaultAppViewModel;
public class SetDefaultHomeBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "SetDefaultHome";
public static final String ACTION_SET_DEFAULT_HOME = "com.android.action.SET_DEFAULT_HOME";
public static final String EXTRA_HOME_PACKAGE = "home_package";
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "===========set HOME start =onReceive==");
if (!ACTION_SET_DEFAULT_HOME.equals(intent.getAction())) {
return;
}
String homePackage = intent.getStringExtra(EXTRA_HOME_PACKAGE);
if (homePackage == null || homePackage.isEmpty()) {
return;
}
Log.i(TAG, "===========set HOME start ===" + homePackage);
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
UserHandle user = UserHandle.of(userManager.getUserHandle());
Role role = Roles.get(context).get(android.app.role.RoleManager.ROLE_HOME);
DefaultAppViewModel.Factory factory = new DefaultAppViewModel.Factory(
role,
user,
(Application) context.getApplicationContext()
);
DefaultAppViewModel viewModel = factory.create(DefaultAppViewModel.class);
viewModel.setDefaultApp(homePackage);
Log.i(TAG, "===========set HOME end ===" + homePackage);
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(homeIntent);
}
}
最核心的模拟的就是调用了这行代码:
java
viewModel.setDefaultApp(homePackage);
2、配置文件中-配置广播
java
<receiver
android:name=".SetDefaultHomeBroadcastReceiver"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="com.android.action.SET_DEFAULT_HOME" />
</intent-filter>
</receiver>
3、调用广播-实现业务逻辑
如上,在SystemUIService 中,设置了主屏幕后,做了一件事,就是 通知广播,通知的广播就是这里的广播,模拟设置主屏幕应用:
java
try {
Intent intent = new Intent("com.android.action.SET_DEFAULT_HOME");
intent.setPackage("com.android.permissioncontroller");
intent.putExtra("home_package", "com.komect.ajlauncher");
mContext.sendBroadcast(intent);
} catch (Exception e) {
e.printStackTrace();
}
4、同步手势导航-彻底解决手势无效问题
在设置完主屏幕后,最后一个步骤就是 让主屏幕应用 同步绑定手势一次即可:我们看看广播里面我们是如何绑定的,就是重新启动了一次HOME程序
java
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(homeIntent);
让系统重新走一遍 "桌面启动流程"
- = 等价于你手动按了一下 HOME 键
- = 等价于你在设置界面点击确认
系统内部会悄悄自动执行 3 个动作
- 设置默认 HOME(你做了)
- 自动触发:AMS 重新解析 HOME Intent
- 自动触发:SystemUI/QuickStep 重新绑定
小结
实战项目中,主屏幕也设置了,就是手势无效;在RK平台中自己做过相关需求,不会有这样的问题。 仔细分析代码 自己在SystemUIService 和 广播里面设置了主屏应用成功了,就是手势导航无效。
自己就思考:都是设置主屏幕应用,为什么在 默认主屏幕应用界面中点击后手势导航有效,而 在服务和广播里面就是无效呢? 或者说 为什么滑动上划一次 左右手势导航就有效了呢?
最后自己找了很多资料,发现: 界面点击是「前台可见操作」,系统自动触发了 SystemUI/Launcher 绑定广播是「后台隐形操作」,系统 不会自动触发绑定!
更奇怪的是哪怕有主屏幕应用,重启机器也无效,所以特别特别奇怪。 好在 最后模拟重新进入Home 程序,不就行了嘛。
C、解决上划崩溃问题
涉及到源码:
java
/vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/AndroidManifest-launcher.xml [修改-也可不改]
/vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java [修改]
为什么这里要改:
- 原生系统默认的主屏幕就一个
Launcher3,所以关联的HOME包名类名不需要处理的。但是现在HOME主屏幕定制了的,所以这里要改成原生Launcher3的包名,不要变。 - 为什么要保留: 这里跟手势挂钩 也就是说
Launcher3跟手势挂钩的,用定制化的Home程序包名类名后,手势导航直接失效。
java
public OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState) {
mContext = context;
mDeviceState = deviceState;
mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
//.addCategory(Intent.CATEGORY_HOME)//huanghb modify
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
//huanghb modify
//ComponentName myHomeComponent =
// new ComponentName(context.getPackageName(), info.activityInfo.name);
ComponentName myHomeComponent =
new ComponentName("com.android.launcher3", "com.android.launcher3.uioverrides.QuickstepLauncher");
//huanghb end
mMyHomeIntent.setComponent(myHomeComponent);
mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
mFallbackIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_DEFAULT)
.setComponent(fallbackComponent)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
ActivityInfo fallbackInfo = context.getPackageManager().getActivityInfo(
mFallbackIntent.getComponent(), 0 /* flags */);
mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
} catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
mContext.registerReceiver(mUserPreferenceChangeReceiver,
new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
updateOverviewTargets();
}

D、解决MTK 平台,开机总是默认打开 权限授权界面
涉及源码:vendor/mediatek/proprietary/packages/apps/PermissionController/AndroidManifest.xml
就是如下界面,之前有笔记详细分析过,这里不做重点讲解,直接用之前的方案即可: MTK平台-去除第一次开机-默认权限提示框


总结
- 多看源码,默认主屏幕、手势导航相关和其它平台不一样,多思考、多看源码、多推敲。
- 涉及到的手势知识点蛮多的,建议自行补充基本知识点。在遇到手势不可用的时候要有自己的思路,然后一步一步解决实际问题
- 这里在
在这里插入代码片中 默认主屏幕和在广播中模拟设置主屏幕其实是重复的,没关系这里实现功能即可。