组件化如何让Android Library拥有Application生命

Posted 凌日新

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了组件化如何让Android Library拥有Application生命相关的知识,希望对你有一定的参考价值。

JetPack之App Startup改造过程

  1. 组件化过程中,遇到了什么困难?
  2. 对这困难,你有啥想法?
  3. App Startup
  4. App Startup优点缺点
  5. 改造思路
  6. 优化体验
  7. 开源代码
  8. 参考资料

组件化过程中,遇到了什么困难

我们希望将业务划分为一个个的模块(Library),从而更好的组装、拆卸、迭代业务。
通过业务模块在开发时,可能需要接入一些SDK(外部or内部),这些SDK都会要求在Application中初始化。

方案一:指定一个Application类,把这些初始化的代码,写到Application中。

public class GlobalApp extends BaseApplication {

    @Override
    protected void setup() {
        initAllProcessDependencies(this);
        if (ProcessUtils.isMainProcess()) {
            initMainProcessDependencies();
        }
    }

    //以下依赖将在所有进程中初始化
    public void initAllProcessDependencies(Application app) {
        CApp.init(app);
        Utils.init(CApp.getApp());
        initTinker();
        initICBC(app);
    }

    //以下依赖仅在主进程中初始化
    public void initMainProcessDependencies() {
        ClideFactory.init(CApp.getApp(), R.mipmap.image_error);
        initReporter();
        KV.init(CApp.getApp());
        initRouter();
        initCrash();
        initBugly();
        initFPush(UDIDUtils.getUniqueID_NotPermission());
        initWOS();
        initLocation();
        initHeader();
        initDeviceId();
        initDun();
        initFFMpeg();
        initStackManage();
    }
	...
}

方案二:定义一个ContentProvider,将初始化的代码放到ContentProvider去初始化。

public class Sdk1InitializeProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        Sdk1.init(getContext());
        return true;
    }
	...
}

然后在androidManifest.xml文件中注册这个privoder,如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="top.gradle.sdk1">
    <application>
        <provider
            android:authorities="${applicationId}.init-provider"
            android:name=".Sdk1InitializeProvider"
            android:exported="false"/>
    </application>
</manifest>

每一个Library都创建一个自己的ContentProvider,那么…

dependencies {
    implementation project(":FirstSdkInitialize")
    implementation project(":SecondSdkInitialize")
	...
}

最后构建出来的apk就会有多个provider注册,官方称每一个空的provider都会带来2毫秒的启动延时消耗。

项目模块化之后,会存在很多的模块,如果每个模块都创建一个ContentProvider会导致App启动负担过大,而且如果有模块初始化存在依赖关系(比如:B模块初始化,要等A初始化完成后再初始化),这种方案就没法实现了。

方案三:将全类名,写到Application标签的meta-data中,在Application中,去反射这些全类名,从而加载它们。这样还是要将一部分代码放到Application中,仍然很难解决依赖的问题。

这个就不写例子了,Application中先读meta-data的name,通过反射把name中描述的全类名加载出来。


对这困难,你有啥想法?

嗯?如果结合方案二和方案三,是不是可以创造一个新东东,既可以不在Application中写代码,又可以不用写很多的ContentProvider…


App Startup

没错,这就是JetPack家族中App Startup组件的思路,它提供了一个ContentProvider代理类,取代了其它ContentProvider的开销,让项目不会因为ContentProvider越来越多而启动速度越来越慢,然后让其它模块注册meta-data全类名时,通过meger标识放到同一个ContentProvider代理类的上下文中,在ContentProvider代理类中,统一去加载。

App Startup到底能带来多大的提升呢?

这是Google在Pixel2的Android 10上做的测试,每一个ContentProvider的增加,至少会带来2毫秒的消耗,这仅仅是空ContentProvider的附加成本。

那么使用App Startup,在模块越多的项目中,效果越好,它会一定程度的加快App启动速度,而且JetPack家庭中的WorkManager和Lifecycle都是基于它来实现库初始化工作的。

这么优秀,那接下来我们了解一下它怎么用的吧。

  • 第一步,让Library依赖app startup库
dependencies {
    implementation "androidx.startup:startup-runtime:1.0.0-alpha02"
...
}
  • 第二步,写一个简单的初始化类
public class FirstSdkInitialize {

    private Context applicationContext;

    private FirstSdkInitialize() {
    }

    public static FirstSdkInitialize getInstance() {
        return FirstSdkInitializeHold.INSTANCE;
    }

