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

时间:2021-7-21 作者:qvyue

前言

笔者最近看到百度网盘的启动图标可以随着办理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。

启动一个新的图标

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。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。