前言
筆者最近看到百度網盤的啟動圖標可以隨著辦理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。