Android動態更換啟動圖標,以及原理

前言

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。