碎碎念 项目在多模块的过程中,出现一个比较烦躁的问题。那就是模块内,需要在 application 中初始化后,调用自己模块的一些代码。可能这样解释会比较抽象。举个例子吧~
现在的项目结构是这样的
image.png
假设需要在 Applicaion 的 onCreate 方法被调用后,调用 ShopModule 中的一些初始化方法。这样我们就面临着一些问题
如何通知 ShopModule 模块 Application 的 onCreate 被调用了 如何收集各个模块中的特定对象然后进行操作。 能否控制优先级别。 比如一些模块可能需要早点初始化 能否集中管理线程。 有一些模块可能需要异步初始化 BootStrap 使用方式
- 导入相关依赖 implementation 'com.balala.bootstrap:bootstrap-core:1.0.3' annotationProcessor 'com.balala.bootstrap:bootstrap-compiler:1.0.3'
- 编写相关逻辑代码 @BootStrapApp(priority = 100, name = "RouterBootStrap",isMainThread = true) public class RouterBootStrap extends BootStrapProxy { @Override public void onCreate(Application application) { if (application instanceof CommonApplication) { CommonApplication commonApplication = (CommonApplication) application; if (commonApplication.enableDebug()) { ARouter.openLog(); ARouter.openDebug(); ARouter.printStackTrace(); } } ARouter.init(application); } } 这样的话,你所编写的 RouterBootStrap 就会在 APP 启动的时候就会自动调用了 onCreate 方法。可以看到这里还可以配置运行的优先级和是否在主线程运行。
优先级 线程 BootStrap 背后的故事 很多时候,并不想造轮子。只是有一些处理方式,在尝试后感觉处理方式不是特别的好。然后在慢慢的尝试的过程中,发现自己对处理这类问题也有了一些心得。所以就尝试的来用自己的方式,去处理问题。
下面是我们的几种尝试:
- ARouter 的处理方式 我们项目中使用的是阿里的 ARouter 的路由框架,在使用的过程中,我们发现了一个对象,可以当做模块之间的通信方式( IProvider ),我们借助这个对象来完成上述的需求。
首先是对 IProvider 进行封装,因为项目中不仅仅使用这个来进行初始化,而且使用他用来模块之间的通讯
//异步模块同步的方式 public interface MProviderSyn extends IProvider {
/**
* 模块之前交互通过的接口
*
* @param context 上下文对象
* @param param 需要传递到模块间的参数
* @return 模块需要返回的参数
*/
public Map<String, String> doExecute(Context context, Map<String, String> param);
} 然后我们就可以在需要初始化的模块来实现这个接口。
@Route(path = "/user/init") public class UserModuleProvider implements MProviderSyn { @Override public Map<String, String> doExecute(Context context, Map<String, String> param) { return null; }
@Override
public void init(Context context) {
Toast.makeText(context, "userInit", Toast.LENGTH_LONG).show();
}
} 这样的话,我们就可以在 Application 初始化的时候,来初始化 UserModuleProvider 这个对象,从而达到上面的需求。
MProviderManager.getProvider("/user/init"); 在项目的使用过程中会慢慢的发现,这样的方式还会存在有一些问题
Application 还是会对一些模块具有感知。 因为还是需要在 Application 中的 onCreate 的方法,来初始化一些模块对象。而这些模块对象是可变的(当删除一个初始化对象,就需要进行更改),所以 Application 里面的方法会经常改动。 因为借助路由框架,这样就造成了一些组件库(不依赖于路由框架)不能使用这种模式。 就这样,我们开始尝试的去寻找其他方式。
- ContentProvider 的处理方式 ContentProvider 的方式,是无意间想知道 AndroidUtilsCode 在什么时候,保存 application 对象所知道的。
public static final class FileProvider4UtilCode extends FileProvider {
@Override
public boolean onCreate() {
Utils.init(getContext());
return true;
}
}
可以知道,当应用一启动的时候, ContentProvider 的 onCreate 的方法就会被调用。但是它会比 Application 的 onCreate 的调用时机晚。这个时候已经可以获取到 Context 对象了。而一些模块仅仅需要获取到上下文对象。所以在这个时候调用可以满足大部分模块的需求。而且可以解决上述 ARouter 存在的问题。
public class PermissionProvider extends ContentProvider { @Override public boolean onCreate() { Application application = (Application) Objects.requireNonNull(getContext()).getApplicationContext(); SoulPermission.init(application); return false; }
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
} 我们开始尝试着新的写法一段时间后,又发现了几个问题。
AndroidManifest.xml 于是,我们开始尝试整合上述的问题和解决方案,尝试着开发出一套新的解决方案。
BootStrap 创建 BootStrap 创建的初衷就是为了处理多模块间 Application 初始化比较繁琐。那么如何收集每个模块需要初始化的对象呢?我们这里使用的是 APT 。使用 APT 将各个模块需要初始化的对象整合在一起。
image.png
上图是一个规划图,我们希望每个模块可以使用特定注解( BootStrapApp ),然后将带有特定注解( BootStrapApp )的类收集在一起,最后可以在 Application 初始化的地方。将我们收集的各个模块的类的信息。通过反射进行统一的调用。
- 需要处理的问题 在收集各个模块带有注解的类的时候,发现各个模块之间会生成相同包名和类名的 java 文件。这样就无法收集各个模块中带有特定注解( BootStrapApp )。我们开始尝试查看 ARouter 是如何处理这样的问题。发现它在初始化话的过程中会去扫描 dex 文件,然后去获取特定的类文件( APT 生成的文件,都实现一个特定的接口,然后根据这个接口,查找实现类)。
在获知这种实现方式的时候,我们并不是特别赞同这种方式
dex ClassLoader dex 之后,我们又发现了一个处理方式,那就是使用 gradle 插件。在 Transform 的时候,对特定的 java 文件进行整合。但是这样的话,会增加插件的配置成本。而且插件开发并不是我们所熟悉的领域
最后,我们尝试着不让 apt 生成 java 文件,而是直接生成一个 json 文件。然后将 json 文件放到 assets 目录中,这样也完成了对特定类的收集。打包完成后的 assets 文件夹
image.png
- BootStrap 的使用 当我们处理完 APT 的问题后,我们来尝试的解决上述存在的问题:
AndroidManifest.xml 在处理问题 2 , 3 的时候,我们在注解上多加了一些属性,来标记它的运行特征
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface BootStrapApp {
/**
* 默认名称
*
* @return 相关名称
*/
String name() default "";
/**
* 组信息
*
* @return 配置组信息
*/
String group() default "application";
/**
* 初始化顺序,优先级越高,越早初始化
*
* @return 优先级
*/
int priority() default -1;
/***
* 是否在主线程中运行
* @return 默认运行在主线程中
*/
boolean isMainThread() default true;
} 当我们增加了一些特定的属性,意味着我们可以在逻辑代码中,根据注解的属性。运行相应的逻辑。(比如说是否运行在主线程,又或者优先级)。
//执行方法,主函数 for (BootStrapAppModel model : mainThreadExecute) { executeMethod(model.bootstrapWrapApplication, args); }
//执行方法,异步函数
new AppExecutors().diskIO().execute(() -> {
for (BootStrapAppModel model : runnableExecute) {
executeMethod(model.bootstrapWrapApplication, args);
}
});
在处理完问题 2 , 3 的时候,让我们处理问题
1 . 模块之间的耦合性很大
4 . 需要操作的步骤太多了,而且需要配置 AndroidManifest.xml
因为使用 APT 来收集特定类的信息,然后统一对特定的类进行处理。这就意味着模块之间的耦合程度就大大的减少了。那像问题 1 的问题就不存在了。而问题 4 是因为之前没有统一管理。现在我们只要定义出模块内部的 ContentProvider 。这样就可以初始换 applicaiton 方法了。而这部分逻辑已经在模块内部实现。对于开发者完全透明的。开发者只需要编写相关实现即可。
public class BootStrapProvider extends ContentProvider { @Override public boolean onCreate() { Application application = (Application) Objects.requireNonNull(getContext()).getApplicationContext(); BootStrapUtils.init(application); BootStrapApplicationCore.invokeApplicaitonOnCreate(application); return false; }
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
} 3. BootStrap 中组的概念 在编写 APT 插件的时候,我们会解析 group 字段来作为索引的标识,并且将它缓存到内存中。 BootStrap 内部会维护相关组的信息和实例。
image.png
只用当你手动调用
BootStrap.invokeGroupMethod("exit", this); BootStrap.findListForGroupMain("exit"); 这些有关组的操作的时候,这些组的实例才会被创建。而当我们使用 BootStrapApp 注解的时候,并不需要去指定特定的组信息。因为 BootStrapApp 默认的组就是 application ,而且会在 APP 启动的时候,自动初始化 application 这个组的相关实例,并且调用方法。
那么为什么我们需要知道组的概念吗?假设我们在 APP 退出的时候,需要告诉其他模块进行一些退出操作,这个时候,组的概念就非常有必要了。我们可以在其他模块中完成退出所需要的逻辑,然后将它们归类为 exit 的组。在真正的退出模块中,只需要调用
BootStrap.invokeGroupMethod("exit", this); 这样框架内部,就会帮你去寻找该组下的所以实例,然后调用其方法。