最近发现的一个相当不错的插件化方案
1.AA的设置
类库的 defaultConfig模块语句中加入如下代码:
javaCompileOptions {
annotationProcessorOptions {
arguments = ["library": "true"]
}
}
dependencies和app写法一致:
kapt "org.androidannotations:androidannotations:$AAVersion"
compileOnly "org.androidannotations:androidannotations-api:$AAVersion"
AA的资源id写法与app中不同:
@EActivity(resName = "view_actionsheet")
open class KotlinActitivy : AppCompatActivity() {
@Click(resName = ["txt_cancel"])
open fun clickCancel(){
finish()
}
}
2.模块分层
projectRoot
+--app
+--module1
+--module2
+--standalone
| +--module1Standalone
| +--module2Standalone
在项目根目录下的 settings.gradle 里的代码是这样的:
// main app
include ':app'
// library modules
include ':module1'
include ':module2'
// for standalone modules
include ':standalone:module1Standalone'
include ':standalone:module2Standalone'
app 模块的 build.gradle 中的依赖部分代码如下:
dependencies {
implementation project(':module1')
implementation project(':module1')
}
以 standalone/module1Standalone 为例,它对应的 build.gradle 中的依赖为:
dependencies {
implementation project(':module1')
}
在 Android Studio 中创建模块,默认模块是位于项目根目录之下的,如果希望把模块移动到某个文件夹下面,需要对模块右键,选择 “Refactor – Move” 移动到指定目录之下。
2.1为每个模块准备 Application
在组件化之前,我们常常把项目中需要在启动时完成的初始化行为,放在自定义的 Application 中,根据本人的项目经验,初始化行为可以分为以下两类:
- 业务相关的初始化。例如服务器推送长连接建立,数据库的准备,从服务器拉取 CMS 配置信息等。
- 与业务无关的技术组件的初始化。例如日志工具、统计工具、性能监控、崩溃收集、兼容性方案等。
针对问题1:
@ModuleSpec
public class Module1Application extends Application {
@Override
public void onCreate() {
super.onCreate();
// do module1 initialization
Log.i("module1", "module1 init is called");
}
}
针对2,因为可能每个模块都需要初始化,这段初始化逻辑可以放在Standalone模块中:
public class Module1StandaloneApplication extends Module1Application {
//注意继承自上面类库中的Module1Application
@Override
public void onCreate() {
// module1 init inside super.onCreate()
super.onCreate();
// initialization only used for running module1 standalone
Log.i("module1Standalone", "module1Standalone init is called");
}
}
注意上面代码中的@ModuleSpec注解,告知 AppJoint 框架,我们需要确保当前模块该 Application 中的初始化行为,能够在最终全量编译时,被主 App 的 Application 类调用到。所以对应的,我们的主 App 模块(app 模块)的自定义 Application 类也需要被一个注解 – AppSpec 标记:
@AppSpec
public class App extends Application {
...
}
2.2 全局获取ApplicationContext的变化
@ModuleSpec
public class Module1Application extends Application {
public static Application INSTANCE;//注意是Application 非ApplicationContext
@Override
public void onCreate() {
super.onCreate();
INSTANCE = (Application)getApplicationContext();
// do module1 initialization
Log.i("module1", "module1 init is called");
}
}
3 模块间调用
理想情况下是不同模块做不同业务,不存在相互调用,但项目改造初期不太可能实现,用router模块来解决,我们在这个模块内定义 所有业务模块希望暴露给其它模块调用的方法,如下图:
projectRoot
+--app
+--module1
+--module2
+--standalone
+--router
| +--main
| | +--java
| | | +--com.yourPackage
| | | | +--AppRouter.java
| | | | +--Module1Router.java
| | | | +--Module2Router.java
在上面的项目结构层次中,我们在新建的 router 模块下定义了 3 个 接口:
AppRouter 接口声明了 app 模块暴露给 module1、module2 的方法的定义。
Module1Router 接口声明了 module1 模块暴露给 app、module2 的方法的定义。
Module2Router 接口声明了 module2 模块暴露给 module1、app 的方法的定义。
以 AppRouter 接口文件为例,这个接口的定义如下:
public interface AppRouter {
/**
* 普通的同步方法调用
*/
String syncMethodOfApp();
/**
* 以 RxJava 形式封装的异步方法
*/
Observable<String> asyncMethod1OfApp();
/**
* 以 Callback 形式封装的异步方法
*/
void asyncMethod2OfApp(Callback<String> callback);
}
这些方法是 app 模块需要暴露给 module1 、 module2 的方法,同时 app 模块自身也需要提供这个接口的实现,所以首先我们需要在 app 、module1 、module2 这三个模块的 build.gradle 文件中依赖 router 这个模块:
dependencies {
// Other dependencies
...
api project(":router")
}
然后我们回到 app 模块,为刚刚在 router 定义的 AppRouter 接口提供一个实现:
@ServiceProvider//注意这个annotation
public class AppRouterImpl implements AppRouter {
@Override
public String syncMethodOfApp() {
return "syncMethodResult";
}
@Override
public Observable<String> asyncMethod1OfApp() {
return Observable.just("asyncMethod1Result");
}
@Override
public void asyncMethod2OfApp(final Callback<String> callback) {
new Thread(new Runnable() {
@Override
public void run() {
callback.onResult("asyncMethod2Result");
}
}).start();
}
}
假设现在 module1 中需要调用 app 模块中的 asyncMethod1OfApp 方法:
AppRouter appRouter = AppJoint.service(AppRouter.class);
// 获得同步调用的结果
String syncResult = appRouter.syncMethodOfApp();
// 发起异步调用
appRouter.asyncMethod1OfApp()
.subscribe((result) -> {
// handle asyncResult
});
// 发起异步调用
appRouter.asyncMethod2OfApp(new Callback<String>() {
@Override
public void onResult(String data) {
// handle asyncResult
}
});
也就是说,如果一个模块需要提供方法供其他模块调用,需要做以下步骤:
1.把接口声明在 router 模块中
2.在自己模块内部实现上一步中声明的接口,同时在实现类上标记 @ServiceProvider 注解
3.1 模块独立编译运行模式下跨模块方法的调用
在模块单独编译运行期间,其它的模块是不参与编译的,它们的代码也不会打包进用于模块独立运行的 standalaone 模块,我们如何解决在模块单独编译运行模式下,跨模块调用的代码依然有效呢?
以 module1 为例,首先为了便于在 module1 内部任何地方都可以调用其它模块的方法,我们创建一个 RouterServices 类用于存放其它模块的接口的实例:
public class RouterServices {
// app 模块对外暴露的接口
public static AppRouter sAppRouter = AppJoint.service(AppRouter.class);
// module2 模块对外暴露的接口
public static Module2Router sModule2Router = AppJoint.service(Module2Router.class);
}
这时需要创建 Mock 了 AppRouter 和 Module2Router 功能的类。这些类由于只对 module1 的独立编译运行有意义,所以这些类最合适的位置是放在 module1Standalone 这个模块内,以 AppRouter 的 Mock 类 AppRouterMock 为例:
public class AppRouterMock implements AppRouter {
@Override
public String syncMethodOfApp() {
return "mockSyncMethodOfApp";
}
@Override
public Observable<String> asyncMethod1OfApp() {
return Observable.just("mockAsyncMethod1OfApp");
}
@Override
public void asyncMethod2OfApp(final Callback<String> callback) {
new Thread(new Runnable() {
@Override
public void run() {
callback.onResult("mockAsyncMethod2Result");
}
}).start();
}
}
在Application中改变引用:
public class Module1StandaloneApplication extends Module1Application {
@Override
public void onCreate() {
// module1 init inside super.onCreate()
super.onCreate();
// initialization only used for running module1 standalone
Log.i("module1Standalone", "module1Standalone init is called");
// Replace instances inside RouterServices
RouterServices.sAppRouter = new AppRouterMock();
RouterServices.sModule2Router = new Module2RouterMock();
}
}
3.2 跨模块启动 Activity 和 Fragment
首先,在 router 模块的 Module1Router 内声明启动 Module1Activity 的方法:
public interface Module1Router {
...
// 启动 Module1Activity
void startModule1Activity(Context context);
}
然后在 module1 模块里 Module1Router 对应的实现类 Module1RouterImpl 中实现刚刚定义的方法:
@ServiceProvider
public class Module1RouterImpl implements Module1Router {
...
@Override
public void startModule1Activity(Context context) {
Intent intent = new Intent(context, Module1Activity.class);
context.startActivity(intent);
}
}
这样, module2 中就可以通过下面的方式启动 module1 中的 Module1Activity 了。
RouterServices.sModule1Router.startModule1Activity(context);
跨模块获取 Fragment 实例也是类似的方法,我们在 Module1Router 里继续声明方法:
public interface Module1Router {
...
// 启动 Module1Activity
void startModule1Activity(Context context);
// 获取 Module1Fragment
Fragment obtainModule1Fragment();
}
差不多的写法,我们只要在 Module1RouterImpl 里接着实现方法即可:
@ServiceProvider
public class Module1RouterImpl implements Module1Router {
@Override
public void startModule1Activity(Context context) {
Intent intent = new Intent(context, Module1Activity.class);
context.startActivity(intent);
}
@Override
public Fragment obtainModule1Fragment() {
Fragment fragment = new Module1Fragment();
Bundle bundle = new Bundle();
bundle.putString("param1", "value1");
bundle.putString("param2", "value2");
fragment.setArguments(bundle);
return fragment;
}
}