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

Android 组件化,从入门到不可自拔组件化能够显著提高 Android 项目开发效率 支持不同业务组件单独打包或者组合打包 可以说是 Android 开发者必备技能 本文通过一个极其简单的实践案例 梳理了组件化的配置过程 并辅以全部源码 希望对还没有应用组件化的开发者有些帮助

写在前面

项目源码已经放在github上:https://github.com/ZuoHailong/AndroidModuleDemo (文后附的还有链接)

欢迎大家关注我的公众号:牛角尖尖上起舞

谈谈模块化

要聊组件化,惯例是要谈谈模块化的,毕竟它与组件化确实有一些相同点,在组件化的项目中它也会与组件化发生关联。

什么是模块化

模块化的好处

模块化有哪些好处呢?

  • 复用
    首先,基础模块,可为业务模块所复用;
    其次,子业务模块,可为父业务模块,甚至不同的项目所复用。




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

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

  • ……

模块化的弊端

任凭模块化做得多么好,还是跳不出组合在单一项目下的范围,项目规模越来越大,业务模块越来越多,团队人数越来越多,模块化开发渐渐出现了以下的问题:

  • 项目代码量越来越大,每次的编译速度越来越慢,哪怕几句代码的修改,都需要等待若干分钟等待编译运行查看执行结果,极大的降低了开发效率;
  • 业务模块越来越多,不可避免地产生越来越多且复杂的耦合,哪怕一次小的功能更新,也需要对修改代码耦合的模块进行充分测试;
  • 团队人数越来越多,却要求开发人员了解与之业务相关的每一个业务模块,防止出现此开发人员修改代码导致其他模块出现bug的情况,这个要求对于开发人员显然是不友好的;
  • ……

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

聊聊组件化

组件化可以说是Android中级开发工程师必备技能了,能有效解决许多单一项目下开发出现的问题。并且我要强调的是,组件化真的不难,还没搞过的小伙伴不要怂。

什么是组件化

看到这里,懵逼否?

组件化的概念,有点云里雾里,并且对上述观点,我认为大家还是要在各自的开发中见仁见智。

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

组件化的好处

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

  • 组件,既可以作为library,又可以单独作为application,便于单独编译单独测试,大大的提高了编译和开发效率;
  • (业务)组件,可有自己独立的版本,业务线互不干扰,可单独编译、测试、打包、部署
  • 各业务线共有的公共模块开发为组件,作为依赖库供各业务线调用,减少重复代码编写,减少冗余,便于维护
  • 通过gradle配置文件,可对第三方库的引入进行统一管理,避免版本冲突,减少冗余库
  • 通过gradle配置文件,可对各组件实现library与application间便捷切换,实现项目的按需加载

组件化实践

首先要说明的是,下述是一个简单的不能再简单的组件化案例,只求帮助大家搭建起组件化的架构,不求实现什么具体的功能。

如果对组件化实现的丰富功能感兴趣,可参考文后的感谢链接,阅读大神们的项目源码。

九层之台,起于累土。我们还是先搭组件化的架构吧!

组件化架构

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

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

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

统一配置文件

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

