什么是组件化Android组件化,为什么要组件化

Android组件化和插件化开发 - Sun‘刺眼的博客 - 博客园
随笔 - 732, 文章 - 0, 评论 - 35, 引用 - 0
Android组件化和插件化开发
什么是组件化和插件化?
组件化开发就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。插件化开发和组件化开发略有不用,插件化开发时将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。
android工程的组件一般分为两种,lib组件和application组件application组件是指该组件本身就可以运行并打包成apklib组件是指该组件属于app的一部分,可以供其它组件使用但是本身不能打包成apk
为什么要有组件化?
加入一个app工程只有一个组件,随着app业务的壮大模块越来越多,代码量超10万是很正常的,这个时候我们会遇到以下问题
稍微改动一个模块的一点代码都要编译整个工程,耗时耗力
公共资源、业务、模块混在一起耦合度太高
不方便测试
组件化正确的姿势
既然选择使用组件化,那么如何正确的使用它呢?这里给出一种解决方案,如果你有更好的方案,欢迎交流。我们创建了一个app工程project,默认里面有一个app组件,这个app组件是可以直接运行的。怎么划分组件呢?
1.新建一个lib组件,new Module&&Andorid Library,取名BaseUtilLib,我们将所有的公共的工具类、网络分装等类放在其中
2.新建一个lib组件,BaseReslLib,我们将所有的公共资源、drawable、String等类放在其中
3.将app按照自己的规则划分成多个模块,比如按业务按地区等都可以
4.逐一开发某个模块,比如Test模块,新建一个TestApp组件,TestApp组件引用[1][2]步骤的BaseUtilLib和BaseReslLib,在TestApp组件里添加并引用TestLib组件。在TestLib的activity中写代码写业务逻辑,TestApp只负责跳转和测试
5.将工程中的所有类似TestLib组件(不是TestApp组件)引入到工程的app中看着有点乱,整理出一张图
这样的好处有
每个模块可以独立开发编译运行开发单个模块时可以共享资源和工具类可以针对单个模块测试
为什么有插件化?
有了组件化,为什么还要用插件化呢?插件化开发总的来说有以下几点好处(不同插件框架不一样):
宿主和插件分开编译
动态更新插件
按需下载模块
方法数或变量数爆棚
开放出来的插件化开发框架比较多,他们各自都有自己的优势和和不足,实现的原理也有差别下面列
开源的插件化框架
目前开源的这几个框架有宿主和插件分离的也有融合在一起的,每个框架的详细介绍和demo在github里都可以查看到。插件化demo运行起来比较简单,但是真正将它用到实际项目中还是要考虑很多小细节的,目前我也正处于研究阶段。
转自:大黑在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。
点击阅读原文
Android彻底组件化方案实践
日 发布,来源:
作者博客http://www.jianshu.com/u/1c68d0bad5a2文章目录
模块化、组件化与插件化
如何实现组件化
组件的单独调试
组件的数据传输
组件之间的UI跳转
组件的生命周期
组件化的拆分步骤和动态需求
组件化的动态需求
1模块化、组件化与插件化 项目发展到一定程度,随着人员的增多,代码越来越臃肿,这时候就必须进行模块化的拆分。在我看来,模块化是一种指导理念,其核心思想就是分而治之、降低耦合。而在Android工程中如何实施,目前有两种途径,也是两大流派,一个是组件化,一个是插件化。
提起组件化和插件化的区别,有一个很形象的图:上面的图看上去比较清晰,其实容易导致一些误解,有下面几个小问题,图中可能说的不太清楚:
组件化是一个整体吗?去了头和胳膊还能存在吗?左图中,似乎组件化是一个有机的整体,需要所有器官都健在才可以存在。而实际上组件化的目标之一就是降低整体(app)与器官(组件)的依赖关系,缺少任何一个器官app都是可以存在并正常运行的。
头和胳膊可以单独存在吗?左图也没有说明白,其实答案应该是肯定的。每个器官(组件)可以在补足一些基本功能之后都是可以独立存活的。这个是组件化的第二个目标:组件可以单独运行。
组件化和插件化可以都用右图来表示吗?如果上面两个问题的答案都是YES的话,这个问题的答案自然也是YES。每个组件都可以看成一个单独的整体,可以按需的和其他组件(包括主项目)整合在一起,从而完成的形成一个app
右图中的小机器人可以动态的添加和修改吗?如果组件化和插件化都用右图来表示,那么这个问题的答案就不一样了。对于组件化来讲,这个问题的答案是部分可以,也就是在编译期可以动态的添加和修改,但是在运行时就没法这么做了。而对于插件化,这个问题的答案很干脆,那就是完全可以,不论实在编译期还是运行时!
本文主要集中讲的是组件化的实现思路,对于插件化的技术细节不做讨论,我们只是从上面的问答中总结出一个结论:组件化和插件化的最大区别(应该也是唯一区别)就是组件化在运行时不具备动态添加和修改组件的功能,但是插件化是可以的。
暂且抛弃对插件化“道德”上的批判,我认为对于一个Android开发者来讲,插件化的确是一个福音,这将使我们具备极大的灵活性。但是苦于目前还没有一个完全合适、完美兼容的插件化方案(RePlugin的饥饿营销做的很好,但还没看到疗效),特别是对于已经有几十万代码量的一个成熟产品来讲,套用任何一个插件化方案都是很危险的工作。所以我们决定先从组件化做起,本着做一个最彻底的组件化方案的思路去进行代码的重构,下面是最近的思考结果,欢迎大家提出建议和意见。
2如何实现组件化? 要实现组件化,不论采用什么样的技术路径,需要考虑的问题主要包括下面几个:
如何将一个庞大的工程拆分成有机的整体?
组件单独运行
上面也讲到了,每个组件都是一个完整的整体,如何让其单独运行和调试呢?
因为每个组件都会给其他组件提供的服务,那么主项目(Host)与组件、组件与组件之间如何传递数据?
UI跳转可以认为是一种特殊的数据传递,在实现思路上有啥不同?
组件的生命周期
我们的目标是可以做到对组件可以按需、动态的使用,因此就会涉及到组件加载、卸载和降维的生命周期。
在开发阶段如何做到按需的编译组件?一次调试中可能只有一两个组件参与集成,这样编译的时间就会大大降低,提高开发效率。
组件之间的交互如果还是直接引用的话,那么组件之间根本没有做到解耦,如何从根本上避免组件之间的直接引用呢?也就是如何从根本上杜绝耦合的产生呢?只有做到这一点才是彻底的组件化。
2.1 代码解耦把庞大的代码进行拆分,Androidstudio能够提供很好的支持,使用IDE中的multiple module这个功能,我们很容易把代码进行初步的拆分。在这里我们对两种
module进行区分:
一种是基础库library,这些代码被其他组件直接引用。比如网络库module可以认为是一个library。
另一种我们称之为Component,这种module是一个完整的功能模块。比如读书或者分享module就是一个Component。
为了方便,我们统一把library称之为依赖库,而把Component称之为组件,我们所讲的组件化也主要是针对Component这种类型。而负责拼装这些组件以形成一个完成app的module,一般我们称之为主项目、主module或者Host,方便起见我们也统一称为主项目。
经过简单的思考,我们可能就可以把代码拆分成下面的结构:这种拆分都是比较容易做到的,从图上看,读书、分享等都已经拆分组件,并共同依赖于公共的依赖库(简单起见只画了一个),然后这些组件都被主项目所引用。
读书、分享等组件之间没有直接的联系,我们可以认为已经做到了组件之间的解耦。但是这个图有几个问题需要指出:
● 从上面的图中,我们似乎可以认为组件只有集成到主项目才可以使用,而实际上我们的希望是每个组件是个整体,可以独立运行和调试,那么如何做到单独的调试呢?
● 主项目可以直接引用组件吗?也就是说我们可以直接使用compile project(:reader)这种方式来引用组件吗?如果是这样的话,那么主项目和组件之间的耦合就没有消除啊。我们上面讲,组件是可以动态管理的,如果我们删掉reader(读书)这个组件,那么主项目就不能编译了啊,谈何动态管理呢?所以主项目对组件的直接引用是不可以的,但是我们的读书组件最终是要打到apk里面,不仅代码要和并到claases.dex里面,资源也要经过meage操作合并到apk的资源里面,怎么避免这个矛盾呢?
● 组件与组件之间真的没有相互引用或者交互吗?读书组件也会调用分享模块啊,而这在图中根本没有体现出来啊,那么组件与组件之间怎么交互呢?
这些问题我们后面一个个来解决,首先我们先看代码解耦要做到什么效果,像上面的直接引用并使用其中的类肯定是不行的了。所以我们认为代码解耦的首要目标就是组件之间的完全隔离,我们不仅不能直接使用其他组件中的类,最好能根本不了解其中的实现细节。只有这种程度的解耦才是我们需要的。
2.2 组件的单独调试
其实单独调试比较简单,只需要把apply plugin: 'com.android.library'切换成apply plugin: 'com.android.application'就可以,但是我们还需要修改一下AndroidManifest文件,因为一个单独调试需要有一个入口的actiivity。
我们可以设置一个变量isRunAlone,标记当前是否需要单独调试,根据isRunAlone的取值,使用不同的gradle插件和AndroidManifest文件,甚至可以添加Application等Java文件,以便可以做一下初始化的操作。
为了避免不同组件之间资源名重复,在每个组件的build.gradle中增加resourcePrefix "xxx_",从而固定每个组件的资源前缀。下面是读书组件的build.gradle的示例:通过这些额外的代码,我们给组件搭建了一个测试Host,从而让组件的代码运行在其中,所以我们可以再优化一下我们上面的框架图。2.3组件的数据传输
上面我们讲到,主项目和组件、组件与组件之间不能直接使用类的相互引用来进行数据交互。那么如何做到这个隔离呢?在这里我们采用接口+实现的结构。每个组件声明自己提供的服务Service,这些Service都是一些抽象类或者接口,组件负责将这些Service实现并注册到一个统一的路由Router中去。如果要使用某个组件的功能,只需要向Router请求这个Service的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。这与Binder的C/S架构很相像。
因为我们组件之间的数据传递都是基于接口编程的,接口和实现是完全分离的,所以组件之间就可以做到解耦,我们可以对组件进行替换、删除等动态管理。这里面有几个小问题需要明确:
● 组件怎么暴露自己提供的服务呢?在项目中我们简单起见,专门建立了一个componentservice的依赖库,里面定义了每个组件向外提供的service和一些公共model。将所有组件的service整合在一起,是为了在拆分初期操作更为简单,后面需要改为自动化的方式来生成。这个依赖库需要严格遵循开闭原则,以避免出现版本兼容等问题。
● service的具体实现是由所属组件注册到Router中的,那么是在什么时间注册的呢?这个就涉及到组件的加载等生命周期,我们在后面专门介绍。
● 一个很容易犯的小错误就是通过持久化的方式来传递数据,例如file、sharedpreference等方式,这个是需要避免的。
  下面就是加上数据传输功能之后的架构图:2.4 组件之间的UI跳转
