Android收藏集

Android动态更换启动图标,以及原理

2020-09-20  本文已影响0人  巴黎没有摩天轮Li

前言

笔者最近看到百度网盘的启动图标可以随着办理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功能有一下几点:

启动一个新的图标

<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。

上一篇 下一篇

猜你喜欢

热点阅读