起源
在采用组件化之前,我负责的一个电商项目经历了1.0单模块版本、2.0多模块版本,3.0决定采用组件化来重构项目,并将项目开源,可供同样在做电商的android开发者参考、在研究android架构的同行者共同学习进步。在做之前,我们考虑三个问题,what、why和how,什么是组件化?为什么要进行组件化?如何进行组件化?
该电商项目源码请点击
什么是组件化
组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程。在Android工程表现上就是把app按照其业务的不同,划分为不同的Module,每个Module可独立作为一个app运行,也可作为整个app的子模块。
为什么要进行组件化
为了解耦:把复杂系统拆分成多个组件,分离组件边界和责任,便于独立升级和维护
各个组件专注自身功能的实现,模块中代码高度聚合,只负责一项任务,也就是常说的单一职责原则;
各业务研发可以互不干扰、提高协作效率;
业务组件可进行拔插,灵活多变;
业务组件之间将不再直接引用和依赖,各个业务模块组件更加独立,降低耦合;
加快编译速度,提高开发效率
android如何进行组件化
在gradle.properties文件中增加常量:isModule=false
在各个组件模块的gradle中增加是否是组件的动态配置
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
如果需要独立模块运行,则将gradle.properties中的isModule置为true,否则置为false,修改后rebuild一下;当然这样只是简单实现了组件的自动拆分和重组,更详细的实现请继续往下看
电商项目组件化之路
根据电商项目的业务特点,我们设计了如下架构图
从下往上来看,依次是通用组件、业务组件、业务模块、APP壳四大层。
通用组件,又称业务无关组件,根据来源分为开源库和基础组件库,开源库是我们在项目中经常会用到的第三方库,比如okhttp、rxJava等,基础组件库是我们自己定义的和业务无关的组件,主要包括BaseMvp、工具类、自定义的Widget、http等
common_http: http通信组件
业务组件,和业务相关的组件
lib_push: 推送组件,负责推送服务
lib_share: 第三方分享
lib_im: 即时聊天的服务
lib_webview: 独立进程webview,用于和h5之间进行交互
业务模块,根据业务的联系紧密程度来拆分
module_main: 首页模块,包括启动页、欢迎页、首页等;
module_goods: 商品模块,包括商品分类、商品详情、商品搜索等;
module_user: 用户模块,包括个人中心、登录等;
module_order:订单模块,包括订单确认、订单列表等;
module_msg: 消息模块,包括客服消息等;
APP壳,是app的入口模块,包括manifest配置、加载各个模块、打包配置等,基于应用安全考虑,通过调用C层的加密方法将业务模块加密
组件化间通信
同级组件间,因为没有相互依赖关系,所以不能相互调用,那么我们就遇到了通信的问题,如何来解决组件间通信,目前开源的有多种方案可以选择,用的最普遍的是阿里的ARouter,这里我们选择ARouter来解决我们的通信问题。
为了使我们的设计具有更强的灵活性和易用性,我们新增 lib_router 模块来对 arouter 做一层包装。
使用举例:在router模块中定义UserIntent.startLoginActivity,在各个组件中可直接调用
public class UserIntent {
public static void startLoginActivity() {
ModuleRouter.newInstance(USER_PROFILE_LOGIN_ACTIVITY).navigation();
}
}
//任意组件中使用
UserIntent.startLoginActivity();
第三方可选择方案
各个方案比较请点击
阿里的ARouter
CC
ActivityRouter
组件初始化
在common层定义接口 IApplication
public interface IApplication {
void onCreate(Application application);
}
在各个组件中实现该接口
public class UserApplication implements IApplication {
public static Application sApp = null;
@Override
public void onCreate(Application application) {
sApp = application;
// to do init
}
}
在common中新增类ModuleConfig,用来存储各个组件的application路径
public class ModuleConfig {
public static final String[] MODULE_LIST=
{
"com.jackting.module_goods.GoodsApplication",
"com.jackting.module_user.UserApplication",
};
}
在CommonApplication中加载各个组件的application
public class CommonApplication extends Application {
public void onCreate() {
modulesApplicationInit();
}
private void modulesApplicationInit(){
for (String moduleImpl : ModuleConfig.MODULE_LIST){
try {
Class> clazz = Class.forName(moduleImpl);
Object obj = clazz.newInstance();
if (obj instanceof IApplication){
((IApplication) obj).onCreate(this);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
}
组件的gradle配置
资源命名前缀定义:为了防止不同组件间资源命名重复问题,我们对各个组件定义不同的命名前缀,这样不同组件间的资源命名便一定不会重复
resourcePrefix "user_"
整个项目一起运行和组件单独运行时,AndroidManifest文件中四大组件的配置一定是不同的(比如启动activity的配置),所以两种模式要使用不同的manifest文件
sourceSets{
main{
if(isModule.toBoolean()){
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
java{
//全部module一起编译的时候剔除debug目录
exclude '**/**/debug/**'
}
}
}
}
MVP+dagger的引入
常用的开发模式有MVC、MVP和MVVM三种,mvc中acitvity太重,model层和view层耦合性太强,mvvm做了双向绑定,但是xml不能复用、调试问题极其不方便,个人对MVP是情有独钟,虽然类会多一点,但是代码结构清晰,便于管理和维护。
dagger2的使用有两种,一种和android平台有关,使用更方便,但是对项目的侵入性很强,不能和组件化结合使用,另一种和android平台无关,但是可以和组件化结合使用,于是我们选择了后者。
dagger2的引入
在CommonApplication定义AppComponent
public class CommonApplication extends Application {
private static AppComponent appComponent;
private void initInjector(){
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(sApplication))
.build();
}
public static AppComponent getAppComponent() {
return appComponent;
}
}
在Component在增加注入配置
void inject(XxxActivity activity);
在activity中初始化注入
DaggerUserComponent.builder()
.appComponent(getAppComponent())
.build()
.inject(this);
butterknife在组件化中的使用
在项目根目录下的build.gradle中增加classpath
dependencies {
classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
}
在各个组件/模块中增加插件配置
apply plugin: 'com.jakewharton.butterknife'
activity/fragment中使用,使用R2,而非R
@BindView(R2.id.et_user_login_username)
EditText etName;
@OnClick({R2.id.btn_login})
public void doClick(View view){
int id = view.getId();
if (id == R.id.btn_login) {
}
}
参考
知乎 Android 客户端组件化实践
浅谈项目架构重构之路——组件化与MVP
架构师的思维,聊一聊APP组件化的那些事儿 备用
WanAndroid架构源码
仿京东app
MVPArms
联系我们
邮箱:itdjx@qq.com