    private static class FirstSdkInitializeHold {
        public static final FirstSdkInitialize INSTANCE = new FirstSdkInitialize();
    }

    public void init(Context applicationContext) {
        this.applicationContext = applicationContext.getApplicationContext();
        Log.d("cheetah","卢本伟牛逼~");
    }

    public Context getContext() {
        return applicationContext;
    }

}
  • 第三步,写一个组件初始化代理类
public class FirstInitializeProxy implements Initializer<FirstSdkInitialize> {
    @NonNull
    @Override
    public FirstSdkInitialize create(@NonNull Context context) {
        FirstSdkInitialize.getInstance().init(context);
        return FirstSdkInitialize.getInstance();
    }

    @NonNull
    @Override
    public List<Class<? extends Initializer<?>>> dependencies() {
        return Collections.emptyList();
    }
}
  • 第四步,清单文件中注册
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.wuba.financial.firstsdkinitialize">

    <application>
        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge">
            <!-- This entry makes ExampleLoggerInitializer discoverable. -->
            <meta-data
                android:name="com.wuba.financial.firstsdkinitialize.FirstInitializeProxy"
                android:value="androidx.startup" />
        </provider>
    </application>
</manifest>
  • 上面,第一个Library就写完了,使用同样的方法,写第一个Library,最后让app模块,依赖它俩,就可以运行了。
dependencies {
    implementation project(":FirstSdkInitialize")
    implementation project(":SecondSdkInitialize")
	...
}

我们看到,它是通过一个InitializationProvider类,加载两个模块的meta-data的。

  • 那么这里,能不能让卢本伟先回来,再夸他呢?

  • 可以,咱让第一个Library,依赖第二个Library,就可以实现。

dependencies {
    implementation "androidx.startup:startup-runtime:1.0.0-alpha02"
    implementation project(":SecondSdkInitialize")
...
}
  • 然后在第一个Library的初始化类中,传个依赖关系list给组件。
public class FirstInitializeProxy implements Initializer<FirstSdkInitialize> {
    @NonNull
    @Override
    public FirstSdkInitialize create(@NonNull Context context) {
        FirstSdkInitialize.getInstance().init(context);
        return FirstSdkInitialize.getInstance();
    }

    @NonNull
    @Override
    public List<Class<? extends Initializer<?>>> dependencies() {
        List<Class<? extends Initializer<?>>> dependencies = new ArrayList<>();
        dependencies.add(SecondInitializeProxy.class);
        return dependencies;
    }
}

  • 这样顺序初始化就实现了,但…这样Library之间就存在依赖关系啦,咱们组件化的项目,希望组件间尽可能的不产生依赖,咋办…

App Startup优点缺点

优点:

  • 解决了多个sdk初始化导致Application文件和Mainfest文件需要频繁改动的问题,同时也减少了Application文件和Mainfest文件的代码量,更方便维护了
  • 方便了sdk开发者在内部处理sdk的初始化问题,并且可以和调用者共享一个ContentProvider,减少性能损耗。
  • 提供了所有sdk使用同一个ContentProvider做初始化的能力,并精简了sdk的使用流程。
  • 符合面向对象中类的单一职责原则
  • 有效解耦,方便协同开发

缺点:

  • 会通过反射实例化Initializer<>的实现类,在低版本系统中会有一定的性能损耗。
  • 模块间遇到(B模块初始化,要等A初始化完成后再初始化)这种情况,就会让模块间产生依赖。
  • 不支持后台初始化

关于App Startup,请自行官网了解,这里就不再赘述。
下面主要针对缺点,进行一些改造。
它让模块化之后的业务模块之间又产生了依赖关系,这个不能接受。


改造思路

试着动手改造App Startup

  1. 先解决(B模块初始化,要等A初始化完成后再初始化)的问题。
    1. 在初始化类上写个优先级注解
    2. 在ContentProvider代理类中,拿到Class后,去读优先级,然后排序
    3. 根据排序好的Class,逐个初始化
//定义初始化类
@QuickPriority(ModulePriority.LEVEL_2)
public class A extends InitService {
    @Override
    protected void bindApplication(Context context) {
        Log.d("cheetah","看我,我手指放松,我目光如龙,当敌人是空,我战法无穷。"+Thread.currentThread().getId());
    }
}
//注册到xml中
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.wuba.borrowfinancials.cid">
    <application>
        <provider
            android:name="com.wuba.borrowfinancials.knife.init.ModuleInitProxy"
            android:authorities="${applicationId}.module-init"
            android:exported="false"
            tools:node="merge">
            <!-- This entry makes ExampleLoggerInitializer discoverable. -->
            <meta-data
                android:name="com.wuba.borrowfinancials.cid.CidInitService"
                android:value="module.knife" />
        </provider>
    </application>
