强烈建议先看上面博客,写的十分优秀,为我理解模块化,和真正实践起到了很大的帮助。我的这篇文章仅是我自己的学习笔记,一个知识点如果只是看过那很快就会从大脑中遗忘,我更喜欢自己去写demo,做笔记,这样不仅对我理解这个知识点有很大的帮助,同时在脑中的记忆也更深刻,即便一段时间我有所遗忘,只要翻开自己的笔记也很快可以回忆起。
一、模块化分层设计
模块化的好处:
- 多团队并行开发测试;
- 模块间解耦、重用;
- 可单独编译打包某一模块,提升开发效率。
模块化和组件化之间的区别定义:
组件:指的是单一的功能组件,如地图组件(MapSDK)、支付组件(AnjukePay)、路由组件(Router)等等;
模块:指的是独立的业务模块,如新房模块(NewHouseModule)、二手房模块(SecondHouseModule)、即时通讯模块(InstantMessagingModule)等等;模块相对于组件来说粒度更大。
具体的设计方案:
整个项目分为三层,从下至上分别是:
- Basic Component Layer: 基础组件层,顾名思义就是一些基础组件,包含了各种开源库以及和业务无关的各种自研工具库;
- Business Component Layer: 业务组件层,这一层的所有组件都是业务相关的,例如上图中的支付组件 AnjukePay、数据模拟组件 DataSimulator 等等;
- Business Module Layer: 业务 Module 层,在 Android Studio 中每块业务对应一个单独的 Module。例如安居客用户 App 我们就可以拆分成新房 Module、二手房 Module、IM Module 等等,每个单独的 Business Module 都必须准遵守我们自己的 MVP 架构。
上面都是Android模块化探索与实践总结出的一些观点,这里还是强烈建议直接浏览这篇博客。
二、模块化基本实现
下面是我自己在实现模块化时的整个流程,包括具体的代码编译过程。
(1)各个模块的开发
就如同上文定义的一般,每个模块是可以独自开发编译,打包调试的。同时也可以作为一个module导入到最终的项目里使用。所以每个模块不仅是“application”还可以是“library”,可以在gradle.properties文件中设置一个开关变量用于判断当前模块的状态。
isBuildModule=false
然后在项目的build.gradle文件中增加判断
if (isBuildModule.toBoolean()){
apply plugin: 'com.android.library'
}else{
apply plugin: 'com.android.application'
}
还需要注意以下几点:
① 作为一个application进行开发是需要applicationId的,作为一个library是不需要的,所以还需要加上判断:
if (!isBuildModule.toBoolean()){
applicationId "com.example.francis.amoudle"
}
② 如果作为一个library开发是不需要应用入口的,即要把AndroidManifest文件中的LAUNCHER关闭,所以需要两套的AndroidManifest文件,可以使用sourceSets进行设置。(build.gradle中的很多配置还是需要学习一下的)
sourceSets {
main {
if (isBuildModule.toBoolean()) {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
java {
//release 时 debug 目录下文件不需要合并到主工程
exclude 'debug/**'
}
}
}
这里完整的build.gradle文件如下:
if (isBuildModule.toBoolean()){
apply plugin: 'com.android.library'
}else{
apply plugin: 'com.android.application'
}
android {
compileSdkVersion 26
defaultConfig {
if (!isBuildModule.toBoolean()){
applicationId "com.example.francis.amoudle"
}
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
resourcePrefix "a_"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if (isBuildModule.toBoolean()) {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
java {
//release 时 debug 目录下文件不需要合并到主工程
exclude 'debug/**'
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
这里还是比较简陋的写法,比如说android的一些基本配置,sdk的版本,依赖第三方库的版本都可以统一起来,一方面保证不同的module不会出现版本冲突的问题,另一方面也方便整理。
(2)导入到壳APP中
直接new -> import module -> 选择需要导入的module,修改导入module的名字就可以了,还是是比较简单的。
导入成功后,setting.gradle文件中会显示所有的application和library
除了导入module还需要将添加依赖,主框架才可以使用module中的代码,右键项目打开Open Module Settings,
添加依赖
这样app就可以调用它所依赖的module了
(3)模块间跳转通信
对业务进行模块化拆分后,为了使各业务模块间解耦,因此各个 Bussiness Module 都是独立的模块,它们之间是没有依赖关系。那么各个模块间的跳转通讯如何实现呢?
比如业务上要求从新房的列表页跳转到二手房的列表页,那么由于是 NewHouseModule 和 SecondHouseModule 之间并不相互依赖,我们通过想如下这种显式跳转的方式来实现 Activity 跳转显然是不可能的实现的。
1
2
|
Intent intent =
new Intent(NewHouseListActivity.
this, SecondHouseListActivity.class);
startActivity(intent);
|
可能会想到用隐式跳转,通过 Intent 匹配规则来实现:
1
2
|
Intent intent =
new Intent(Intent.ACTION_VIEW,
"<scheme>://<host>:<port>/<path>");
startActivity(intent);
|
但是这种代码写起来比较繁琐,且容易出错,出错也不太容易定位问题。因此一个简单易用、解放开发的路由框架是必须的了。
这里代码都是从Android模块化探索与实践获取的,感兴趣的朋友可以直接查看他项目的github地址
定义了一些基本注解:
用于定义跳转 URI 的注解 FullUri:
1
2
3
4
5
|
public
FullUri {
String value();
}
|
用于定义跳转传参的 UriParam( UriParam 注解的参数用于拼接到 URI 后面):
1
2
3
4
5
|
public
UriParam {
String value();
}
|
用于定义跳转传参的 IntentExtrasParam( IntentExtrasParam 注解的参数最终通过 Intent 来传递):
1
2
3
4
5
|
public
IntentExtrasParam {
String value();
}
|
主要方法:
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
FullUri fullUri = method.getAnnotation(FullUri.class);//通过反射获取注解信息
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append(fullUri.value());
//获取注解参数
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
HashMap<String, Object> serializedParams = new HashMap<>();
//拼接跳转 URI
int position = 0;
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] annotations = parameterAnnotations[i];
if (annotations == null || annotations.length == 0)
break;
Annotation annotation = annotations[0];
if (annotation instanceof UriParam) {
urlBuilder.append(position == 0 ? "?" : "&");
position++;
UriParam uriParam = (UriParam) annotation;
urlBuilder.append(uriParam.value()).append("=").append(args[i]);
} else if (annotation instanceof IntentExtrasParam) {
IntentExtrasParam intentExtrasParam = (IntentExtrasParam) annotation;
serializedParams.put(intentExtrasParam.value(), args[i]);
}
}
//执行Activity跳转操作
performJump(urlBuilder.toString(), serializedParams);
return null;
}
});
}
/**
* 执行Activity跳转操作
*
* @param routerUri Intent跳转URI
*/
private void performJump(String routerUri, HashMap<String, Object> serializedParams) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(routerUri));//使用隐式跳转
Bundle bundle = new Bundle();
for (Map.Entry<String, Object> entry : serializedParams.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Integer) {
bundle.putInt(key, Integer.parseInt(value.toString()));
} else if (value instanceof Long) {
bundle.putLong(key, Long.parseLong(value.toString()));
} else if (value instanceof Double) {
bundle.putDouble(key, Double.parseDouble(value.toString()));
} else if (value instanceof Short) {
bundle.putShort(key, Short.parseShort(value.toString()));
} else if (value instanceof Float) {
bundle.putFloat(key, Float.parseFloat(value.toString()));
} else if (value instanceof String) {
bundle.putString(key, (String) value);
} else if (value instanceof CharSequence) {
bundle.putCharSequence(key, (CharSequence) value);
} else if (value.getClass().isArray()) {
if (int[].class.isInstance(value)) {
bundle.putIntArray(key, (int[]) value);
} else if (long[].class.isInstance(value)) {
bundle.putLongArray(key, (long[]) value);
} else if (double[].class.isInstance(value)) {
bundle.putDoubleArray(key, (double[]) value);
} else if (short[].class.isInstance(value)) {
bundle.putShortArray(key, (short[]) value);
} else if (float[].class.isInstance(value)) {
bundle.putFloatArray(key, (float[]) value);
} else if (String[].class.isInstance(value)) {
bundle.putStringArray(key, (String[]) value);
} else if (CharSequence[].class.isInstance(value)) {
bundle.putCharSequenceArray(key, (CharSequence[]) value);
} else if (Parcelable[].class.isInstance(value)) {
bundle.putParcelableArray(key, (Parcelable[]) value);
}
} else if (value instanceof ArrayList && !((ArrayList) value).isEmpty()) {
ArrayList list = (ArrayList) value;
if (list.get(0) instanceof Integer) {
bundle.putIntegerArrayList(key, (ArrayList<Integer>) value);
} else if (list.get(0) instanceof String) {
bundle.putStringArrayList(key, (ArrayList<String>) value);
} else if (list.get(0) instanceof CharSequence) {
bundle.putCharSequenceArrayList(key, (ArrayList<CharSequence>) value);
} else if (list.get(0) instanceof Parcelable) {
bundle.putParcelableArrayList(key, (ArrayList<? extends Parcelable>) value);
}
} else if (value instanceof Parcelable) {
bundle.putParcelable(key, (Parcelable) value);
} else if (value instanceof Serializable) {
bundle.putSerializable(key, (Serializable) value);
} else {
throw new IllegalArgumentException("不支持的参数类型!");
}
}
intent.putExtras(bundle);
PackageManager packageManager = context.getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
if (!activities.isEmpty()) {
context.startActivity(intent);
}
}
在使用 Router 来跳转的时候,首先需要定义一个 Interface(类似于 Retrofit 的使用方式):
1
2
3
4
5
6
7
|
public
interface RouterService {
"router://com.baronzhang.android.router.FourthActivity")
(
void startUserActivity(@UriParam("cityName")
String cityName, @
IntentExtrasParam
("user") User user);
}
|
接下来我们就可以通过如下方式实现 Activity 的跳转传参了:
1
2
3
4
|
RouterService routerService =
new Router(
this).create(RouterService.class);
User user =
new User(
"张三",
17,
165,
88);
routerService.startUserActivity(
"上海", user);
|
主要代码都在上面了,这里我们可以来分析下:
前提:代码最终使用的是Intent的Uri隐式跳转,所以需要为组件声明跳转链接。
定义注解,是为了给每一个跳转方法贴上标签包括跳转Activity的链接和需要传递的一些参数信息。然后在代理方法中通过反射获取这些信息,包括路径,传递的值等等,
FullUri fullUri = method.getAnnotation(FullUri.class);
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append(fullUri.value());
最后拼接成一条完整的Intent请求。
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(routerUri));
完整代码可以参考点这里。