Android工程 模块/组件化
目录
一、为什么要模块/组件化
设想一下,如果项目工程不做模块/组件化设计,经过不断地迭代,增加的业务、功能越来越多,代码全部混在一起,耦合性越来越高,越来越难维护,打包越来越慢。
牵一发而动全身,改一句代码1秒钟,等待构建验证半小时,这对多人开发的大型项目简直就是灾难。
二、什么是模块/组件化
再来说说模块化,就是将整个APP按照业务或功能拆分成不同的模块,比如登录模块,搜索模块,支付模块等等。但是在工程不断扩大的情况下,模块之间的耦合还是会越来越强,这时想对工程进一步解耦,组件化思想也就出现了。
可以说组件化是在模块化上的演进,将模块作为一个个的组件,并且组件可以独立使用/运行,这样在开发时,还需要等待半小时构建整包吗?是不是只要把当前修改的模块构建好就可以验证了。
其实模块化、组件化都是一种架构设计思想,其目的都是为了将工程大化小,实现解耦与重用,提高可维护性。
三、如何实现组件化
Talk is cheap, show me the code!
3.1 结构图
组件 | 说明 |
---|---|
App主项目 | 应用壳子,用于构建最终的APK |
movie_module | 业务模块,电影业务 |
music_module | 业务模块,音乐业务 |
base_module | 基础模块,内部可封装一些通用基础功能 |
route_module | 路由模块,解决各模块间的通信问题 |
3.1 敲代码
首先创建好各个module
3.1.1 模块怎么做到整体构建还是自有构建(library和application)
1.base和router模块在build.gradle中将其指定为library(因为这两个基础模块不需要当做一个application来运行使用)
apply plugin: 'com.android.library'
2.看到这里应该已经想到,肯定movie和music的build.gradle中要动态指定application或library。怎么做呢,首先是不是要有个开关配置,在gradle.properties中加个配置,true代表以模块形式运行,false代表以Application形式运行
# module switch
isModule=true
3.开关做好以后,在movie和music的build.gradle中根据开关做动态配置。
if (isModule.toBoolean()) {
// 当做module时作为library使用
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
defaultConfig {
if (!isModule.toBoolean()) {
applicationId "com.example.movie_module"
}
minSdkVersion 24
targetSdkVersion 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
同時区分不同模式打包时manifest文件
sourceSets {
main {
if (isModule.toBoolean()) { // 作为module打包
manifest.srcFile '/src/main/module-file/AndroidManifest.xml' // manifest 文件路径
} else {
manifest.srcFile '/src/main/AndroidManifest.xml' // manifest 文件路径
}
}
}
3.1.2 处理依赖关系
1.route_module依赖base_module
// api依赖base_module,传递依赖,使得依赖route_module的组件也能使用base的能力
api project(':base_module')
2.其他模块都依赖route_module
implementation project(':router_module')
3.主工程判断业务以module形式构建时需对其依赖(要把各个module都打入App中)
// 组件化方式时,依赖其他模块
if (isModule.toBoolean()) {
implementation project(':movie_module')
implementation project(':music_module')
}
3.1.3 各模块具体实现
1.base_module:
简单封装了一个LogUtil
public class LogUtil {
private static final String TAG = "LogUtil";
public static void i(String method, String msg) {
Log.i(TAG, "method " + method + " msg");
}
}
2.movie/music_module:
也是简单写了两个界面,在音乐界面增加了一个跳转到电影界面的点击事件(Router下面会说到,也是关键点,music和movie两个组件相互独立,如何跳转),这里贴一下Activity代码
public class MusicActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_music);
}
public void onClickToMovie(View view) {
// 路由跳转到Movie模块界面
Router.getInstance().startRouterActivity(this, RouterConstant.MODULE_MOVIE_ACTIVITY_MOVIE);
}
}
3.route_module:
这里主要是一个Router类和定义了Path常量类
public class Router {
private final HashMap<String, Class> routers = new HashMap<>();
private static class LazyHolder {
static final Router INSTANCE = new Router();
}
public static Router getInstance() {
return LazyHolder.INSTANCE;
}
public void register(String path, Class cls) {
routers.put(path, cls);
}
public void startRouterActivity(Context context, String targetPath) {
Class aClass = routers.get(targetPath);
if (aClass == null) {
LogUtil.i("startRouterActivity", "class null");
return;
}
Intent intent = new Intent(context, aClass);
context.startActivity(intent);
}
}
Router是个单例类,提供了一个注册和启动Activity的方法;通过注册将path(字符串)和对应的类保存到路由表中,这个表就记录了对应的path和类的关系。
4.App主工程:
主要是进行注册路由表,在Application的onCreate()中进行注册;同时还有一个MainActivity作为主入口。
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 注册路由表
Router.getInstance().register(RouterConstant.MODULE_MOVIE_ACTIVITY_MOVIE, MovieActivity.class);
Router.getInstance().register(RouterConstant.MODULE_MUSIC_ACTIVITY_MUSIC, MusicActivity.class);
}
}
3.3 ShowCase
3.3.1 isModule为false时,单独运行movie/music
可以看见单独运行movie_module,桌面会有一个movie_module的app入口,这样就可以做到movie模块单独打包调试了,而不需要整个Demo应用来调试。
3.3.2 isModule为true,整个Demo运行
可以看出是整个demo的应用入口,打开后进入MainActivity,可以跳转到movie或music,同时music也可以跳转到movie
四、总结
了解模块/组件化的设计思想,以及具体在代码中如何配置,通过单独/整体打包,运行单个组件进行调试。同时这里也通过一个路由表注册保存path与类的信息,这样就解决了组件间通讯的问题。
关于ARouter或WMRouter的实现,肯定不可能这么简单的啦,他们有使用到注解,然后用到APT(Annotation Processing Tool)来扫描注解自动生成代码等,美团WMRouter还提供了类似ServiceLoader功能,多个模块之间通过接口调用代码,也是更好解决组件间通信。建议大家也多去研究。