从2017年只有几个大厂在作组件化,到今天已经繁花似锦。html
愈来愈多的团队,愈来愈多的项目都作了组件化。java
大叔相信即便你没有作过组件化项目,可是,对组件化也早就听烂了。android
可是,组件化开发多少有些技术门槛。git
有不少大神写过相关文章,通俗易懂的很少。深刻浅出的更很少。github
大叔不才,愿意冒着不要脸的风险一试,通俗易懂、深深浅浅的来聊聊组件化开发,若是对你有一点点启发,请记得回来给大叔点个赞。api
这篇blog,大叔酝酿了很长长长长长长长长长长长长长长时间。微信
这种分层架构,有什么用呢?markdown
分解成多module的项目结构,就是组件化开发了吗?数据结构
固然不是,多module分层的项目结构,只是组件化开发的一部分。只是组件化开发的基础。架构
大叔,搜索了不少资料,发现,对于组件化开发,并无很严格的定义。
固然,咱们没有必要,过于纠结 ”组件化开发的定义“;
咱们更关注这种开发思想对项目带来的好处以及在团队中如何运用。
下面是个人理解,若有出入,欢迎提出来一块儿讨论。
尤为在大团队,大项目上,组件化的优点会更加凸显。
大项目分解成一个个小型项目,至关于将一个复杂的问题拆解成一个个相对简单的问题。
每一个成员,能够专一在本身相关业务的module上。
同层的代码不能相互调用。底层的代码也不能调用上层。
这种编译隔离,带来了,模块间的高度解耦。
让模块的依赖关系清晰。
(若是构建正确)组件化设计的系统,比传统的总体设计具备更高的可重用性。
什么是组件?什么是模块?
组件强调复用,模块强调职责划分。 他们没有很是严格的划分。
达到可复用要求的模块,那么这个模块就是组件。
base层的module必须是可复用的,若是项目设计的好,business层都能被复用,每一个module都能成为组件。
可重用性是组件化思想的核心。
如此架构,是否也适合技术中台的架构?
若是咱们要为某个已经存在的组件,从新开发一个新组件,将变得很是可行。
组件内的重构也将变得很是可行。
新的组件的设计只要保证对外提供的接口,彻底符合,旧组件对外提供的接口
咱们想象下,在APP运行时,business中的组件能够动态加载,也可动态卸载。
那么咱们能够轻松实现组件的懒加载:用户用到的组件,那么就加载进来。用完以后即可以卸载。
大的android工程项目,build一次要到5分钟左右,太浪费时间了。
拆成多个组件以后,若是每一个组件都能单独build,单独测试,那么将大大提高开发效率。
上面讨论的这些优点,并非将简单将 单工程 拆分红 分层的多module工程结构 就能得到这些优点。
想要得到这些优点,还任重道远,咱们还须要解决不少问题,才能让咱们的项目具有上面的说的优点。
经过ARouter通讯。
ARouter是阿里开源的一个项目。github.com/alibaba/ARo…
经过ARouter跨module跳转Activity
@Route(path = "/test/activity")//申明路由
public class YourActivity extend Activity {
...
}
//经过路由启动Activity
ARouter.getInstance().build("/test/activity").withLong("key1", 666L).navigation();
复制代码
经过ARouter在module间共享对象,实现module间通讯。
好比:咱们有一个帐号模块 business:account ,提供了登陆、登出、用户信息查询等业务。
同级的其余模块,如何跟帐号模块通讯?获取用户的登陆状态以及用户相关信息?
public class AccountBean {
private String name;
private int age;
//....
}
public interface IAccountService extends IProvider {
void login(Context context);//登陆
void logout(Context context);//登出
AccountBean getAccountBean();//获取帐号信息
}
复制代码
对外的数据结构和接口定义。
@Route(path = BusinessRoutePath.ModuleAccount.ACCOUNT)
public class AccountServiceImpl implements IAccountService {
//.....
}
复制代码
bussiness:account模块中的实现。
IAccountService accountService = ARouter.getInstance().navigation(IAccountService.class);
accountService.login(activity);
AccountBean bean = accountService.getAccountBean();
复制代码
可是问题来了:
同层的其余模块,如何,能拿到ARouter的path?
同层的其余模块编译时,如何,共享AccountBean类、IAccountService接口?
这就是模块之间的编译隔离,带来的问题。
咱们很天然的想到了framework模块,或者base层的其余模块。
咱们只要将这些path定义、AccountBean类、IAccountService接口,下沉到base层,就能够实现编译上的代码共享。
如此一来,就带来了,另外一个问题:代码的中心化问题。
简单的path字符串定义,放在framework却是还好。
若是全部business模块对外提供的接口和数据结构,都定义到framework的话,问题就有点严峻。
将会破坏:组件的 可替代性、可重用性、组件间耦合度
由于framework是基础模块嘛,全部business模块都依赖的模块,如此,无论你的business1模块是否依赖business2模块的对外接口,都会存在这一层依赖。
模块间的代码边界出现一些劣化。缺乏了编译上的隔离。许多模块将会变得不够“独立”了。
可替代性、可重用性 愈来愈弱,想要替换或者复用某个business组件将变得愈来愈难。
将会致使,咱们很难知道,哪些business对哪些business 接口有依赖。
同时,framework模块随着功能迭代,会不断膨胀。
这就是,中心化的问题。
因而咱们很天然的想到了一个解决方案:
实现了另外一种接口暴露的形式——“.api化”。
将 business模块 对外提供的接口单独抽到 business-api 模块中。其余依赖他的模块只须要依赖他的business-api便可。
这个方案如何实践下去呢?
微信团队出了一个很巧妙的方案,这个方案对android的组件化开发,产生了很是深远的影响。
后面不少作组件化开发的团队,在解决中心化问题基本都会用到相似的方案。
如下为,微信官方博客的原文引用:
使用方式和思路都很简单。对于java文件,将工程里想要暴露出去的接口类后缀名从“.java”改为“.api”,就能够了。
并且并不仅是java文件,其余文件若是也想暴露,在文件名后增长".api”,也同样能够。
固然,要让工程支持这样的方式,gradle文件确定会有一点改变。
![]()
它的实现原理也至关简单:自动生成一个“SDK”工程,拷贝.api后缀文件到工程中就好了,后面其余工程依赖编译的只是这个生成的工程。简单好用。
api方案有点相似于android的AIDL的思路。
gradle 根据src/api文件来,自动生成{moduleName}-api模块。
若是,src/api文件不存在,将不会自动生成 {moduleName}-api 模块。
复制代码
经过API模块来解决代码中心化问题带来的好处:
从而增强了模块的:可替代性。
只要两个business对外提供的API一致,就能够相互替换。
模块变多了,项目变大了,整个项目的编译速度变慢了。
业内有两种经常使用作法。
方案一:动态配置 build.gradle。
只要让单个的组建能编译成APP就能单独测试。
方案二:多壳APP
方案来自,在聚美优品。
这里须要注意:假如,Demo1是business1的壳APP。那么Demo1还须要依赖哪些businessXXX呢?
恰好,前面作的api化,能排上用场。
business1依赖的businessXXX-api模块对应的businessXXX模块,Demo1也须要依赖。
为甚?由于,business1依赖的businessXXX-api模块,意味着,business1须要依赖 businessXXX提供的功能,好比要跳转到businessXXX的activity?或者,要获取businessXXX的对象。
方案一:Extra properties
developer.android.com/studio/buil…
在项目跟目录的build.gradle文件中配置Extra属性
如此能够实现统一管理版本号,和依赖。
可是,可是,可是,这个方案存在明显的缺陷。
不支持,自动补全
不支持Find Usages,查找全部应用的地方
使用时,不支持点击跳转
严重影响开发体验。
方案二:buildSrc
支持,Find Usages
更完美的语法高亮
gradle文件复用
版本和依赖作到了统一管理,可是每一个module都有各自的build.gradle,重复的build.gradle代码依然没有复用。
咱们能够经过apply from:"xxx.gradle"的方式来复用gradle代码。
以下图
如上,咱们能够在base.gradle中为每个项目添加配置统一的编译逻辑,如:kotlin的支持,java版本的修改,maven库上场的逻辑等等
如何防止资源名冲突?
好比businessA 和 businessB都在drawable目录下,都有一张同名的图片。
这两张图片只有一张会被打包到apk中,被使用。
这样就容易出现混乱。
这个问题比较好解决。让每一个模块的资源名固定一个前缀。只要模块之间的前缀不同就不会冲突。
gradle的resourcePrefix配置,恰好符合咱们的需求。
以下配置,若是module中存在资源不以app_
开头,**lint走查会报warnning。**注意不会编译失败。
android {
resourcePrefix 'app_'
}
复制代码
以下截图的warning:
能够经过booster的资源内联工具解决,R类的冗余。
详细能够本身查看booster官网,booster是didi开源的一个插件。booster.johnsonlee.io/feature/shr…
没有找到很好的解决方案。
每一个方案都有缺陷
好比说,business1和business2都要用到同一张图片。
那么这张图片该放到哪里呢?
一、把他放到api模块里来共享
资源这种,并不是功能依赖,放到api模块也不太合适。
由于这样可能形成business1和business2模块本来没有关联也没有依赖;
但由于共用同一个资源,却致使存在了依赖。
二、在business1和business2中都放一个图片
如此会增大包体
三、在business1和business2中都放文件名同名的图片,让编译时资源合并的时候只使用同一张图片。
如此一来,放开各个模块资源命名,也容易致使开发时发生冲突。
自定义lint规则,让存在common和{moduleName}两种前缀?
四、将这张图片下沉到base层,如:framework模块,或者,单开一个lib-resource
如此一来,将会出现资源中心化问题。
上面的方法多少都有些缺陷,大叔尚未找到一个优雅的方式。若是你有什么好想法,必定要留言告诉大叔,大叔在此谢过你了。
千万不能这么操做。
假如:在 business/setting 中直接在gradle配置中依赖,business:account。
那么编译上的代码隔离就完全被毁。就跟不要谈组件的可重用性、可替代性了。
implementation project(":business:account") 复制代码
各个组件如何得到Application.attach()、Application.onCreate()、Application.onTerminate()等的回调。
未完待续
未完待续,待大叔踩过坑,实现了,再来写。
未完待续,待大叔踩过坑,实现了,再来写。
待大叔继续探索
最后咱们再回到,组件化自己上来。
组件化开发不只仅是一种多module分层的项目结构;他不只仅是一种架构;他更是一种架构思想,一种追求模块复用精神。
有人说小项目没有必要作组件化开发。大叔不这么认为,小项目依然适合作组件化,除非大家团队只有一个项目,而且项目几乎不须要迭代。组件跨项目的复用也是一件让人十分兴奋的优点。
前几年听烂了的技术中台,跟组件化的架构也不谋而合。
最后,十分感谢,前辈们将他们的组件化经验分享到互联网,为咱们这些组件化的后来者提供了宝贵的资料。
感谢!感谢!感谢!感谢!感谢!大叔给大家一个么么哒
Is there a difference between a component and a module
最后的最后,若是这篇文章对你有一点点启发,请必定要点个赞再走。这对一个草根原创技术博主很重要……