一丶组件化原理:
就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
1.与插件化何异:
与插件化不同的是: 插件化开发时将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。
二丶组件化用处与好处:
1.用处:
- 随着APP版本不断的迭代,新功能的不断增加,业务也会变的越来越复杂,APP业务模块的数量有可能还会继续增加,而且每个模块的代码也变的越来越多,这样发展下去单一工程下的APP架构势必会影响开发效率,增加项目的维护成本,每个工程师都要熟悉如此之多的代码,将很难进行多人协作开发。
- 而且Android项目在编译代码的时候电脑会非常卡,又因为单一工程下代码耦合严重,每修改一处代码后都要重新编译打包测试,导致非常耗时,最重要的是这样的代码想要做单元测试根本无从下手,所以必须要有更灵活的架构代替过去单一的工程架构。
2.好处:
- 代码简洁,冗余量少,维护方便,易扩展新功能。
- 提高编译速度,从而提高并行开发效率。
- 避免模块之间的交叉依赖,做到低耦合、高内聚。
- 引用的第三方库代码统一管理,避免版本统一,减少引入冗余库。
- 定制项目可按需加载,组件之间可以灵活组建,快速生成不同类型的定制产品。
- 制定相应的组件开发规范,可促成代码风格规范,写法统一。
- 系统级的控制力度细化到组件级的控制力度,从复杂系统构建变成组件构建。
- 每个组件有自己独立的版本,可以独立编译、测试、打包和部署。
三丶组件化框架实现:
一个完整的组件化项目结构:common是基础组件module,作为library存在,需要所有组件依赖;comp1、comp2作为组件存在,可配置成library或可独立运行的module;app是个壳,通过组装组件实现其价值。 如下图:
集成模式与组件模式转换(热插拔)
Android工程通过gradle构建,通过配置每个module的gradle,来实现module的不同表现。Android Studio的module有两种属性,分别是:
- application属性:可独立运行,也就是我们的app
- library属性:不可独立运行,被app依赖的库
module属性通过其目录下的gradle文件配置,当module属性为application时,该module作为完整的app存在,可以独自运行,方便编译和调试;当module属性为library时,该module作为一个依赖库,被壳工程依赖并组装成一个app。那么如何让这两种模式可以自动转换呢?如果每次切换模式的时候,都手动去修改每个组件的配置,组件少的情况下还可以接受,组件多了会非常不方便,下面就让我们来聊聊如何实现两种模式的自动转换。
1.首先,声明全局配置变量,来标识module的属性(app or library),如在工程目录下的build.gradle文件中声明布尔变量ext.isModule,true代表组件作为可独立运行的app,false代表组件作为被依赖的library,如下所示 :
- buildscript {
- ext.kotlin_version = '1.3.21'
- ext.isModule = true //true-每个组件都是单独的module,可独立运行 false-组件作为library存在
- repositories {
- google()
- jcenter()
2、配置组件的build.gradle文件
- if (rootProject.ext.isModule) {
- //可独立运行的app apply plugin: 'com.android.application'
- } else{
- //被依赖的library
- apply plugin: 'com.android.library' }
- apply plugin: 'kotlin-android'
- apply plugin: 'kotlin-android-extensions'
- apply plugin: 'kotlin-kapt'
- android {
- compileSdkVersion 28
- defaultConfig {
- *//applicationId "com.study.comp1" //2 如果没有,默认包名为applicationId
- minSdkVersion 19
- targetSdkVersion 28
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
- 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
- }
- //3
- sourceSets {
- main {
- if(rootProject.ext.isModule){
- manifest.srcFile 'src/main/java/module/AndroidManifest.xml'
- } else{
- manifest.srcFile 'src/main/java/library/AndroidManifest.xml'
- java {//移除module包下的代码
- exclude 'module'
上面是组件gradle的部分代码,包含了组件化需要配置的所有内容,每一点都进行了注释:
- 注释1:如上所述,根据isModule的值,来设置module的属性,作为app or library
- 注释2:当module属性为library时,不能设置applicationId;当为app时,如果未设置applicationId,默认包名为applicationId,所以为了方便,此处不设置applicationId
- 注释3:Android Studio会为每个module生成对应的AndroidManifest.xml文件,声明自身需要的权限、四大组件、数据等内容;当module属性为app时,其对应的AndroidManifest.xml需要具备完整app所需要的所有配置,尤其是声明Application和launch的Activity;当module属性为library时,如果每个组件都声明自己的Application和launch的Activity,那在合并的时候就会发生冲突,编译也不会通过,所以,就需要为当前module重新定义一个AndroidManifest.xml文件,不声明Application和launch的Activity,然后根据isModule的值指定AndroidManifest.xml的路径,因此就有了注释3处的写法。为了避免集成模式下的命名冲突,每个文件都以自身module名为前缀来命名会是一个很好的方法。下图是该module的目录
在需要切换module属性的时候改变步骤1处声明的变量值,然后重新编译即可
组件之间页面跳转(路由)
在组件化架构中,不同的组件之间是平衡的,不存在相互依赖的关系(可参考文章开头的架构图)。因此,假设在组件A中,想要跳转到组件B中的页面,如果使用Intent显式跳转就行不通了,而且大家都知道,Intent隐式跳转管理起来非常不方便,所以Arouter出现了,并且有强大的技术团队支持,可以放心使用了。那么如何在组件化架构中应用Arouter呢?下面详细来聊一聊
1、依赖处理
在common组件中将Arouter依赖进来,并配置编译参数;在业务组件中引入arouter编译器插件,同时配置编译器参数,下面是Common组件gradle文件的部分片段
- //配置arouter编译器参数,每个组件都需配置
- kapt {
- arguments {
- arg("AROUTER_MODULE_NAME", project.getName())
- }
- }
- dependencies {
- //arouter api,只需在common组件中引入一次
- api('com.alibaba:arouter-api:1.4.1') {
- exclude group: 'com.android.support'
- }
- //arouter编译器插件,每个组件都需引入
- kapt 'com.alibaba:arouter-compiler:1.2.2'
- }
2、初始化及编码实现
在组件架构中,经常会遇到组件需要使用全局Context的情况,当组件属性为app时,可以通过自定义Application实现;当组件属性为library时,由于组件被app依赖,导致无法调用app的Application实例,而且自身不存在Application;所以,这里给出的方案是在common组件中创建一个BaseApplication,然后让集成模式(组件模式)下的Application继承BaseApplication,在BaseApplication中获取全局Context,并做一些初始化的工作,这里需要初始化Arouter,如下是在common组件中声明的BaseApplication。
- abstract class BaseApplication : Application() {
- companion object {
- var _context: Application? = null
- //获取全局Context
- fun getContext(): Application {
- return _context!!
- }
- }
- override fun onCreate() {
- super.onCreate()
- _context = this
- //初始化Arouter
- initARouter()
- //初始化其他第三方库
- }
- private fun initARouter() {
- if (BuildConfig.DEBUG) {
- ARouter.openDebug()
- ARouter.openLog()
- }
- ARouter.init(this)
- }
- override fun onTerminate() {
- super.onTerminate()
- //清理Arouter注册表
- ARouter.getInstance().destroy()
- }
根据Arouter的路由特性,初始化之后,就可以通过@Route注解注册页面,然后调用Arouter api实现页面的跳转了(这里所谓的跨组件页面跳转是指在集成模式下,而非组件模式下),无关乎是否在同一个组件下面,假设我们要从组件1页面携带参数跳转到组件2页面,请看下面示例 :
- @Route(path = "/comp2/msg",name = "我是组件2的MSGActivity")
- class Comp2MsgActivity : BaseActivity() {
- //传递过来的参数
- @Autowired(name = "msg")
- @JvmField
- var msg: String? = null
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- *//注入传递的参数
- ARouter.getInstance().inject(this)
- setContentView(R.layout.comp2_activity_msg)
- comp2_msg_msg.text = msg!!
- }
- }
- *//在组件1中发起跳转命令
- ARouter.getInstance()
- .build("/comp2/msg")
- .withString("msg", "hello Im from Comp1")
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .navigation()
以上便完成了一次简单的跨越组件的页面跳转,仅仅是Arouter的基本使用而已。解决了组件间页面跳转的问题后,我们来看看组件之间通信、调用彼此服务的实现。
组件之间通信、调用彼此服务
组件间通信功能和路由功能有着共通的地方,即都是利用Arouter的基础功能实现,在Arouter驱动层定义各个组件对外提供的接口,然后在组件自身模块实现该接口,通过Arouter调用其他组件服务。假设我们在组件2中需要调用组件1中的服务,可以总结为以下三点:
1.定义接口:在common组件中定义组件1对外提供的接口CompServer1,注意该接口类型为Arouter模板类型IProvider
- * 组件1对外提供的接口**
- */
- interface CompServer1 : IProvider {
- fun showMsg(msg: String)
- }
2.实现接口:在comm1中实现上面定义的接口,并通过@Route注解注册
- @Route(path = "/comp1/server",name = "comp1对外提供的服务")
- class CompServer : CompServer1 {
- var mContext: Context? = null
- override fun showMsg(msg: String) {
- Toast.makeText(mContext,msg,Toast.LENGTH_SHORT).show()
- }
- override fun init(context: Context?) {
- this.mContext = context!!
- }
3.调用服务:在完成组件1接口的定义和实现之后,在组件2中需要的地方调用该接口即可
- val server1 = ARouter.getInstance().build("/comp1/server").navigation() as CompServer1
- server1.showMsg("我从comp2吊起了comp1的接口")
四丶总结:
上述就是组件化的原理,以及怎样去实现一个组件化。快动手去试试看自己是否实践一遍。
有关更多面经、核心技术笔记;自己也是从事Android开发5年有余了;整理了一些Android开发技术核心笔记和面经题纲,如有需要的同学请私信我回复“核心笔记”或“面试”领取!
版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除