/ * 全局统一配置 */ ext { / * module开关统一声明在此处 * true:module作为application,可单独打包为apk * false:module作为library,可作为宿主application的组件 */ isNorthModule = false isSouthModule = false / * 版本统一管理 */ versions = [ applicationId : "com.hailong.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组件主要负责封装公共部分,如网络请求、数据存储、自定义控件、各种工具类等,以及对第三方库进行统一依赖。

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。

/ * Describe:基础Application,所有需要模块化开发的module都需要继承自此BaseApplication。 * Created by ZuoHailong on 2019/4/11. */ 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.hailong.amd.north" } 

manifest管理

组件在集成模式和组件化模式下,需要配置不同的manifest文件,因为在组件化模式下,程序入口Activity和自定义的Application是不可或缺的。

在组件的build.gradle文件的android中进行manifest的管理:

 /* * java插件引入了一个概念叫做SourceSets,通过修改SourceSets中的属性, * 可以指定哪些源文件(或文件夹下的源文件)要被编译, * 哪些源文件要被排除。 * */ sourceSets { main { if (Boolean.valueOf(rootProject.ext.isModule_North)) {//apk manifest.srcFile 'src/main/manifest/AndroidManifest.xml' } else { manifest.srcFile 'src/main/AndroidManifest.xml' java { //library模式下,排除java/debug文件夹下的所有文件 exclude '*module' } } } } 

资源名冲突问题

资源名冲突问题,相信大家多多少少都遇到过,以前最常见的就是第三方SDK导致的资源名冲突了。这个问题没有特别好的解决办法,只能通过设置资源名前缀 resourcePrefix 以及约束自己开发习惯进行解决。

资源名前缀 resourcePrefix ,是在project的build.gradle中进行设置的:

/ * 限定所有子类xml中的资源文件的前缀 * 注意:图片资源,限定失效,需要手动添加前缀 * */ subprojects { afterEvaluate { android { resourcePrefix "${project.name}_" } } } 

这样设置完之后,string、style、color、dimens等中资源名,必须以设置的字符串为前缀,而layout、drawable文件夹下的shape他们的xml文件的命名,必须以设置的字符串为前缀,否则会报错提示。

另外,资源前缀的设置对图片的命名无法限定,建议大家约束自己的开发习惯,自觉加上前缀。

butterknife问题

有博主在去年的博文中建议大家选用butterknife 8.4.0版本,我进行组件化构建时,目前的最新版10.1.0用起来是没有问题的,大家可放心将butterknife升级到最新版。

butterknife存在是问题是控件ID找不到,只要将R替换为R2即可解决问题。需要注意的是,在如下代码示例外的位置,不要这样做,保持使用R即可,如 setContentView(R.layout.b_module_north_activity_splash)

/ * Describe: * Created by ZuoHailong on 2019/4/19. */ public class SplashActivity extends BaseActivity { @BindView(R2.id.btn_toMain) Button btnToMain; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.b_module_north_activity_splash); ButterKnife.bind(this); } …… @OnClick(R2.id.btn_toMain) public void onViewClicked() { } } 

另外要注意的是,每一个使用butterknife的组件,在其 build.gradle 的 dependencies 都要配置注解处理器处理其 compiler 库

apply plugin: 'com.jakewharton.butterknife' …… dependencies { …… //黄油刀 annotationProcessor rootProject.ext.dependencies["butterknife_compiler"] } 

组件间跳转

业务组件间不存在依赖关系,不可以通过Intent进行显式跳转,是要借助于路由的,我使用的是阿里的开源框架ARouter。

我在案例中只使用了ARouter的基础的页面跳转功能,更复杂的诸如携带参数跳转、声明拦截器等功能的使用方法,大家可到Github上查看其使用文档。

在每一个需要用到ARouter的组件的build.gradle文件中对其进行配置:

android { ... defaultConfig { ... //Arouter路由配置 javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] includeCompileClasspath = true } } } } dependencies{ ... //Arouter路由 annotationProcessor rootProject.ext.dependencies["arouter_compiler"] } 

跳转目标页面配置:

@Route(path = "/main/MainActivity") public class MainActivity extends BaseActivity { …… } 

跳转发起页面的发起调用:

... ARouter.getInstance() .build("/main/MainActivity") .navigation(); ... 

第三方sdk集成问题

项目不可避免的要使用第三方sdk,如友盟分享、高德地图、腾讯 bugly 等,都需要在相应第三方的开发者中心使用包名、applicationId注册,获取相应的 appkey 、appsecret等。

那么,在组件化开发中,到底应该使用哪个组件的包名、applicationId 到第三方平台进行注册呢?

我的想法是使用基础库的包名、applicationId 进行注册,然后将相应的第三方sdk的功能封装为功能组件,供上层业务组件进行调用。

但也存在一个问题,即在第三方的管理平台上,将无法区分相应统计信息到底属于哪一个组件化app。

所以还是要根据业务要求、统计要求自己选择了。

写在后面

组件化优势多多,本文标题上说不可自拔,快感来的最快的,当属大大提升了编译速度了。

开心的笑了起来
项目源码已经放在github上:https://github.com/ZuoHailong/AndroidModuleDemo

欢迎大家关注我的公众号:牛角尖尖上起舞

感谢

鸿洋大神:wanAndroid

吴蜀黍:Android组件化框架搭建

杨充:Android组件化开发实践和案例分享

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/213222.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午6:18
下一篇 2026年3月18日 下午6:18


相关推荐

  • order by case when执行优先级_sql case语句

    order by case when执行优先级_sql case语句MySQL语句中执行优先级——and比or高例:select*fromtablewhere条件1AND条件2OR条件3等价于select*fromtablewhere(条件1AND条件2)OR条件3select*fromtablewhere条件1AND条件2OR条件3AND条件4等价于select*fromtablewhere(条件1AND条件2)OR(条件3AND条件4)sql执行顺序

    2025年8月26日
    6
  • java语言和C语言的区别

    java语言和C语言的区别简单的说就是两种不同的语言.但是它们之间既有联系又有区别

    2022年7月8日
    24
  • Linux TSO流程分析

    Linux TSO流程分析1 TSO transimitseg 是针对 tcp 而言的 是指协议栈可以将 tcp 分段的操作 offload 到硬件的能力 本身需要硬件的支持 当网卡具有 TSO 能力时 上层协议栈可以直接下发一个超过 MTU 数据包 而把数据包拆分的动作交给硬件去做 节省 cpu 资源 除了 TSO 内核还有一个 GSO GSO 不区分协议类型 GSO 默认是开启的 GSO 是在软件上实现的一种延迟分段的技术 相比 TSO GSO 最终还是需要协议栈自己完成分段的处理 即使网卡没有 TSO 能力 传输层依然可以封装一个超过 M

    2026年3月18日
    2
  • 预训练模型还要训练吗_多模态预训练模型

    预训练模型还要训练吗_多模态预训练模型若使用已保存好的镜像reid_mgn:v1,在本机上可按如下操作训练#1.进入已保存环境的镜像(reid_mgn:v1(8.48G)、pytorch/pytorch:1.0.1-cuda10.0-cudnn7-devel_mgnreid(6.37G))nvidia-dockerrun-it–rm-v/home/lc-deep/sdr:/home/personReID…

    2022年10月6日
    4
  • Trae SOLO实战分享:3小时上线一个网站,全栈开发 + 自动部署,吊打Claude Code?(附保姆级教程)

    Trae SOLO实战分享:3小时上线一个网站,全栈开发 + 自动部署,吊打Claude Code?(附保姆级教程)

    2026年3月16日
    4
  • YUV 简介及使用

    YUV 简介及使用一、YUV简介YUV:是一种颜色编码方法,常使用在各个视频处理组件中Y’UV,YCbCr,YPbPr等专有名词都可以称为YUV,彼此有重叠Y表示明亮度(单取此通道即可得灰度图),U和V则是色度、浓度主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0可以根据其采样格式来从码流中还原每个像素点的YUV值,进而通过YUV…

    2022年7月16日
    17

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号