写点什么

Android 组件化,从入门到不可自拔,2021 必看

用户头像
Android架构
关注
发布于: 17 小时前

解耦


降低模块间的耦合,避免出现一处代码修改,牵一发而动全身的尴尬局面。


协同开发


项目越来越大,团队人数越来越多,模块化开发可在尽量解耦的情况下,使不同的开发人员专注于自己负责的业务,同步开发,显著提供开发效率。


[](


)模块化的弊端




那,模块化开发有没有什么弊端呢?


有。


任凭模块化做得多么好,还是跳不出是组合在单一项目下的。随着项目的发展与迭代,模块化开发渐渐显现了以下的问题:


项目代码量越来越大


每次的编译速度越来越慢,哪怕几行代码的修改,都需要花费好几分钟的时间,等着编译器编译运行结束后,才能查看代码的执行结果,这极大的降低了开发效率;


业务模块越来越多


不可避免地产生越来越多且复杂的耦合,哪怕一次小的功能更新,也需要对修改代码耦合的模块进行充分测试;


团队人数越来越多


这就要求开发人员了解与之业务相关的每一个业务模块,防止出现某位开发人员修改代码导致其他模块出现 bug 的情况,这个要求对于开发人员显然是不友好的;


那怎样解决模块化开发的这些弊端呢?


当然是组件化喽!


[](


)聊聊组件化


=======================================================================


组件化可以说是 Android 中级开发工程师必备技能了,能有效解决许多单一项目下开发中出现的问题。


并且我要强调的是,组件化真的不难,还没搞过的小伙伴不要怂。


[](


)什么是组件化




组件,顾名思义,“组装的零件”,术语上叫做软件单元,可用于组装在应用程序中。


所以,组件化,要更关注可复用性、更注重关注点分离、功能单一、高内聚、粒度更小、是业务上能划分的最小单元,毕竟是“组装的零件”嘛!


从这个角度上看,组件化的粒度,似乎要比模块化的粒度更小。


不过,我个人认为,要把组件化拆分到如此小的粒度,不可能,也没有必要。在组件化项目的实际开发中,组件化的粒度,是要比模块化的粒度更大的。


[](


)组件化的好处




首先要说的是,上述模块化的好处,组件化都有,不再赘述;上述模块化的弊端,组件化都给解决了,具体如下:


  1. 组件,既可以作为 library,又可以单独作为 application,便于单独编译单独测试,大大的提高了编译和开发效率;

  2. (业务)组件,可有自己独立的版本,业务线互不干扰,可单独编译、测试、打包、部署;

  3. 各业务线共有的公共模块可开发为组件,作为依赖库供各业务线调用,减少重复代码编写,减少冗余,便于维护;

  4. 通过 gradle 配置文件,可对第三方库进行统一管理,避免版本冲突,减少冗余;

  5. 通过 gradle 配置文件,可实现 application 与 library 灵活组合与拆分,可以更快速的响应需求方对功能模块的选择。


[](


)组件化实践


=======================================================================


首先要说明的是,下述是一个简单的不能再简单的组件化案例,只求帮助大家搭建起组件化的架构,功能上极其简约。


九层之台,起于累土。我们这就开始搭组件化的架构吧!


[](


)组件化架构




先上一张组件化项目整体架构图



其中的“业务组件”,既可以作为 application 单独打包为 apk,又可以作为 library 灵活组合为综合一些的应用程序。


大多数开发者做组件化时面对的业务需求,都是上面这种情况。


我司的需求略有不同,不是将子业务组件组合为整体应用程序,而是反其道而行之,需要将已上线项目拆分给不同的业务公司使用,在不同业务系统中,项目的逻辑和代码会有区别,且版本不统一。


基于此,我搭建项目架构如下图所示,其中“m_moudle_main”是公司主要的、且逻辑和代码相同的业务组件,“b_moudle_north”和“b_moudle_south”是拆分出来的业务组件,管理各自私有的逻辑和代码,且版本有差别。



从 Android 工程看,结构如下图所示:



注:取 moudle 名,手动加上“b_” “m_” “x_”这样的前缀,只是为了便于分辨组件层次。


[](


)统一配置文件




在项目根目录下,自建 config.gradle 文件,对项目进行全局统一配置,并对版本和依赖进行统一管理,源码如下:


/**


  • 全局统一配置


*/


ext {


/**


  • module 开关统一声明在此处

  • true:module 作为 application,可单独打包为 apk

  • false:module 作为 library,可作为宿主 application 的组件


*/


isNorthModule = false


isSouthModule = false


/**


  • 版本统一管理


*/


versions = [


applicationId : "com.niujiaojian.amd", //应用 ID


versionCode : 100, //版本号


versionName : "1.0.0", //版本名称


compileSdkVersion : 28,


minSdkVersion : 21,


targetSdkVersion : 28,


androidSupportSdkVersion: "28.0.0",


constraintlayoutVersion : "1.1.3",


runnerVersion : "1.1.0-alpha4",


espressoVersion : "3.1.0-alpha4",


junitVersion : "4.12",


annotationsVersion : "28.0.0",


appcompatVersion : "1.0.0-beta01",


designVersion : "1.0.0-beta01",


multidexVersion : "1.0.2",


butterknifeVersion : "10.1.0",


arouterApiVersion : "1.4.1",


arouterCompilerVersion : "1.2.2",


arouterAnnotationVersion: "1.0.4"


]


dependencies = [


"appcompat" : "androidx.appcompat:appcompat:${versions["appcompatVersion"]}",


"constraintlayout" : "androidx.constraintlayout:constraintlayout:${versions["constraintlayoutVersion"]}",


"runner" : "androidx.test:runner:${versions["runnerVersion"]}",


"espresso_core" : "androidx.test.espresso:espresso-core:${versions["espressoVersion"]}",


"junit" : "junit:junit:${versions["junitVersion"]}",


//注释处理器


"support_annotations" : "com.android.support:support-annotations:${versions["annotationsVersion"]}",


"design" : "com.google.android.material:material:${versions["designVersion"]}",


//方法数超过 65535 解决方法 64K MultiDex 分包方法


"multidex" : "androidx.multidex:multidex:2.0.0",


//阿里路由


"arouter_api" : "com.alibaba:arouter-api:${versions["arouterApiVersion"]}",


"arouter_compiler" : "com.alibaba:arouter-compiler:${versions["arouterCompilerVersion"]}",


"arouter_annotation" : "com.alibaba:arouter-annotation:${versions["arouterAnnotationVersion"]}",


//黄油刀


"butterknife" : "com.jakewharton:butterknife:${versions["butterknifeVersion"]}",


"butterknife_compiler": "com.jakewharton:butterknife-compiler:${versions["butterknifeVersion"]}"


]


}


