概要
本文主要介绍Android13的版本下,设置默认Launcher的服务端代码调用过程,以及如何自己写代码设置默认应用
本文主要参考文章:
- https://blog.csdn.net/tq501501/article/details/126462114
- https://blog.csdn.net/lmpt90/article/details/134484614
一、开发Launcher应用
相信大家都了解Launcher本身是一个app,只不过其功能比较特殊,能提供一系列的app管理、与用户交互的可视化界面。如何让普通应用成为Launcher呢:
其实就是在activity中增加两条category:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!-- 增加以下两条category -->
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
二、多Launcher环境下如何设置默认的Launcher
经过网上的资料,有以下几种方案:
-
1、系统中如果存在多个Launcher,则开机会弹出一个选择器,让你选择哪个Launcher作为主Launcher,这个我有遇到过,概率性触发,但是不清楚其触发逻辑,如果有了解的小伙伴可以留言评论下~~
-
2、通过原生设置–>应用–>默认应用–>主屏幕 设置默认主屏幕即可
-
3、通过adb命令设置:adb shell pm set-home-activity com.android.launcher3
-
4、通过系统配置,再编译系统刷机(尚未尝试)
-
5、自己写代码控制,这个需要开发成系统app方可
三、查看方案2的过程
通过查看原生的Settings应用,设置默认Launcher的过程,学习其中的逻辑,自己也能手搓代码实现“设置默认应用”
在上述方案2中,设置默认主屏幕,由于我本人没咋看过Settings应用,里面的代码就不展开了,免得误人子弟。
在网上检索到,点击事件最终会调用到ManageRoleHolderStateLiveData#setRoleHolderAsUser()方法中,因此我在该方法里面打印下堆栈,最终发现其调用过程:
java.lang.Exception
at com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData.setRoleHolderAsUser(ManageRoleHolderStateLiveData.java:109)
at com.android.permissioncontroller.role.ui.DefaultAppViewModel.setDefaultApp(DefaultAppViewModel.java:87)
at com.android.permissioncontroller.role.ui.DefaultAppChildFragment.setDefaultApp(DefaultAppChildFragment.java:254)
at com.android.permissioncontroller.role.ui.DefaultAppChildFragment.onPreferenceClick(DefaultAppChildFragment.java:246)
at androidx.preference.Preference.performClick(Preference.java:1186)
at androidx.preference.Preference.performClick(Preference.java:1168)
at androidx.preference.CheckBoxPreference.performClick(CheckBoxPreference.java:90)
at androidx.preference.Preference$1.onClick(Preference.java:181)
at android.view.View.performClick(View.java:7542)
at android.view.View.performClickInternal(View.java:7519)
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
at android.view.View$PerformClick.run(View.java:29476)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7963)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
View层面的话,就是DefaultAppChildFragment里面响应了点击事件,最终调用到了ManageRoleHolderStateLiveData.setRoleHolderAsUser(),本文不去探讨Settings应用的界面组成、调用过程。只关注于怎么设置的默认Launcher。
因此先看ManageRoleHolderStateLiveData.setRoleHolderAsUser()方法:
public void setRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
boolean add, int flags, @NonNull UserHandle user, @NonNull Context context) {
......
RoleManager roleManager = context.getSystemService(RoleManager.class);
Executor executor = context.getMainExecutor();
Consumer<Boolean> callback = successful -> {
if (successful) {
if (DEBUG) {
Log.i(LOG_TAG, "Package " + (add ? "added" : "removed")
+ " as role holder, role: " + roleName + ", package: " + packageName);
}
setValue(STATE_SUCCESS);
} else {
if (DEBUG) {
Log.i(LOG_TAG, "Failed to " + (add ? "add" : "remove")
+ " package as role holder, role: " + roleName + ", package: "
+ packageName);
}
setValue(STATE_FAILURE);
}
};
if (add) {
roleManager.addRoleHolderAsUser(roleName, packageName, flags, user, executor, callback); // 核心代码就是这里
} else {
roleManager.removeRoleHolderAsUser(roleName, packageName, flags, user, executor,
callback);
}
}
核心就这个:roleManager.addRoleHolderAsUser()。
因此如果我们想要自己写代码的去控制默认Launcher的话,直接这样写:
public void setDefaultHome(Context context){
RoleManager mRoleManager = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
mRoleManager = context.getSystemService(RoleManager.class);
}
final Executor executor = getMainExecutor();
Consumer<Boolean> callBack = success -> {
if (success){
// 在这里可以保存到系统属性中,方便其他地方取出来用,看个人情况吧
Settings.Global.putString(context.getContentResolver(), "defalut_home", "你的应用包名");
Log.d(TAG, "setDefaultHome: success");
}else{
Log.d(TAG, "setDefaultHome: unSuccess");
}
};
UserHandle userHandle = Process.myUserHandle();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mRoleManager.addRoleHolderAsUser("android.app.role.HOME",
"com.example.app7", 0, userHandle, executor, callBack);
}
}
注意:这样写需要应用设置为系统应用,在AndroidManifest中,添加上android:sharedUserId="android.uid.system", 同时别忘了添加上权限:android.permission.MANAGE_ROLE_HOLDERS
四、系统应用层以及服务层的调用过程
mRoleManager.addRoleHolderAsUser()发起系统层的调用,往下的调用过程流程图如下:
也没什么难度,就是应用层于服务层之前的相互调用,具体调用代码也是很简单,小伙伴们感兴趣的话自行查看。有个问题要注意下,这里面的类在不同的包下,找起来比较麻烦,我这里给大家标注出来吧:
packages\modules\Permission\PermissionController\src\com\android\permissioncontroller\role\ui 包下
- ManageRoleHolderStateLiveData.java
packages\modules\Permission\framework-s\java\android\app\role 包下
- RoleManager.java
- IRoleManager.aidl
- RoleControllerManager.java
- RoleControllerService.java
packages\modules\Permission\service\java\com\android\role 包下
- RoleService.java
- RoleUserState.java
packages\modules\Permission\PermissionController\src\com\android\permissioncontroller\role\service 包下:
- RoleControllerServiceImpl.java
最终调用到RoleUserState#addRoleHolder()方法:
@CheckResult
public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {
boolean changed;
synchronized (mLock) {
ArraySet<String> roleHolders = mRoles.get(roleName);
if (roleHolders == null) {
Log.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
+ ", package: " + packageName);
return false;
}
// 核心1:添加到内存列表数据中
changed = roleHolders.add(packageName);
// 核心2:数据固化:保存到地址 /data/misc_de/0/apexdata/com.android.permission/roles.xml
if (changed) {
scheduleWriteFileLocked();
}
}
// 3 回调通知下各单位,roleName这个属性的应用已经更新了
if (changed) {
mCallback.onRoleHoldersChanged(roleName, mUserId);
}
new Exception().printStackTrace();
return true;
}
在注释2哪里,会把默认应用的包名固化到xml中,保存的路径是:/data/misc_de/0/apexdata/com.android.permission/roles.xml
注意这里的保存数据是异步的,有200ms的延迟,大家注意下实时性。
五、总结
从settings应用中,发起设置默认Launcher,关键的代码就是:roleManager.addRoleHolderAsUser()发起服务端的响应过程。因此我们在自己的代码中如果想设置默认应用的话,也通过这个方法即可。
注意:其中该方法有个String类型的RoleName需要注意下,就是你要设置的默认app的类型:
android.app.role.HOME
: 设置默认Launcher
android.app.role.System_UI
:设置默认的SystemUI
android.app.role.BROWSER
:设置默认浏览器
…
你要设置什么默认类型,就填其对应的RoleName即可。
既然设置了默认的Launcher,那么开机启动的时候又是怎么选择默认的Launcher呢,后面继续~