可以说UI的跳转也是组件提供的一种特殊的服务,可以归属到上面的数据传递中去。不过一般UI的跳转我们会单独处理,一般通过短链的方式来跳转到具体的Activity。每个组件可以注册自己所能处理的短链的schme和host,并定义传输数据的格式。然后注册到统一的UIRouter中,UIRouter通过schme和host的匹配关系负责分发路由。
UI跳转部分的具体实现是通过在每个Activity上添加注解,然后通过apt形成具体的逻辑代码。这个也是目前Android中UI路由的主流实现方式。
2.5 组件的生命周期由于我们要动态的管理组件,所以给每个组件添加几个生命周期状态:加载、卸载和降维。为此我们给每个组件增加一个ApplicationLike类,里面定义了onCreate和onStop两个生命周期函数。
加载:上面讲了,每个组件负责将自己的服务实现注册到Router中,其具体的实现代码就写在onCreate方法中。那么主项目调用这个onCreate方法就称之为组件的加载,因为一旦onCreate方法执行完,组件就把自己的服务注册到Router里面去了,其他组件就可以直接使用这个服务了。
卸载:卸载与加载基本一致,所不同的就是调用ApplicationLike的onStop方法,在这个方法中每个组件将自己的服务实现从Router中取消注册。不过这种使用场景可能比较少,一般适用于一些只用一次的组件。
降维:降维使用的场景更为少见,比如一个组件出现了问题,我们想把这个组件从本地实现改为一个wap页。降维一般需要后台配置才生效,可以在onCreate对线上配置进行检查,如果需要降维,则把所有的UI跳转到配置的wap页上面去。
一个小的细节是,主项目负责加载组件,由于主项目和组件之间是隔离的,那么主项目如何调用组件ApplicationLike的生命周期方法呢,目前我们采用的是基于编译期字节码插入的方式,扫描所有的ApplicationLike类(其有一个共同的父类),然后通过javassisit在主项目的onCreate中插入调用ApplicationLike.onCreate的代码。
我们再优化一下组件化的架构图:2.6 集成调试
每个组件单独调试通过并不意味着集成在一起没有问题,因此在开发后期我们需要把几个组件机集成到一个app里面去验证。由于我们上面的机制保证了组件之间的隔离,所以我们可以任意选择几个组件参与集成。这种按需索取的加载机制可以保证在集成调试中有很大的灵活性,并且可以加大的加快编译速度。
我们的做法是这样的,每个组件开发完成之后,发布一个relaese的aar到一个公共仓库,一般是本地的maven库。然后主项目通过参数配置要集成的组件就可以了。所以我们再稍微改动一下组件与主项目之间的连接线,形成的最终组件化架构图如下:2.7 代码隔离
此时在回顾我们在刚开始拆分组件化是提出的三个问题,应该说都找到了解决方式,但是还有一个隐患没有解决,那就是我们可以使用compile project(xxx:reader.aar)来引入组件吗?虽然我们在数据传输章节使用了接口+实现的架构,组件之间必须针对接口编程,但是一旦我们引入了reader.aar,那我们就完全可以直接使用到其中的实现类啊,这样我们针对接口编程的规范就成了一纸空文。千里之堤毁于蚁穴,只要有代码(不论是有意还是无意)是这么做了,我们前面的工作就白费了。
我们希望只在assembleDebug或者assembleRelease的时候把aar引入进来,而在开发阶段,所有组件都是看不到的,这样就从根本上杜绝了引用实现类的问题。我们把这个问题交给gradle来解决,我们创建一个gradle插件,然后每个组件都apply这个插件,插件的配置代码也比较简单:3组件化的拆分步骤和动态需求 3.1 拆分原则
组件化的拆分是个庞大的工程,特别是从几十万行代码的大工程拆分出去,所要考虑的事情千头万绪。为此我觉得可以分成三步:
从产品需求到开发阶段再到运营阶段都有清晰边界的功能开始拆分,比如读书模块、直播模块等,这些开始分批先拆分出去
在拆分中,造成组件依赖主项目的依赖的模块继续拆出去,比如账户体系等
最终主项目就是一个Host,包含很小的功能模块(比如启动图)以及组件之间的拼接逻辑
3.2 组件化的动态需求最开始我们讲到,理想的代码组织形式是插件化的方式,届时就具备了完备的运行时动态化。在向插件化迁徙的过程中,我们可以通过下面的集中方式来实现编译速度的提升和动态更新。
在快速编译上,采用组件级别的增量编译。在抽离组件之前可以使用代码级别的增量编译工具如freeline(但databinding支持较差)、fastdex等
动态更新方面,暂时不支持新增组件等大的功能改进。可以临时采用方法级别的热修复或者功能级别的Tinker等工具,Tinker的接入成本较高。
4总结 本文是笔者在设计“得到app”的组件化中总结一些想法,在设计之初参考了目前已有的组件化和插件化方案,站在巨人的肩膀上又加了一点自己的想法,主要是组件化生命周期以及完全的代码隔离方面。特别是最后的代码隔离,不仅要有规范上的约束(针对接口编程),更要有机制保证开发者不犯错,我觉得只有做到这一点才能认为是一个彻底的组件化方案。
相关推荐 点赞与转发就是对我最大的支持!
我要该,理由是:
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)26766人阅读
Android开发(8)
版权声明:本文为博主原创文章,欢迎大家转载!
转载请标明出处:
,本文出自:【张华洋的博客】
Android组件化项目地址:
Android组件化之终极方案地址:
1、为什么要项目组件化
随着APP版本不断的迭代,新功能的不断增加,业务也会变的越来越复杂,APP业务模块的数量有可能还会继续增加,而且每个模块的代码也变的越来越多,这样发展下去单一工程下的APP架构势必会影响开发效率,增加项目的维护成本,每个工程师都要熟悉如此之多的代码,将很难进行多人协作开发,而且Android项目在编译代码的时候电脑会非常卡,又因为单一工程下代码耦合严重,每修改一处代码后都要重新编译打包测试,导致非常耗时,最重要的是这样的代码想要做单元测试根本无从下手,所以必须要有更灵活的架构代替过去单一的工程架构。
上图是目前比较普遍使用的Android APP技术架构,往往是在一个界面中存在大量的业务逻辑,而业务逻辑中充斥着各种网络请求、数据操作等行为,整个项目中也没有模块的概念,只有简单的以业务逻辑划分的文件夹,并且业务之间也是直接相互调用、高度耦合在一起的;
上图单一工程模型下的业务关系,总的来说就是:你中有我,我中有你,相互依赖,无法分离。
然而随着产品的迭代,业务越来越复杂,随之带来的是项目结构复杂度的极度增加,此时我们会面临如下几个问题:
1、实际业务变化非常快,但是单一工程的业务模块耦合度太高,牵一发而动全身;
2、对工程所做的任何修改都必须要编译整个工程;
3、功能测试和系统测试每次都要进行;
4、团队协同开发存在较多的冲突.不得不花费更多的时间去沟通和协调,并且在开发过程中,任何一位成员没办法专注于自己的功能点,影响开发效率;
5、不能灵活的对业务模块进行配置和组装;
为了满足各个业务模块的迭代而彼此不受影响,更好的解决上面这种让人头疼的依赖关系,就需要整改App的架构。
2、如何组件化
上图是组件化工程模型,为了方便理解这张架构图,下面会列举一些组件化工程中用到的名词的含义:
所有的业务组件被“app壳工程”依赖,组成一个完整的APP;
可以独立开发业务组件,每一个业务组件就是一个APP;
负责管理各个业务组件,和打包apk,没有具体的业务功能;
根据公司具体业务而独立形成一个的工程;
提供开发APP的某些基础功能,例如打印日志、树状图等;
属于业务组件,指定APP启动页面、主界面;
Common组件
属于功能组件,支撑业务组件的基础,提供多数业务组件需要的功能,例如提供网络请求功能;
Android APP组件化架构的目标是告别结构臃肿,让各个业务变得相对独立,业务组件在组件模式下可以独立开发,而在集成模式下又可以变为arr包集成到“app壳工程”中,组成一个完整功能的APP;
从组件化工程模型中可以看到,业务组件之间是独立的,没有关联的,这些业务组件在集成模式下是一个个library,被app壳工程所依赖,组成一个具有完整业务功能的APP应用,但是在组件开发模式下,业务组件又变成了一个个application,它们可以独立开发和调试,由于在组件开发模式下,业务组件们的代码量相比于完整的项目差了很远,因此在运行时可以显著减少编译时间。
这是组件化工程模型下的业务关系,业务之间将不再直接引用和依赖,而是通过“路由”这样一个中转站间接产生联系,而Android中的路由实际就是对URL Scheme的封装;
如此规模大的架构整改需要付出更高的成本,还会涉及一些潜在的风险,但是整改后的架构能够带来很多好处:
1、加快业务迭代速度,各个业务模块组件更加独立,不再出现业务耦合情况;
2、稳定的公共模块采用依赖库方式,提供给各个业务线使用,减少重复开发和维护工作量;
3、迭代频繁的业务模块采用组件方式,各业务研发可以互不干扰、提升协作效率,并控制产品质量;
4、为新业务随时集成提供了基础,所有业务可上可下,灵活多变;
5、降低团队成员熟悉项目的成本,降低项目的维护难度;
6、加快编译速度,提高开发效率;
7、控制代码权限,将代码的权限细分到更小的粒度;
3、组件化实施流程
1)组件模式和集成模式的转换
Android Studio中的Module主要有两种属性,分别为:
1、application属性,可以独立运行的Android程序,也就是我们的APP;
apply plugin: ‘com.android.application’
2、library属性,不可以独立运行,一般是Android程序依赖的库文件;
apply plugin: ‘com.android.library’
Module的属性是在每个组件的 build.gradle 文件中配置的,当我们在组件模式开发时,业务组件应处于application属性,这时的业务组件就是一个 Android App,可以独立开发和调试;而当我们转换到集成模式开发时,业务组件应该处于 library 属性,这样才能被我们的“app壳工程”所依赖,组成一个具有完整功能的APP;
但是我们如何让组件在这两种模式之间自动转换呢?总不能每次需要转换模式的时候去每个业务组件的 Gralde 文件中去手动把 Application 改成 library 吧?如果我们的项目只有两三个组件那么这个办法肯定是可行的,手动去改一遍也用不了多久,但是在大型项目中我们可能会有十几个业务组件,再去手动改一遍必定费时费力,这时候就需要程序员发挥下懒的本质了。
试想,我们经常在写代码的时候定义静态常量,那么定义静态常量的目的什么呢?当一个常量需要被好几处代码引用的时候,把这个常量定义为静态常量的好处是当这个常量的值需要改变时我们只需要改变静态常量的值,其他引用了这个静态常量的地方都会被改变,做到了一次改变,到处生效;根据这个思想,那么我们就可以在我们的代码中的某处定义一个决定业务组件属性的常量,然后让所有业务组件的build.gradle都引用这个常量,这样当我们改变了常量值的时候,所有引用了这个常量值的业务组件就会根据值的变化改变自己的属性;可是问题来了?静态常量是用Java代码定义的,而改变组件属性是需要在Gradle中定义的,Gradle能做到吗?
Gradle自动构建工具有一个重要属性,可以帮助我们完成这个事情。每当我们用AndroidStudio创建一个Android项目后,就会在项目的根目录中生成一个文件 gradle.properties,我们将使用这个文件的一个重要属性:在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来;那么我们在上面提到解决办法就有了实际行动的方法,首先我们在gradle.properties中定义一个常量值 isModule(是否是组件开发模式,true为是,false为否):
# 每次更改“isModule”的值后,需要点击 "Sync Project" 按钮
isModule=false
然后我们在业务组件的build.gradle中读取 isModule,但是
gradle.properties
还有一个重要属性: gradle.properties
中的数据类型都是String类型,使用其他数据类型需要自行转换;也就是说我们读到
是个String类型的值,而我们需要的是Boolean值,代码如下:
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
apply plugin: 'com.android.library'
这样我们第一个问题就解决了,当然了 每次改变isModule的值后,都要同步项目才能生效;
2)组件之间AndroidManifest合并问题
在 AndroidStudio 中每一个组件都会有对应的
AndroidManifest.xml,用于声明需要的权限、Application、Activity、Service、Broadcast等,当项目处于组件模式时,业务组件的
AndroidManifest.xml 应该具有一个 Android APP 所具有的的所有属性,尤其是声明 Application 和要
launch的Activity,但是当项目处于集成模式的时候,每一个业务组件的
AndroidManifest.xml
都要合并到“app壳工程”中,要是每一个业务组件都有自己的
Application 和
launch的Activity,那么合并的时候肯定会冲突,试想一个APP怎么可能会有多个 Application 和
launch 的Activity呢?
但是大家应该注意到这个问题是在组件开发模式和集成开发模式之间转换引起的问题,而在上一节中我们已经解决了组件模式和集成模式转换的问题,另外大家应该都经历过将 Android 项目从 Eclipse 切换到 AndroidStudio 的过程,由于 Android 项目在 Eclipse 和 AndroidStudio开发时 AndroidManifest.xml 文件的位置是不一样的,我们需要在build.gradle
AndroidManifest.xml 的位置,AndroidStudio 才能读取到 AndroidManifest.xml,这样解决办法也就有了,我们可以为组件开发模式下的业务组件再创建一个 AndroidManifest.xml,然后根据isModule指定AndroidManifest.xml的文件路径,让业务组件在集成模式和组件模式下使用不同的AndroidManifest.xml,这样表单冲突的问题就可以规避了。
上图是组件化项目中一个标准的业务组件目录结构,首先我们在main文件夹下创建一个module文件夹用于存放组件开发模式下业务组件的
AndroidManifest.xml,而
AndroidStudio
AndroidManifest.xml
则依然保留,并用于集成开发模式下业务组件的表单;然后我们需要在业务组件的
build.gradle
中指定表单的路径,代码如下:
sourceSets {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
manifest.srcFile 'src/main/AndroidManifest.xml'
这样在不同的开发模式下就会读取到不同的 AndroidManifest.xml ,然后我们需要修改这两个表单的内容以为我们不同的开发模式服务。
首先是集成开发模式下的 AndroidManifest.xml,前面我们说过集成模式下,业务组件的表单是绝对不能拥有自己的 Application 和 launch 的 Activity的,也不能声明APP名称、图标等属性,总之app壳工程有的属性,业务组件都不能有,下面是一份标准的集成开发模式下业务组件的
AndroidManifest.xml:
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guiying.girls"&
android:theme="@style/AppTheme"&
android:name=".main.GirlsActivity"
android:screenOrientation="portrait" /&
android:name=".girl.GirlActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar" /&
我在这个表单中只声明了应用的主题,而且这个主题还是跟app壳工程中的主题是一致的,都引用了common组件中的资源文件,在这里声明主题是为了方便这个业务组件中有使用默认主题的Activity时就不用再给Activity单独声明theme了。
然后是组件开发模式下的表单文件:
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guiying.girls"&
android:name="debug.GirlsApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/girls_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"&
android:name=".main.GirlsActivity"
android:screenOrientation="portrait"&
android:name="android.intent.action.MAIN" /&
android:name="android.intent.category.LAUNCHER" /&
android:name=".girl.GirlActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar" /&
组件模式下的业务组件表单就是一个Android项目普通的AndroidManifest.xml,这里就不在过多介绍了。
3)全局Context的获取及组件数据初始化
当Android程序启动时,Android系统会为每个程序创建一个 Application 类的对象,并且只创建一个,application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。在默认情况下应用系统会自动生成
Application 对象,但是如果我们自定义了 Application,那就需要在
AndroidManifest.xml
中声明告知系统,实例化的时候,是实例化我们自定义的,而非默认的。
但是我们在组件化开发的时候,可能为了数据的问题每一个组件都会自定义一个Application类,如果我们在自己的组件中开发时需要获取 全局的Context,一般都会直接获取
application 对象,但是当所有组件要打包合并在一起的时候就会出现问题,因为最后程序只有一个 Application,我们组件中自己定义的 Application 肯定是没法使用的,因此我们需要想办法再任何一个业务组件中都能获取到全局的 Context,而且这个 Context 不管是在组件开发模式还是在集成开发模式都是生效的。
在 组件化工程模型图中,功能组件集合中有一个 Common 组件, Common 有公共、公用、共同的意思,所以这个组件中主要封装了项目中需要的基础功能,并且每一个业务组件都要依赖Common组件,Common 组件就像是万丈高楼的地基,而业务组件就是在 Common 组件这个地基上搭建起来我们的APP的,Common 组件会专门在一个章节中讲解,这里只讲 Common组件中的一个功能,在Common组件中我们封装了项目中用到的各种Base类,这些基类中就有BaseApplication 类。
BaseApplication
主要用于各个业务组件和app壳工程中声明的
Application
类继承用的,只要各个业务组件和app壳工程中声明的Application类继承了
BaseApplication,当应用启动时
BaseApplication
就会被动实例化,这样从
BaseApplication
就会生效,也就从根本上解决了我们不能直接从各个组件获取全局
这时候大家肯定都会有个疑问?不是说了业务组件不能有自己的
Application 吗,怎么还让他们继承
BaseApplication
呢?其实我前面说的是业务组件不能在集成模式下拥有自己的
Application,但是这不代表业务组件也不能在组件开发模式下拥有自己的Application,其实业务组件在组件开发模式下必须要有自己的
Application
类,一方面是为了让
BaseApplication
被实例化从而获取
Context,还有一个作用是,业务组件自己的
Application
可以在组件开发模式下初始化一些数据,例如在组件开发模式下,A组件没有登录页面也没法登录,因此就无法获取到 Token,这样请求网络就无法成功,因此我们需要在A组件这个
启动后就应该已经登录了,这时候组件自己的
Application
类就有了用武之地,我们在组件的 Application的
方法中模拟一个登陆接口,在登陆成功后将数据保存到本地,这样就可以处理A组件中的数据业务了;另外我们也可以在组件Application中初始化一些第三方库。
但是,实际上业务组件中的Application在最终的集成项目中是没有什么实际作用的,组件自己的
Application
仅限于在组件模式下发挥功能,因此我们需要在将项目从组件模式转换到集成模式后将组件自己的Application剔除出我们的项目;在
AndroidManifest
合并问题小节中介绍了如何在不同开发模式下让
识别组件表单的路径,这个方法也同样适用于Java代码;
我们在Java文件夹下创建一个
文件夹,用于存放不会在业务组件中引用的类,例如上图中的
NewsApplication
,你甚至可以在
文件夹中创建一个Activity,然后组件表单中声明启动这个Activity,在这个Activity中不用setContentView,只需要在启动你的目标Activity的时候传递参数就行,这样就就可以解决组件模式下某些Activity需要getIntent数据而没有办法拿到的情况,代码如下;
public class LauncherActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
request();
Intent intent = new Intent(this, TargetActivity.class);
intent.putExtra("name", "avcd");
intent.putExtra("syscode", "023e2e12ed");
startActivity(intent);
private void request() {
AndPermission.with(this)
.requestCode(110)
.permission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA, Manifest.permission.READ_PHONE_STATE)
.callback(this)
接下来在业务组件的
build.gradle
是否是集成模式将
Java代码文件夹排除:
sourceSets {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
manifest.srcFile 'src/main/AndroidManifest.xml'
exclude 'debug/**'
4)library依赖问题
在介绍这一节的时候,先说一个问题,在组件化工程模型图中,多媒体组件和Common组件都依赖了日志组件,而A业务组件有同时依赖了多媒体组件和Common组件,这时候就会有人问,你这样搞岂不是日志组件要被重复依赖了,而且Common组件也被每一个业务组件依赖了,这样不出问题吗?
其实大家完全没有必要担心这个问题,如果真有重复依赖的问题,在你编译打包的时候就会报错,如果你还是不相信的话可以反编译下最后打包出来的APP,看看里面的代码你就知道了。组件只是我们在代码开发阶段中为了方便叫的一个术语,在组件被打包进APP的时候是没有这个概念的,这些组件最后都会被打包成arr包,然后被app壳工程所依赖,在构建APP的过程中Gradle会自动将重复的arr包排除,APP中也就不会存在相同的代码了;
但是虽然组件是不会重复了,但是我们还是要考虑另一个情况,我们在build.gradle中compile的第三方库,例如AndroidSupport库经常会被一些开源的控件所依赖,而我们自己一定也会compile AndroidSupport库 ,这就会造成第三方包和我们自己的包存在重复加载,解决办法就是找出那个多出来的库,并将多出来的库给排除掉,而且Gradle也是支持这样做的,分别有两种方式:根据组件名排除或者根据包名排除,下面以排除support-v4库为例:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
exclude module: 'support-v4'
exclude group: 'android.support.v4'
library重复依赖的问题算是都解决了,但是我们在开发项目的时候会依赖很多开源库,而这些库每个组件都需要用到,要是每个组件都去依赖一遍也是很麻烦的,尤其是给这些库升级的时候,为了方便我们统一管理第三方库,我们将给给整个工程提供统一的依赖第三方库的入口,前面介绍的Common库的作用之一就是统一依赖开源库,因为其他业务组件都依赖了Common库,所以这些业务组件也就间接依赖了Common所依赖的开源库。
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
compile "com.android.support:design:$rootProject.supportLibraryVersion"
compile "com.android.support:percent:$rootProject.supportLibraryVersion"
compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
compile "com.orhanobut:logger:$rootProject.loggerVersion"
compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
compile "com.google.code.gson:gson:$rootProject.gsonVersion"
compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"
compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"
compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
5)组件之间调用和通信
在组件化开发的时候,组件之间是没有依赖关系,我们不能在使用显示调用来跳转页面了,因为我们组件化的目的之一就是解决模块间的强依赖问题,假如现在要从A业务组件跳转到业务B组件,并且要携带参数跳转,这时候怎么办呢?而且组件这么多怎么管理也是个问题,这时候就需要引入“路由”的概念了,由本文开始的组件化模型下的业务关系图可知路由就是起到一个转发的作用。
这里我将介绍开源库的“ActivityRouter” ,有兴趣的同学情直接去ActivityRouter的Github主页学习:,ActivityRouter支持给Activity定义 URL,这样就可以通过 URL 跳转到Activity,并且支持从浏览器以及 APP 中跳入我们的Activity,而且还支持通过 url 调用方法。下面将介绍如何将ActivityRouter集成到组件化项目中以实现组件之间的调用;
1、首先我们需要在
build.gradle
将ActivityRouter 依赖进来,方便我们在业务组件中调用:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
2、这一步我们需要先了解 APT这个概念,APT(Annotation Processing Tool)是一种处理注解的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。在这里我们将在每一个业务组件的
build.gradle
都引入ActivityRouter 的 Annotation处理器,我们将会在声明组件和Url的时候使用,annotationProcessor是Android官方提供的Annotation处理器插件,代码如下:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
3、接下来需要在 app壳工程的
AndroidManifest.xml
配置,到这里ActivityRouter配置就算完成了:
android:name="com.github.mzule.activityrouter.router.RouterActivity"
android:theme="@android:style/Theme.NoDisplay"&
android:name="android.intent.action.VIEW" /&
android:name="android.intent.category.DEFAULT" /&
android:name="android.intent.category.BROWSABLE" /&
android:scheme="@string/global_scheme" /&
4、接下来我们将声明项目中的业务组件,声明方法如下:
@Module("girls")
public class Girls {
在每一个业务组件的java文件的根目录下创建一个类,用 注解@Module 声明这个业务组件;
然后在“app壳工程”的
应用Application
中使用 注解@Modules 管理我们声明的所有业务组件,方法如下:
@Modules({"main", "girls", "news"})
public class MyApplication extends BaseApplication {
到这里组件化项目中的所有业务组件就被声明和管理起来了,组件之间的也就可以互相调用了,当然前提是要给业务组件中的Activity定义 URL。
5、例如我们给
GirlsActivity
注解@Router 定义一个
URL:“news”,方法如下:
@Router("girls")
public class GirlsActivity extends BaseActionBarActivity {
private GirlsView mV
private GirlsContract.Presenter mP
protected int setTitleId() {
return R.string.girls_activity_
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mView = new GirlsView(this);
setContentView(mView);
mPresenter = new GirlsPresenter(mView);
mPresenter.start();
然后我们就可以在项目中的任何一个地方通过
URL地址 :
module://girls,
GirlsActivity,方法如下:
Routers.open(MainActivity.this, "module://girls");
组件之间的调用解决后,另外需要解决的就是组件之间的通信,例如A业务组件中有消息列表,而用户在B组件中操作某个事件后会产生一条新消息,需要通知A组件刷新消息列表,这样业务场景需求可以使用Android广播来解决,也可以使用第三方的事件总线来实现,比如EventBus。
6)组件之间资源名冲突
因为我们拆分出了很多业务组件和功能组件,在把这些组件合并到“app壳工程”时候就有可能会出现资源名冲突问题,例如A组件和B组件都有一张叫做“ic_back”的图标,这时候在集成模式下打包APP就会编译出错,解决这个问题最简单的办法就是在项目中约定资源文件命名规约,比如强制使每个资源文件的名称以组件名开始,这个可以根据实际情况和开发人员制定规则。当然了万能的Gradle构建工具也提供了解决方法,通过在在组件的build.gradle中添加如下的代码:
resourcePrefix "girls_"
但是设置了这个属性后有个问题,所有的资源名必须以指定的字符串做前缀,否则会报错,而且resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名;所以我并不推荐使用这种方法来解决资源名冲突。
4、组件化项目的工程类型
在组件化工程模型中主要有:app壳工程、业务组件和功能组件3种类型,而业务组件中的Main组件和功能组件中的Common组件比较特殊,下面将分别介绍。
1)app壳工程
app壳工程是从名称来解释就是一个空壳工程,没有任何的业务代码,也不能有Activity,但它又必须被单独划分成一个组件,而不能融合到其他组件中,是因为它有如下几点重要功能:
1、app壳工程中声明了我们Android应用的 Application,这个
Application
必须继承自
Common组件中的
BaseApplication(如果你无需实现自己的Application可以直接在表单声明BaseApplication),因为只有这样,在打包应用后才能让BaseApplication中的Context生效,当然你还可以在这个
Application中初始化我们工程中使用到的库文件,还可以在这里解决Android引用方法数不能超过
的限制,对崩溃事件的捕获和发送也可以在这里声明。
2、app壳工程的
AndroidManifest.xml
是我Android应用的根表单,应用的名称、图标以及是否支持备份等等属性都是在这份表单中配置的,其他组件中的表单最终在集成开发模式下都被合并到这份
AndroidManifest.xml
3、app壳工程的
build.gradle
是比较特殊的,app壳不管是在集成开发模式还是组件开发模式,它的属性始终都是:com.android.application,因为最终其他的组件都要被app壳工程所依赖,被打包进app壳工程中,这一点从组件化工程模型图中就能体现出来,所以app壳工程是不需要单独调试单独开发的。另外Android应用的打包签名,以及buildTypes和defaultConfig都需要在这里配置,而它的dependencies则需要根据isModule的值分别依赖不同的组件,在组件开发模式下app壳工程只需要依赖Common组件,或者为了防止报错也可以根据实际情况依赖其他功能组件,而在集成模式下app壳工程必须依赖所有在应用Application中声明的业务组件,并且不需要再依赖任何功能组件。
下面是一份 app壳工程 的 build.gradle文件:
apply plugin: 'com.android.application'
static def buildTime() {
return new Date().format("yyyyMMdd");
signingConfigs {
keyAlias 'guiying712'
keyPassword 'guiying712'
storeFile file('/mykey.jks')
storePassword 'guiying712'
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.guiying.androidmodulepattern"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
multiDexEnabled false
resValue "string", "build_time", buildTime()
buildTypes {
buildConfigField "boolean", "LEO_DEBUG", "false"
zipAlignEnabled true
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
applicationIdSuffix ".debug"
buildConfigField "boolean", "LOG_DEBUG", "true"
zipAlignEnabled false
shrinkResources false
minifyEnabled false
debuggable true
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
if (isModule.toBoolean()) {
compile project(':lib_common')
compile project(':module_main')
compile project(':module_girls')
compile project(':module_news')
2)功能组件和Common组件
功能组件是为了支撑业务组件的某些功能而独立划分出来的组件,功能实质上跟项目中引入的第三方库是一样的,功能组件的特征如下:
1、功能组件的
AndroidManifest.xml
是一张空表,这张表中只有功能组件的包名;
2、功能组件不管是在集成开发模式下还是组件开发模式下属性始终是:
com.android.library,所以功能组件是不需要读取
gradle.properties
值的;另外功能组件的
build.gradle
也无需设置
buildTypes
dependencies
这个功能组件需要的jar包和开源库。
下面是一份 普通 的功能组件的 build.gradle文件:
apply plugin: 'com.android.library'
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
Common组件除了有功能组件的普遍属性外,还具有其他功能:
1、Common组件的
AndroidManifest.xml
不是一张空表,这张表中声明了我们
Android应用用到的所有使用权限
uses-permission
uses-feature,放到这里是因为在组件开发模式下,所有业务组件就无需在自己的
AndroidManifest.xm
声明自己要用到的权限了。
2、Common组件的
build.gradle
需要统一依赖业务组件中用到的 第三方依赖库和jar包,例如我们用到的ActivityRouter、Okhttp等等。
3、Common组件中封装了Android应用的
Base类和网络请求工具、图片加载工具等等,公用的
widget控件也应该放在Common 组件中;业务组件中都用到的数据也应放于Common组件中,例如保存到 SharedPreferences
和 DataBase 中的登陆数据;
4、Common组件的资源文件中需要放置项目公用的 Drawable、layout、sting、dimen、color和style 等等,另外项目中的 Activity 主题必须定义在
Common中,方便和
BaseActivity
配合保持整个Android应用的界面风格统一。
下面是一份 Common功能组件的 build.gradle文件:
apply plugin: 'com.android.library'
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
compile "com.android.support:design:$rootProject.supportLibraryVersion"
compile "com.android.support:percent:$rootProject.supportLibraryVersion"
compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
compile "com.orhanobut:logger:$rootProject.loggerVersion"
compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
compile "com.google.code.gson:gson:$rootProject.gsonVersion"
compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"
compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"
compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
2)业务组件和Main组件
业务组件就是根据业务逻辑的不同拆分出来的组件,业务组件的特征如下:
1、业务组件中要有两张AndroidManifest.xml,分别对应组件开发模式和集成开发模式,这两张表的区别请查看 组件之间AndroidManifest合并问题 小节。
2、业务组件在集成模式下是不能有自己的Application的,但在组件开发模式下又必须实现自己的Application并且要继承自Common组件的BaseApplication,并且这个Application不能被业务组件中的代码引用,因为它的功能就是为了使业务组件从BaseApplication中获取的全局Context生效,还有初始化数据之用。
3、业务组件有debug文件夹,这个文件夹在集成模式下会从业务组件的代码中排除掉,所以debug文件夹中的类不能被业务组件强引用,例如组件模式下的
Application
就是置于这个文件夹中,还有组件模式下开发给目标 Activity
传递参数的用的
launch Activity
也应该置于
文件夹中;
4、业务组件必须在自己的 Java文件夹中创建业务组件声明类,以使
中的 应用Application能够引用,实现组件跳转,具体请查看 组件之间调用和通信 小节;
5、业务组件必须在自己的
build.gradle
值的不同改变自己的属性,在组件模式下是:com.android.application,而在集成模式下com.android.library;同时还需要在build.gradle配置资源文件,如 指定不同开发模式下的AndroidManifest.xml文件路径,排除debug文件夹等;业务组件还必须在dependencies中依赖Common组件,并且引入ActivityRouter的注解处理器annotationProcessor,以及依赖其他用到的功能组件。
下面是一份普通业务组件的 build.gradle文件:
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
apply plugin: 'com.android.library'
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
sourceSets {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
manifest.srcFile 'src/main/AndroidManifest.xml'
exclude 'debug/**'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
compile project(':lib_common')
Main组件除了有业务组件的普遍属性外,还有一项重要功能:
1、Main组件集成模式下的AndroidManifest.xml是跟其他业务组件不一样的,Main组件的表单中声明了我们整个Android应用的launch Activity,这就是Main组件的独特之处;所以我建议SplashActivity、登陆Activity以及主界面都应属于Main组件,也就是说Android应用启动后要调用的页面应置于Main组件。
android:name=".splash.SplashActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme"&
&intent-filter&
&action android:name="android.intent.action.MAIN" /&
&category android:name="android.intent.category.LAUNCHER" /&
&/intent-filter&
&/activity&
5、组件化项目的混淆方案
组件化项目的Java代码混淆方案采用在集成模式下集中在app壳工程中混淆,各个业务组件不配置混淆文件。集成开发模式下在app壳工程中build.gradle文件的release构建类型中开启混淆属性,其他buildTypes配置方案跟普通项目保持一致,Java混淆配置文件也放置在app壳工程中,各个业务组件的混淆配置规则都应该在app壳工程中的混淆配置文件中添加和修改。
之所以不采用在每个业务组件中开启混淆的方案,是因为 组件在集成模式下都被
类型的arr包,一旦业务组件的代码被混淆,而这时候代码中又出现了bug,将很难根据日志找出导致bug的原因;另外每个业务组件中都保留一份混淆配置文件非常不便于修改和管理,这也是不推荐在业务组件的
build.gradle
文件中配置
buildTypes (构建类型)的原因。
6、工程的build.gradle和gradle.properties文件
1)组件化工程的build.gradle文件
在组件化项目中因为每个组件的
build.gradle
都需要配置
compileSdkVersion、buildToolsVersion和defaultConfig
等的版本号,而且每个组件都需要用到
annotationProcessor,为了能够使组件化项目中的所有组件的
build.gradle
中的这些配置都能保持统一,并且也是为了方便修改版本号,我们统一在Android工程根目录下的build.gradle中定义这些版本号,当然为了方便管理Common组件中的第三方开源库的版本号,最好也在这里定义这些开源库的版本号,然后在各个组件的build.gradle中引用Android工程根目录下的build.gradle定义的版本号,组件化工程的
build.gradle
文件代码如下:
buildscript {
repositories {
mavenCentral()
dependencies {
classpath "com.android.tools.build:gradle:$localGradlePluginVersion"
allprojects {
repositories {
mavenCentral()
maven { url "https://jitpack.io" }
dirs 'libs'
task clean(type: Delete) {
delete rootProject.buildDir
buildToolsVersion = localBuildToolsVersion
compileSdkVersion = 25
minSdkVersion = 16
targetSdkVersion = 25
versionCode = 1
versionName = "1.0"
javaVersion = JavaVersion.VERSION_1_8
supportLibraryVersion = "25.3.1"
retrofitVersion = "2.1.0"
glideVersion = "3.7.0"
loggerVersion = "1.15"
eventbusVersion = "3.0.0"
gsonVersion = "2.8.0"
photoViewVersion = "2.0.0"
annotationProcessor = "1.1.7"
routerVersion = "1.2.2"
easyRecyclerVersion = "4.4.0"
cookieVersion = "v1.0.1"
toastyVersion = "1.1.3"
2)组件化工程的gradle.properties文件
在组件化实施流程中我们了解到gradle.properties有两个属性对我们非常有用:
1、在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来,不管这个build.gradle是组件的还是整个项目工程的build.gradle;
2、gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换;
利用gradle.properties的属性不仅可以解决集成开发模式和组件开发模式的转换,而且还可以解决在多人协同开发Android项目的时候,因为开发团队成员的Android开发环境(开发环境指Android SDK和AndroidStudio)不一致而导致频繁改变线上项目的build.gradle配置。
在每个Android组件的 build.gradle 中有一个属性:buildToolsVersion,表示构建工具的版本号,这个属性值对应 AndroidSDK 中的 Android SDK Build-tools,正常情况下 build.gradle 中的 buildToolsVersion 跟你电脑中 Android SDK Build-tools 的最新版本是一致的,比如现在 Android SDK Build-tools 的最新的版本是:25.0.3,那么我的Android项目中 build.gradle 中的 buildToolsVersion 版本号也是 25.0.3,但是一旦一个Android项目是由好几个人同时开发,总会出现每个人的开发环境 Android SDK Build-tools 是都是不一样的,并不是所有人都会经常升级更新 Android SDK,而且代码是保存到线上环境的(例如使用 SVN/Git 等工具),某个开发人员提交代码后线上Android项目中 build.gradle 中的 buildToolsVersion 也会被不断地改变。
另外一个原因是因为Android工程的根目录下的 build.gradle 声明了 Android Gradle
构建工具,而这个工具也是有版本号的,而且 Gradle Build Tools 的版本号跟 AndroidStudio 版本号一致的,但是有些开发人员基本很久都不会升级自己的 AndroidStudio 版本,导致团队中每个开发人员的 Gradle Build Tools 的版本号也不一致。
如果每次同步代码后这两个工具的版本号被改变了,开发人员可以自己手动改回来,并且不要把改动工具版本号的代码提交到线上环境,这样还可以勉强继续开发;但是很多公司都会使用持续集成工具(例如Jenkins)用于持续的软件版本发布,而Android出包是需要 Android SDK Build-tools 和 Gradle Build Tools 配合的,一旦提交到线上的版本跟持续集成工具所依赖的Android环境构建工具版本号不一致就会导致Android打包失败。
为了解决上面问题就必须将Android项目中
build.gradle
buildToolsVersion
GradleBuildTools
版本号从线上代码隔离出来,保证线上代码的
buildToolsVersion
Gradle Build Tools
版本号不会被人为改变。
具体的实施流程大家可以查看我的这篇博文:
7、组件化项目Router的其他方案-ARouter
在组件化项目中使用到的跨组件跳转库ActivityRouter可以使用阿里巴巴的开源路由项目:;
ActivityRouter和ARouter的接入组件化项目的方式是一样的,ActivityRouter提供的功能目前ARouter也全部支持,但是ARouter还支持依赖注入解耦,页面、拦截器、服务等组件均会自动注册到框架。对于大家来说,没有最好的只有最适合的,大家可以根据自己的项目选择合适的Router。
下面将介绍ARouter的基础使用方法,更多功能还需大家去Github自己学习;
1、首先 ARouter 这个框架是需要初始化SDK的,所以你需要在“app壳工程”中的应用Application中加入下面的代码,注意:在 debug 模式下一定要 openDebug:
if (BuildConfig.DEBUG) {
ARouter.openDebug();
ARouter.openLog();
ARouter.init(this);
2、首先我们依然需要在
build.gradle
将ARouter 依赖进来,方便我们在业务组件中调用:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.alibaba:arouter-api:1.2.1.1'
3、然后在每一个业务组件的
build.gradle
都引入ARouter 的 Annotation处理器,代码如下:
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor 'com.alibaba:arouter-compiler:1.0.3'
4、由于ARouter支持自动注册到框架,所以我们不用像ActivityRouter那样在各个组件中声明组件,当然更不需要在Application中管理组件了。
GirlsActivity
添加注解:@Route(path = “/girls/list”),需要注意的是这里的路径至少需要有两级,/xx/xx,之所以这样是因为ARouter使用了路径中第一段字符串(/*/)作为分组,比如像上面的”girls”,而分组这个概念就有点类似于ActivityRouter中的组件声明 @Module ,代码如下:
@Route(path = "/girls/list")
public class GirlsActivity extends BaseActionBarActivity {
private GirlsView mV
private GirlsContract.Presenter mP
protected int setTitleId() {
return R.string.girls_activity_
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mView = new GirlsView(this);
setContentView(mView);
mPresenter = new GirlsPresenter(mView);
mPresenter.start();
然后我们就可以在项目中的任何一个地方通过
URL地址 :
/girls/list,
GirlsActivity,方法如下:
ARouter.getInstance().build("/girls/list").navigation();
组件化相比于单一工程优势是显而易见的:
组件模式下可以加快编译速度,提高开发效率;
自由选择开发框架(MVC /MVP / MVVM /);
方便做单元测试;
代码架构更加清晰,降低项目的维护难度;
适合于团队开发;
最后贴出Android组件化Demo地址:
想要学习更多Android组件化知识,请查看 :
感谢以下文章提供的帮助:
感谢以下开源项目:
访问:97177次
排名:千里之外
原创:12篇
评论:142条
(1)(2)(1)(2)(1)(1)(1)(2)(2)(1)}

我要回帖

更多关于 什么是前端组件化 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信