组件化如何让Android Library拥有Application生命
Posted 凌日新
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了组件化如何让Android Library拥有Application生命相关的知识,希望对你有一定的参考价值。
JetPack之App Startup改造过程
组件化过程中,遇到了什么困难
我们希望将业务划分为一个个的模块(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
- 先解决(B模块初始化,要等A初始化完成后再初始化)的问题。
- 在初始化类上写个优先级注解
- 在ContentProvider代理类中,拿到Class后,去读优先级,然后排序
- 根据排序好的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;
}
// ...
}
- 注:篇幅问题仅展示核心代码
- 再提供一种子线程初始化的方法,但通常不建议放子线程中,控制不好就会挂,极限启动优化,可能会用。
- 定义优先级注解为Integer的最大值,则标识需要子线程初始化
- 读取优先级时,将这些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 组件化使用 Gradle 实现组件化 ( 组件 / 集成模式下的 Library Module 开发 )
如何让 Visual Studio 2015 安装程序知道我已经拥有 Android SDK?