然后在 project 的 build.gradle 中引入 config.gradle 文件:


apply from: "config.gradle"


[](


)基础公共组件




基础公共组件 common 将一直作为 library 存在,所有业务组件都需要依赖 common 组件。


common 组件主要负责封装公共部分,如网络请求、数据存储、自定义控件、各种工具类等,以及对第三方库进行统一依赖等。


下图是我的 common 组件的包结构图:



前文有言,common 组件还负责对第三方库进行统一依赖,这样上层业务组件就不需要再对第三方库进行重复依赖了,其 build.gradle 源码如下所示:


apply plugin: 'com.android.library'


apply plugin: 'com.jakewharton.butterknife'


……


dependencies {


// 在项目中的 libs 中的所有的.jar 结尾的文件,都是依赖


implementation fileTree(dir: 'libs', include: ['*.jar'])


//把 implementation 用 api 代替,它是对外部公开的, 所有其他的 module 就不需要添加该依赖


api rootProject.ext.dependencies["appcompat"]


api rootProject.ext.dependencies["constraintlayout"]


api rootProject.ext.dependencies["junit"]


api rootProject.ext.dependencies["runner"]


api rootProject.ext.dependencies["espresso_core"]


//注释处理器,butterknife 所必需


api rootProject.ext.dependencies["support_annotations"]


//MultiDex 分包方法


api rootProject.ext.dependencies["multidex"]


//Material design


api rootProject.ext.dependencies["design"]


//黄油刀


api rootProject.ext.dependencies["butterknife"]


annotationProcessor rootProject.ext.dependencies["butterknife_compiler"]


//Arouter 路由


annotationProcessor rootProject.ext.dependencies["arouter_compiler"]


api rootProject.ext.dependencies["arouter_api"]


api rootProject.ext.dependencies["arouter_annotation"]


}


[](


)业务组件




业务组件在 library 模式下,向上组合为整体性项目;在 application 模式下,可独立运行。


其 build.gradle 源码如下:


if (Boolean.valueOf(rootProject.ext.isModule_North)) {


apply plugin: 'com.android.application'


} else {


apply plugin: 'com.android.library'


}


apply plugin: 'com.jakewharton.butterknife'


……


dependencies {


implementation fileTree(dir: 'libs', include: ['*.jar'])


//公用依赖库


implementation project(':x_module_common')


implementation project(':m_module_main')


//黄油刀


annotationProcessor rootProject.ext.dependencies["butterknife_compiler"]


//Arouter 路由


annotationProcessor rootProject.ext.dependencies["arouter_compiler"]


}


至此,组件化架构的搭建就算完成了。


可还有几个问题,是组件化开发中必须要关注的,也是项目做组件化改造时可能会遭遇的难点,我们一起来看看吧。


[](


)组件化必须要关注的几个问题


===============================================================================


[](


)Application




在 common 组件中有 BaseAppliaction,提供全局唯一的 context,上层业务组件在组件化模式下,均需继承于 BaseAppliaction。


/**


  • 基础 Application,所有需要模块化开发的 module 都需要继承自此 BaseApplication。


*/


public class BaseApplication extends Application {


//全局唯一的 context


private static BaseApplication application;


@Override


protected void attachBaseContext(Context base) {


super.attachBaseContext(base);


application = this;


//MultiDexf 分包初始化,必须最先初始化


MultiDex.install(this);


}


@Override


public void onCreate() {


super.onCreate();


initARouter();


}


/**


  • 初始化路由


*/


private void initARouter() {


if (BuildConfig.DEBUG) {


ARouter.openLog(); // 打印日志


ARouter.openDebug(); // 开启调试模式(如果在 InstantRun 模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)


}


ARouter.init(application);// 尽可能早,推荐在 Application 中初始化


}


/**


  • 获取全局唯一上下文

  • @return BaseApplication


*/


public static BaseApplication getApplication() {


return application;


}


[](


)applicationId 管理




可为不同组件设置不同的 applicationId,也可缺省,在 Android Studio 中,默认的 applicationId 与包名一致。


组件的 applicationId 在其 build.gradle 文件的 defaultConfig 中进行配置:


if (Boolean.valueOf(rootProject.ext.isModule_North)) {


//组件模式下设置 applicationId


applicationId "com.niujiaojian.amd.north"

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android 组件化,从入门到不可自拔,2021必看