前言
笔者最近看到百度网盘的启动图标可以随着办理SVIP可以动态更换启动器图标。所以自己试着搞了一下。
LaunchUtil
class LaunchUtil(packageManager: PackageManager) {
var mPm: PackageManager = packageManager
// 双重锁单例
companion object {
fun getInstance(packageManager: PackageManager): LaunchUtil {
val instance: LaunchUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
LaunchUtil(packageManager)
}
return instance
}
}
// 开启四大组件的方法
fun enableComponent(componentName: ComponentName) {
mPm.setComponentEnabledSetting(
componentName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
}
// 禁用四大组件的方法
fun disableComponent(componentName: ComponentName) {
mPm.setComponentEnabledSetting(
componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
}
提到PackageManager,我们就复习下PM类吧。
Context#getPackageManager()
/** Return PackageManager instance to find global package information. */
public abstract PackageManager getPackageManager();
ContextImpl#getPackageManager()
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
ActivityThread#getPackageManager()
@UnsupportedAppUsage
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}
PackageManager是抽象类,所以很容易找到ApplicationPackageManager是其实现类, 而后找到APM的setComponentEnabledSetting()
ApplicationPackageManager#setComponentEnabledSetting()
@UnsupportedAppUsage
private final IPackageManager mPM
@Override
public void setComponentEnabledSetting(ComponentName componentName,
int newState, int flags) {
try {
mPM.setComponentEnabledSetting(componentName, newState, flags, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
IPackageManager是AIDL类,所以mPM成员变量是通过Binder调用到PackageManagerService。
然而我们知道PM功能有一下几点:
- 提供一个应用程序的所有信息(ApplicationInfo)。
- 提供四大组件的信息。
- 查询permission信息。
- 安装与卸载apk。
启动一个新的图标
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity-alias
android:name="Double11Alias"
android:enabled="false"
android:icon="@mipmap/ic_double_11"
android:label="双十一"
android:roundIcon="@mipmap/ic_launcher"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias
android:name="Double12Alias"
android:enabled="false"
android:icon="@mipmap/ic_double_12"
android:label="双十二"
android:roundIcon="@mipmap/ic_launcher"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
activity-alias标签就是多入口配置,例如著名的LeakCanary就是通过这种方式开启另一个子app,并放开启了一个新的Activity栈。
cnDefault = ComponentName(baseContext, "$packageName.DefaultAlias")
cnNewActivity = ComponentName(baseContext, "$packageName.NewActivity1")
launchUtil = LaunchUtil.getInstance(applicationContext.packageManager)
fun bt1(view: View) {
launchUtil!!.enableComponent(cnDefault!!)
}
fun bt2(view: View) {
launchUtil!!.disableComponent(cnDefault!!)
launchUtil!!.enableComponent(cnNewActivity!!)
}
通过这种开启与禁用四大服务的方式,通知PMS进行刷新请求,即可完成动态换ICON。