【Android之组件化开发】
前言
说起组件化,就不得不提一下模块化和插件化。它们的概念十分相似,咋一看还真分不清它们的区别是什么,很多人都会将它们搞混。严格意义来说,组件化、插件化其实也算是模块化的观念。
下面,我们先这三个xx化有个基本的了解。
模块化
在Android studio中,相信大家都创建过Module,而一个Module就是一个小的项目,而每一个Module也被称为模块。
因此,在项目中,我们可能会设计common模块, common_business模块、db模块等等。
设计这些模块的好处是什么?
相比于包来讲,模块更灵活,耦合更低,随意插拔,想引入哪个就引入哪个。
根据不同的关注点,
将一个项目的公共的部分抽取出来,形成独立的Module,就是模块化
。当然,模块化不只包含公共部分,当然也可以是业务模块。
组件化
组件化,它是建立在模块化思想上的一次演进,一个变种。
我们知道,Module又可以是两种格式:
- application:当Module是application的时候,它将具备独立运行的能力
- library:当Module是library的时候,它将作为整个App的一个模块,与App一同运行
组件化的核心是模块角色的可转换性
:在打包时, 是library;在调试时, 是application。
调试时,使模块作为一个单独的app,你需要关注的业务更专一,并且相对来说编译速度也会更快。
插件化
将一个完整的工程,按业务划分为不同的插件,这也是分治法的一种体现。
组件化的单位是组件(module),而插件化的单位是apk
。插件化还可以实现热插拔(热更新)。
组件化的灵活性在于按加载时机切换,而插件化的灵活性在于加载apk,完全可以动态下载,动态更新,比组件化更灵活。
插件化本质上就是不同的apk, 你把各种不同的模块单独做成一个完全独立的app,需要整合的时候就插在一起,就形成了一个大型的app了。
插件化的加载是动态的,这点很重要,也是灵活的根源。
通信方式
- 模块化的通信方式,无非是相互引入;我抽取了common,其他模块使用自然要引入这个module。
- 组件化的通信方式,按理说可以划分为多种,主流的是隐式和路由。隐式的存在使解耦与灵活大大降低,因此路由是主流。
- 插件化的通信方式,不同插件本身就是不同的进程了。因此通信方式偏向于Binder机制类似的进程间通信。
本篇的重点是介绍组件化,因此模块化和插件化就不再赘述,我们重点讲讲组件化。
组件化的使用
组件化就是将一个app分成多个Module,每个Module都是一个组件(也可以是一个基础库供组件依赖),开发的过程中我们可以单独调试部分组件,组件间不需要互相依赖,但可以相互调用,最终发布的时候所有组件以lib的形式被主app工程依赖并打包成1个apk。
集成至Android步骤如下:
1、在工程根目录的build.gradle中添加cc-register插件的classpath。最新版本号1.1.2
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.billy.android:cc-register:1.1.2'
}
2、在工程根目录创建一个名为"cc-settings-2.gradle"的文件,并将以下代码复制到该文件中(也可直接下载github中的cc-settings-2.gradle文件到工程根目录)
project.apply plugin: 'cc-register'
project.dependencies.add('api', "com.billy.android:cc:2.1.6") //用最新版
3、在主app module中添加对cc-settings-2.gradle文件的依赖将apply plugin: 'com.android.application’替换为
ext.mainApp = true //标记为主app
apply from: rootProject.file('cc-settings-2.gradle')
如图所示:
4、新建一个module,并在该module下的build.gradle中添加对cc-settings-2.gradle文件的依赖,同时,将applicationId去除或者修改,与步骤3类似
apply from: rootProject.file('cc-settings-2.gradle')
5、在主app module中按如下方式添加对组件module的依赖
6、创建组件类,向外暴露当前组件提供的服务,新建一个类实现IComponent接口即可
public class ComponentA implements IComponent {
@Override
public String getName() {
//指定组件的名称
return "ComponentA";
}
@Override
public boolean onCall(CC cc) {
String actionName = cc.getActionName();
switch (actionName) {
case "showActivity": //响应actionName为"showActivity"的组件调用
//跳转到页面:ActivityA
CCUtil.navigateTo(cc, MainActivityx.class);
//返回处理结果给调用方
CC.sendCCResult(cc.getCallId(), CCResult.success());
//同步方式实现(在return之前听过CC.sendCCResult()返回组件调用结果),return false
return false;
case "showActivity2": //响应actionName为"showActivity"的组件调用
//跳转到页面:ActivityA
CCUtil.navigateTo(cc, Main2Activity.class);
//返回处理结果给调用方
CC.sendCCResult(cc.getCallId(), CCResult.success());
//同步方式实现(在return之前听过CC.sendCCResult()返回组件调用结果),return false
return false;
default:
//其它actionName当前组件暂时不能响应,可以通过如下方式返回状态码为-12的CCResult给调用方
CC.sendCCResult(cc.getCallId(), CCResult.errorUnsupportedActionName());
return false;
}
}
}
7、调用组件
调用组件分为同步调用和异步调用2种方式
同步调用:调用方将用同步的方式获得返回的调用结果
异步调用:调用方通过回调接收组件的调用结果
//同步调用,直接返回结果
val result = CC.obtainBuilder("ComponentA")
.setActionName("showActivity")
.build()
.call()
//或 异步调用,不需要回调结果
val callId = CC.obtainBuilder("ComponentA")
.setActionName("showActivity")
.build()
.callAsync()
//或 异步调用,在子线程执行回调
val callId = CC.obtainBuilder("ComponentA")
.setActionName("showActivity")
.build()
.callAsync { cc, result ->
//此onResult在子线程中运行
}
//或 异步调用,在主线程执行回调
val callId = CC.obtainBuilder("ComponentA")
.setActionName("login")
.build()
.callAsyncCallbackOnMainThread { cc, result -> //此onResult在主线程中运行
val toast =
"login " + if (result.isSuccess) "success" else "failed"
Toast.makeText(this@MainActivity, toast, Toast.LENGTH_SHORT).show()
}