1.背景介绍
在移动端项目功能不断完善和丰富的过程中我们一直在寻找一种可以高效开发且复用率高的开发模式,特别是多应用同步开发、管理。
在开发过程中你是否遇到需要发布影子工程?新建项目是否需要耗费大量时间将原有基类、工具类、第三方通用类库逐个copy进新项目中?在项目不断迭代功能越来越丰富的同时是否发现项目编译时间不断增加?组件化开发就可以很好的解决上述这些问题,它实际是将一个完整的项目划分为若干个模块,过程类似搭积木,一个一个拼接,你可以单独使用其中任意一个,也可以用多个拼接出各种形状,达到高效开发,最大复用。
在了解组件化开发之前我们需要先了解组件和模块这两个概念
- 组件:指的是单一的功能组件,如地图组件(MapSDK)、支付组件(AnjukePay)、路由组件(Router)等等;
- 模块:指的是独立的业务模块,如基类模块(common-module)、商城模块(mall-module)、会员模块(member-module)等等;模块相对于组件来说粒度更大。
2.系统架构图
系统架构图.png项目结构.png
项目由主项目app,商城模块mall-module,会员模块member-module,公共模块common-module各个基类(BaseActivity/BaseApplication/BaseAdapter等)、工具类、网络、图片等一些公共常用的第三方sdk:
- app:作为项目的主程序入口,生产环境时可以引入所有项目模块如本项目的common-module、mall-modulem、member-module;开发调试不涉及到跨模块调用业务时可以单独引入common-module依赖运行。
- mall-module:会员模块,编写完成整的商城相关模块,生产环境作为library引入app主项目,开发阶段可以application单独运行,同时亦可作为单独商城发布项目。
- member-module:会员模块,编写完成整的商城相关模块,生产环境作为library引入app主项目,开发阶段可以application单独运行,同时亦可作为单独商城发布项目。
- common-module:作为整个项目的基石,所有基类增加修改,均可以在引入项目中生效,达到最大复用率,提高开发效率。
3.实践
-
mall-module、member-module、common-module作为library被引入app中运行效果图:
项目运行效果图.png -
mall-module作为应用单独运行效果图:
mall-module作为应用单独运行效果图.png
- 主项目app创建
项目build.gradle配置文件:
apply plugin: 'com.alibaba.arouter'
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath "com.alibaba:arouter-register:1.0.2"
}
}
allprojects {
repositories {
google()
jcenter { url "http://jcenter.bintray.com/" }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
项目gradle.properties全局配置文件:
#IS_MAIN_APP true主项目为应用 mall-module为library false mall-module为应用可单独启动
IS_MAIN_APP=true
#AS 编译配置
COMPILE_SDK_VERSION=28
BUILDTOOLS_VERSION=28.0.3
MIN_SDK_VERSION=15
TARGET_SDK_VERSION=25
#版本号
APP_VERSION=1
APP_VERSION_CODE=1.0.1
- app build.gradle配置文件:
apply plugin 根据IS_MAIN_APP值设置为application或library
if (IS_MAIN_APP.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)
buildToolsVersion BUILDTOOLS_VERSION
defaultConfig {
if (IS_MAIN_APP.toBoolean()) {
applicationId "com.fenlibao.arouter"
}
minSdkVersion Integer.parseInt(MIN_SDK_VERSION)
targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)
versionCode Integer.parseInt(APP_VERSION)
versionName APP_VERSION_CODE
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if (IS_MAIN_APP.toBoolean()) {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
compile project(':common-module')//公共方法 基类 放在该项目中
if (IS_MAIN_APP.toBoolean()) {
compile project(':mall-module')//商城模块
compile project(':member-module')//会员模块
}
}
- 创建mall-module
mall-module build.gradle配置文件
apply plugin 根据IS_MALL_APP值设置为application或library
if (IS_MALL_APP.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)
buildToolsVersion BUILDTOOLS_VERSION
defaultConfig {
if (IS_MALL_APP.toBoolean()) {
applicationId "com.fenlibao.mall"
}
minSdkVersion Integer.parseInt(MIN_SDK_VERSION)
targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)
versionCode Integer.parseInt(APP_VERSION)
versionName APP_VERSION_CODE
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
resourcePrefix "mall_"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if (IS_MALL_APP.toBoolean()) {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
}
}
}
dependencies {
compile project(':common-module')
compile 'com.android.support.constraint:constraint-layout:1.1.3'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
}
- common-module创建
common-module build.gradle配置文件
apply plugin 固定为'com.android.library'作为库被其他项目引用
apply plugin: 'com.android.library'
android {
compileSdkVersion Integer.parseInt(COMPILE_SDK_VERSION)
buildToolsVersion BUILDTOOLS_VERSION
defaultConfig {
minSdkVersion Integer.parseInt(MIN_SDK_VERSION)
targetSdkVersion Integer.parseInt(TARGET_SDK_VERSION)
versionCode Integer.parseInt(APP_VERSION)
versionName APP_VERSION_CODE
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [ AROUTER_MODULE_NAME : project.getName() ]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:28.0.0'
compile 'com.android.support:design:28.0.0'
compile 'com.android.support.constraint:constraint-layout:1.1.3'
testCompile 'junit:junit:4.12'
androidTestCompile 'com.android.support.test:runner:1.0.2'
androidTestCompile('com.android.support.test.espresso:espresso-core:3.0.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
//该项目中suppourt包为25.2.0版本比较低 顾做排除处理
compile('com.alibaba:arouter-api:1.4.1') {
exclude group: 'com.android.support'
}
//Rxjava2
compile 'io.reactivex.rxjava2:rxjava:2.2.6'
compile 'io.reactivex.rxjava2:rxandroid:2.1.0'
//Retrofit2
compile 'com.squareup.retrofit2:retrofit:2.4.0'
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
//Okhttp-interceptor
compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'
}
4.注意事项
-
多个module间Activity如何跳转?
多个module间activity跳转.png
由于模块与模块之间是相互独立存在,因而不能使用项目内方式实现Activity跳转,下面介绍两种Activity跳转方式,日常开发中推荐使用方式二,方式二不管如何修改类名包名都能保持与字符串常量映射关系,维护和使用更方便,更多ARouter高级用法请查阅相关资料
- 通过反射方式
try {
Class clazz = Class.forName("com.fenlibao.member.MainActivity");
Intent intent = new Intent(_activity, clazz);
startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
- 使用ARouter注解方式,实现映射关系自动注册
/**
* ARouter在MyApplication中初始化
*/
public class MyApplication extends BaseApplication {
@Override
public void onCreate() {
super.onCreate();
init();
}
private void init() {
if (isDebug()) {
ARouter.openLog();
ARouter.openDebug();
}
ARouter.init(this);
}
public boolean isDebug() {
return getApplicationInfo() != null && (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
@Override
public void onTerminate() {
super.onTerminate();
ARouter.getInstance().destroy();
}
}
/**
* 在常量类中定义静态字符串常量
*/
public static final String MEMBER_1_URL_MAIN = "/member/MainActivity";
/**
* 在对应module Activity中使用@Route标签进行绑定
*/
@Route(path = RoutePath.MALL_1_URL_MAIN)
public class MainActivity extends BaseActivity {
}
/**
* 在需要跳转至该页面build方法中传入对应Activity字符串常量,即可实现跳转
*/
ARouter.getInstance().build(RoutePath.MEMBER_1_URL_MAIN).navigation();
- 在module中如何获取Application上下文对象?
- 在common-module中定义BaseApplication继承Application。
**
* 项目Application基类主项Application需集成此类
*/
public class BaseApplication extends Application {
/**
* 系统上下文
*/
private static BaseApplication mAppContext;
@Override
public void onCreate() {
super.onCreate();
mAppContext = this;
}
/**
* 获取系统上下文单例
*/
public static BaseApplication getInstance() {
return mAppContext;
}
}
- 在app项目中MyApplication(上部分代码示例中可以查看)继承BaseApplication,同时设置到AndroidManifest.xml application标签下name属性中。
- 在app及module中都可通过BaseApplication.getInstance();方法即可获取应用上下文对象,如下:
BaseApplication application = BaseApplication.getInstance();
-
如何将module作为应用单独运行?
在项目gradle.properties全局配置文件中找到IS_MAIN_APP配置变量,设置为ture时为主项目启动方式,设置为false,脱离其他mall-module作为应用单独启动,设置完成之后重新编译项目即可,开发测试阶段推荐使用该方式,可缩短项目编译时间,项目越大,缩短时间越明显。
##IS_MAIN_APP true主项目为应用,mall-module为library;false mall-module为应用可单独启动
IS_MAIN_APP=true
-
应用主入口只能有一个,如何切换应用主入口的?
根据IS_MAIN_APP设置值加载不同目录下的AndroidManifest.xml文件,实现根据参数加载配置文件,下面是app mall-module的build.gradle配置。
#app build.gradle
android {
sourceSets {
main {
if (IS_MAIN_APP.toBoolean()) {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
}
}
}
#mall-module build.gradle
android {
sourceSets {
main {
if (!IS_MAIN_APP.toBoolean()) {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
}
}
}
-
如何避免资源文件冲突覆盖?
在组件化开发过程中经常会出现资源文件冲突覆盖问题,主项目会覆盖module项目资源文件。
- 在module项目中build.gradle配置文件中设置
resourcePrefix "mall_"
通过给模块设置不同的前缀,避免资源文件重名覆盖问题。
2.良好的命名(资源文件/类文件)习惯,需以module前缀开头,例如mall-module布局资源文件,采用mall_activity_home,方式避免资源文件重名覆盖问题,如果资源文件不按resourcePrefix "mall_" 定义前缀命名会有错误提示,例如以如下方式命名布局资源文件 activity_mall_home.xm:
错误提示.png
5.总结
以上就是组件化开发的详细步骤和相关注意事项,如果你面前是一个庞大的工程,建议你使用该方案,以最小的代价尽快开始实施组件化。如果你现在负责的是一个开发初期的项目,代码量还不大,那么也建议尽快进行组件化的规划,不要给未来的自己增加徒劳的工作量。
项目示例Github地址:https://github.com/guixia/AndroidModule。
网友评论