一.浅谈模块
其基本理念就是,把常用的功能、控件、基础类、第三方库、权限等公共部分抽离封装,把业务拆分成N个模块进行独立(module)的管理,而所有的业务组件都依赖于封装的基础库,业务组件之间不做依赖,这样的目的是为了让每个业务模块能单独运行。而在APP层对整个项目的模块进行组装,拼凑成一个完整的APP。借助路由(Arouter)来对各个业务组件之间的跳转,通过消息(eventbus)来做各个业务模块之间的通信。 模块化的好处:
- 解耦 只要封装做得好,实际开发中会省去大量的重复代码的coding。
- 结构清晰、层次明显,对后面的维护也是极其容易。
- 每个业务模块可独立运行,单独提测,节省开发时间。
二.基础搭建
先来一张整个项目构思图
根据项目构思图搭建的项目结构图
下面逐一介绍每个模块的功:
项目配置
在project工程目录下的build.gradle文件里,定义isModule 字段作为module和library切换的开关。
ext {
isModule = false //false:作为Lib组件存在, true:作为application存在
signingConfig = [
signFile : '../app/moduledemo.jks',
storePassword: '123456',
keyAlias : 'moduledemo',
keyPassword : '123456'
]
android = [
applicationId : "com.lwx.moduledemo",
moduleAppId_news : "com.lwx.module_news",
moduleAppId_login: "com.lwx.module_login",
compileSdkVersion: 26,
buildToolsVersion: '26.0.3',
minSdkVersion : 15,
targetSdkVersion : 26,
versionName : '1.0.0',
versionCode : 100
]
deps = [
supportv4 : 'com.android.support:support-v4:26.1.0',
appcompatv7 : 'com.android.support:appcompat-v7:26.1.0',
constraintlayout : 'com.android.support.constraint:constraint-layout:1.1.3',
design : 'com.android.support:design:26.1.0',
//阿里Arouter
arouterapi : 'com.alibaba:arouter-api:1.5.0',
aroutercompiler : 'com.alibaba:arouter-compiler:1.2.2',
// ------------- Test dependencies -------------
junit : 'junit:junit:4.12',
testrunner : 'com.android.support.test:runner:1.0.2',
testespresso : 'com.android.support.test.espresso:espresso-core:3.0.2',
]
}
引入阿里的Arouter(文档:https://github.com/alibaba/ARouter)
apply plugin: 'com.alibaba.arouter'
dependencies {
...
classpath "com.alibaba:arouter-register:1.0.2"
...
}
app壳工程:
app壳没有任何功能主要就是集成每个业务组件,最终打包成一个完整的APK app壳的gradle做如下配置,根据配置文件中的isModule字段来依赖不同的业务组件
defaultConfig {
...
//Arouter路由配置
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]
}
}
}
dependencies {
...
implementation project(':common_lib')
if (!rootProject.ext.isModule) {
implementation project(':module_news')
}
if (!rootProject.ext.isModule) {
implementation project(':module_login')
}
annotationProcessor rootProject.ext.deps.aroutercompiler
...
}
common_lib模块:
功能组件主要负责封装公共部分,如第三方库加载、网络请求、数据存储、自定义控件、各种工具类等。 为了防止重复依赖问题,所有的第三方库都放在该模块加载,业务模块不在做任何的第三方库依赖,只做common_base库的依赖即可。 common模块无论在什么情况下都是以library的形式存在,所有的业务组件都必须依赖于common ,
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation rootProject.ext.deps.junit
androidTestImplementation rootProject.ext.deps.testrunner
androidTestImplementation rootProject.ext.deps.testespresso
api rootProject.ext.deps.appcompatv7
api rootProject.ext.deps.design
api rootProject.ext.deps.constraintlayout
api (rootProject.ext.deps.arouterapi){
exclude module: 'support-v4'//去掉重复依赖,不去掉可能会报错
}
annotationProcessor rootProject.ext.deps.aroutercompiler
}
业务组件
在集成模式下它以library的形式存在。在组件开发模式下它以application的形式存在,可以单独独立运行。 业务组件完整的gradle如下:
//切换module和library
if (rootProject.ext.isModule) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
....
defaultConfig {
if (rootProject.ext.isModule) {
//组件模式下设置applicationId
applicationId rootProject.ext.android.moduleAppId_login
}
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
if (!rootProject.ext.isModule) {
//集成模式下Arouter的配置,用于组件间通信的实现
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
//强制前缀,解决资源冲突
resourcePrefix "login_"
}
}
......
sourceSets {
main {
//控制两种模式下的资源和代码配置情况
if (rootProject.ext.isModule) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
......
dependencies {
.....
implementation project(':common_lib')
if (!rootProject.ext.isModule) {
//集成模式下需要编译器生成路由通信的代码
annotationProcessor rootProject.ext.deps.aroutercompiler
}
.....
}
三.实现Arouter跳转
经过上述配置之后就可以实现Arouter跳转了,我们首先在common_lib库定义一个常量类,这个类就是定义跳转路径的作用:
public class RouterPath {
public static final String ROUTER_LOGIN= "/module_login/LoginActivity";
public static final String ROUTER_NEWS= "/module_news/NewsActivity";
}
然后分别在module_login和module_news业务组件中的Activity加上Route注解定义路径
@Route(path = RouterPath.ROUTER_LOGIN)
public class LoginActivity extends AppCompatActivity {}
最后在App工程下的mainActivity中加上点击跳转事件
mBtnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//传值
ARouter.getInstance().build(RouterPath.ROUTER_LOGIN)
.withString("phone","18212341234").withString("pwd","123456").navigation();
}
});
mBtnNews.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//不传值
ARouter.getInstance().build(RouterPath.ROUTER_NEWS).navigation();
}
});
四.遇到的问题
1.AndroidManifest的管理
我们知道APP在打包的时候最后会把所有的AndroidManifest进行合并,所以每个业务组件的Activity只需要在各自模块的AndroidManifest中注册即可。如果业务组件需要独立运行,则需要单独配置一份AndroidManifest,在gradle的sourceSets根据不同的模式加载不同的AndroidManifest文件。
gradle配置
...
android {
...
sourceSets {
main {
//控制两种模式下的资源和代码配置情况
if (rootProject.ext.isModule) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
...
注意:在配置Gradle的时候 manifest.srcFile… manifest 是小写的
其中集成模式加载的Manifest中不能设置Application和程序入口:
//集成模式下Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lwx.module_login">
<application>
<activity android:name=".LoginActivity" />
</application>
</manifest>
//组件模式下Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lwx.module_login">
<application
android:name=".BaseApplication"
android:allowBackup="true"
android:label="@string/app_name"
android:theme="@style/AdmTheme">
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
需要注意的是如果在组件开发模式下,组件的Applicaion必须继承自BaseApplicaion
2.不同组件之间通信
可以利用第三方 如EventBus对消息进行管理。在common_base组件中的Base类做了对消息的简单封装,子类只需要重写regEvent()返回true即可对事件的注册,重写onEventBus(Object)即可对事件的接收。
public abstract class BaseActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (regEvent()) {
EventBus.getDefault().register(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (regEvent()) {
EventBus.getDefault().unregister(this);
}
}
/**
* 子类接收事件 重写该方法
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventBus(Object event) {
}
/**
* 需要接收事件 重写该方法 并返回true
*/
protected boolean regEvent() {
return false;
}
3.butterknife的问题
在library中使用butterknife会存在找不到的问题。 推荐使用8.4.0版本,用R2代替R,onClick中使用if else不要使用switch case即可解决问题 。
public class HomeFragment extends BaseMvpFragment<HomePresenter> implements IHomeView, OnRcyItemClickListener {
@BindView(R2.id.banner)
Banner banner;
@BindView(R2.id.recycle_view)
RecyclerView recyclerView;
...
@OnClick({R2.id.tv_title, R2.id.btn_open})
public void onClick(View v) {
if (v.getId() == R.id.tv_title) {
//do something
} else if (v.getId() == R.id.btn_open) {
//do something
}
}
}
4.资源文件冲突问题
目前没有比较好的约束方式,只能通过设置资源的前缀来防止资源文件冲突,然后在提交代码的时候对代码进行检查是否规范来控制
//强制前缀,解决资源冲突
resourcePrefix "login_"
最后附上demo