</manifest>

//在代理类中加载
public class ModuleInitProxy extends ContentProvider {

    @Override
    public boolean onCreate() {
        try {
            ComponentName provider = new ComponentName(mContext.getPackageName(),
                    ModuleInitProxy.class.getName());
            ProviderInfo providerInfo = mContext.getPackageManager()
                    .getProviderInfo(provider, GET_META_DATA);
            Bundle metadata = providerInfo.metaData;
            if (metadata != null) {
                Set<Class<?>> initializing = new HashSet<>();
                List<ModuleInitOrderBean> orderBeans = new ArrayList<>();
                Set<String> keys = metadata.keySet();
                for (String key : keys) {
                    String value = metadata.getString(key, null);
                    if (ModuleInitUtils.MODULE_KNIFE.equals(value)) {
                        Class<?> clazz = Class.forName(key);
                        if (InitService.class.isAssignableFrom(clazz)) {
                            Class<? extends IModuleInitializer> component =
                                    (Class<? extends IModuleInitializer>) clazz;
                            ModuleInitOrderBean bean = new ModuleInitOrderBean();
                            bean.setClazz(component);
                            bean.setPriority(ModuleInitUtils.getPriority(clazz));
                            orderBeans.add(bean);
                        }
                    }
                }
                Collections.sort(orderBeans);
                for (ModuleInitOrderBean bean : orderBeans) {
                    try {
                        Object instance = bean.getClazz().getDeclaredConstructor().newInstance();
                        IModuleInitializer initializer = (IModuleInitializer) instance;
                        initializer.create(mContext);
                        initializing.remove(bean.getClazz());
                    } catch (Throwable throwable) {
                        throw new ModuleInitException(throwable);
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {
            throw new ModuleInitException(exception);
        }
        return true;
    }
    // ...
}
  • 注:篇幅问题仅展示核心代码
  1. 再提供一种子线程初始化的方法,但通常不建议放子线程中,控制不好就会挂,极限启动优化,可能会用。
    1. 定义优先级注解为Integer的最大值,则标识需要子线程初始化
    2. 读取优先级时,将这些Class,单独拿出来,开线程初始化它们。
try {
            ComponentName provider = new ComponentName(mContext.getPackageName(),
                    ModuleInitProxy.class.getName());
            ProviderInfo providerInfo = mContext.getPackageManager()
                    .getProviderInfo(provider, GET_META_DATA);
            Bundle metadata = providerInfo.metaData;
            if (metadata != null) {
                Set<Class<?>> initializing = new HashSet<>();
                List<ModuleInitOrderBean> orderBeans = new ArrayList<>();
		List<ModuleInitOrderBean> delayBeans = new ArrayList<>();
                Set<String> keys = metadata.keySet();
                for (String key : keys) {
                    String value = metadata.getString(key, null);
                    if (ModuleInitUtils.MODULE_KNIFE.equals(value)) {
                        Class<?> clazz = Class.forName(key);
                        if (InitService.class.isAssignableFrom(clazz)) {
                            Class<? extends IModuleInitializer> component =
                                    (Class<? extends IModuleInitializer>) clazz;
                            ModuleInitOrderBean bean = new ModuleInitOrderBean();
                            bean.setClazz(component);
                            bean.setPriority(ModuleInitUtils.getPriority(clazz));
                            if (bean.isDelay()) {
                                delayBeans.add(bean);
                            } else {
                                orderBeans.add(bean);
                            }
                        }
                    }
                }
                Collections.sort(orderBeans);
                for (ModuleInitOrderBean bean : orderBeans) {
                    try {
                        Object instance = bean.getClazz().getDeclaredConstructor().newInstance();
                        IModuleInitializer initializer 如何让coordinatorlayout自动往上滚动 android

Android组件化和插件化开发

Android 组件化使用 Gradle 实现组件化 ( 组件 / 集成模式下的 Library Module 开发 )

如何让 Visual Studio 2015 安装程序知道我已经拥有 Android SDK?

Android - 如何更改 TextInputLayout(材料组件)中的文本颜色?

Android 组件化路由组件 ( 路由组件结构 )