多种方式实现动态替换Android默认桌面Launcher
背景简介
Launcher-是安卓系统中的桌面启动器,安卓系统的桌面UI统称为Launcher。Launcher是安卓系统中的主要程序组件之一,安卓系统中如果没有Launcher就无法启动Android
- 当前场景
现在安卓设备应用越来越广泛,有些出厂前可能没有预留系统OTA升级的入口,但是有app的升级通道,而又在一些情况下需要更改已经出厂的系统默认桌面,所以针对这种情况需要实现一种方案,在不进行系统升级更改的情况下,只通过升级App的方式,实现动态替换默认Launcher的方案
技术方案
三种方案
-
用代码设置系统配置,保留两个Launcher应用进行动态切换
-
动态删除添加系统默认/system/priv-app/目录下的默认Launcher.apk文件
-
不设置Launcher,通过获取系统底层对Activity状态的监控,拦截Launcher
前三种方案都需要系统权限,并在AndroidManifest.xml 中配置Launcher属性
- 系统权限,配置shareUserId,并且用系统签名文件进行签名
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.launcher"
android:sharedUserId="android.uid.system">
- 新增Launcher属性
<activity android:name=".MainActivity">
<intent-filter>
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</acivity>
方案一
使用代码配置动态Launcher,需要额外的添加一个系统级别的权限
<permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"/>
该权限允许应用程序,进行系统属性的配置更改,有了该权限后,才可以进行动态的属性配置。
清除默认Launcher:
- 安装运行自定义Launcher应用
- 遍历所有配置Launcher属性的应用
- 清除除所有应用Launcher属性
- 添加自定义桌面应用Launcher属性配置
private void clear(Context context){
ArrayList<IntentFilter> intentList = new ArrayList<>();
ArrayList<ComponentName> cnList = new ArrayList<>();
context.getPackageManager().getPreferredActivities(intentList, cnList, null);
for(int i = 0; i < cnList.size(); i++) {
IntentFilter dhIF = intentList.get(i);
if(dhIF.hasAction(Intent.ACTION_MAIN) && dhIF.hasCategory(Intent.CATEGORY_HOME)) {//遍历过滤所有launcher
try {
String name = cnList.get(i).getPackageName();
//清除原有的默认launcher
context.getPackageManager().clearPackagePreferredActivities(name;
}catch (Exception ex){
}
}
}
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);
final int N = mHome.size();
ComponentName[] set = new ComponentName[N];
for (int i = 0; i < N; i++) {
ResolveInfo r = mHome.get(i);
set[i] = new ComponentName(r.activityInfo.packageName,r.activityInfo.name);
}
//设置自定义launcher
ComponentName launcher = new ComponentName(hiBoxLauncher, hiBoxActivity);
context.getPackageManager().addPreferredActivity(filter, 1081344, set, launcher);
}
恢复默认Launcher方法同上一样,清除完所有Launcher相关的意图配置,最后添加上系统Launcher包名。
方案二
通过文件删除的方式进行Launcher替换,需要文件的读写权限,而且系统应用删除后需要重启才能生效,所以还需要reboot的权限
<permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<permission android:name="android.permission.REBOOT"/>
清除默认Launcher:
- 安装运行自定义Launcher应用
- 复制/system/priv-app/Launcher2.apk保存到sdcrad
- 删除/system/priv-app/Launcher2.apk
- 重启安卓系统,只留下自定义Launcher应用生效
恢复默认launcher只需要将事先保存的apk,拷贝到系统目录即可,当前系统会存在两个Launcher,需要手动选择一次默认Launcher
方案三
不需要替换Launcher,通过反射的方式注册系统android.app.
IActivityController的隐藏aidl回调。监听每个应用的Activity界面的状态,当回调系统显示Launcher的时候拦截回调,打开自定义的应用界面,覆盖系统Launcher,需要系统权限,还需要额外的Activity设置权限
<uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER" />
1.声明aidl文件
在工程中声明一个android.app.IActivityController的aidl,用于注册系统的回调,可以直接从Android源码中拷贝
interface IActivityController{
boolean activityStarting(in Intent intent, String pkg);
boolean activityResuming(String pkg);
boolean appCrashed(String processName, int pid,
String shortMsg, String longMsg,
long timeMillis, String stackTrace);
int appEarlyNotResponding(String processName, int pid, String annotation);
int appNotResponding(String processName, int pid, String processStats);
}
2.反射注册aidl回调代码:
//在进入页面时调用setActivityController()方法,注册aidl回调,当界面有变化时会触发activityStarting(),activityResuming()事件
public void setActivityController() {
Log.d(TAG,">>>>>>0519,");
try {
Class<?> cActivityManagerNative = Class.forName("android.app.ActivityManagerNative");
Method mGetDefault = cActivityManagerNative.getMethod("getDefault", new Class[]{});
Object oActivityManagerNative = mGetDefault.invoke(null, new Object[]{});
Method mSetActivityController = cActivityManagerNative.getMethod("setActivityController",
Class.forName("android.app.IActivityController"));
mSetActivityController.invoke(oActivityManagerNative, new ActivityController());
} catch (ClassNotFoundException e) {
exceptionWhenSet(e);
}
3.拦截Launcher
绑定服务,获取系统回调的Activity状态接口,进行应用打开操作
private class ActivityController extends android.app.IActivityController.Stub {
public boolean activityStarting(Intent intent, String pkg) {
if(launcher2.equals(pkg)&&isFilter){
startHiBoxLauncher(); }
return true;
}
public boolean activityResuming(String pkg) {
Log.d(TAG,">>>>>>0519,pkg=" + pkg);
if(launcher2.equals(pkg)&&isFilter){
startHiBoxLauncher();
}
return true;
}
}
如果是管理员进行操作,需要恢复Launcher,可以用过标志位Boolen值来控制是否拦截系统Launcher
风险
-
1.重启安卓设备Launcher配置重置,方案一代码设置生效后,不需要手动进行选择,可以同时在两个launcher中切换,但是测试发现,安卓设备重启后,会清除配置,需要重新选择默认Launcher
-
2.Android系统应用无法删除,在一些设备上测试发现,即使有了系统权限,只能对/system/priv-app/Launcher2.apk进行拷贝操作,无法进行删除操作。
-
3.签名不一致覆盖安装风险,所有的方案都需要重新系统签名,获取系统权限,跟之前Android应用签名不一致,可能会出现覆盖安装失败的问题