说到组件化开发,咱们先聊聊三个问题。
第一个问题:什么是组件化?
组件化这三个字顾名思义就是将一个项目拆成多个项目,也就是说一个项目由多个组件组成,就比如一辆汽车,你可以把它拆分成发动机、底盘、车身和电气设备等四个模块;又比如一架飞机你可以把它拆分成机身、动力装置、机翼、尾翼、起落装置、操纵系统和机载设备等7个模块,那么你在开发项目的时候是否也应该把你的项目根据业务的不同拆分成不同的模块,如果不同的模块可以单独运行的话,我们就可以叫它组件。
第二个问题:组件化开发有什么好处?
现在市场上的app几乎都有个人中心这个东西,那么是不是可以把个人中心的所有业务都提到单独的一个模块中,当然是可以的,我们将它放在一个单独的模块中,这个时候你会发现一些好处:1、耦合度降低了2、你需要修改个人中心的时候直接从这个模块中找修改的地方就好了3、你需要测试的时候直接单独运行这个模块就好了,不需要运行整个项目(测试的效率是不是提高了很多呢)4、由于测试的时候可以单独编译某个模块编译速度是不是提高了呢5、如果是团队开发的话,每个人单独负责自己的模块就好了(妈妈再也不用担心我的代码被人家修改了)。
第三个问题:怎么开始组件化开发?
如何开始组件化开发呢?这是个问题啊,有问题得解决啊!android的开发工具android studio中我们新建一个项目的时候,会自动在app下的build.gradle下生成apply plugin: ‘com.android.application’ 这句代码,意思就是这个模块可以独立运行,而如果建立一个Library模块的话会有apply plugin: ‘com.android.library’这么一句代码,标志这个模块不能独立编译成一个app,那么就可以在这两句话中做文章,可以在gradle.properties写个属性例如isLib=true之类的,在组件的配置文件中加入
if (isLib.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
如果为false编译的时候模块就可以自己编译成apk,如果为true将会被编译到主模块中组成完整的apk。如果是多个模块呢?这个时候最好写一个配置文件专门以模块的名字来标记哪些模块暂时需要单独编译,哪些模块需要编译到主项目中,例如在根目录写一个moudle.properties,在这个文件配置所有模块的属性是否开启,在每个模块的app下的build.gradle中读取moudle.properties的配置,这里通过Properties这个类直接读取属性接着标记当前的moudle是library还是application。
这些工作做完后,大体上就实现了组件化配置的一大步了,由于每一个组件在必要的时候都需要能单独运行,那么就必须在AndroidManifest.xml配置入口的Activity,那么这里又存在一个问题了,在这个模块并入主模块的时候,是不是有多个入口Activity,那么安装apk的时候是不是出现多个启动图标,是不是很流氓,要处理这个问题的话就得在子模块中配置两个AndroidManifest.xml,一个专门用于合并时候的配置文件,一个文件专门用于单独启动,区别就是一个有配置入口Activity,一个不配置入口Activity,那么这个区别可以根据配置属性来区别:
sourceSets {
main {
if (isLib.toBoolean()) {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/moudle/AndroidManifest.xml'
}
}
}
做完这些后,也算迈出了组件化的一大步,但是有没有发现还有一个问题,不管写什么项目差不多都需要一个BaseApplication来处理一些事情,由于BaseApplication在所有的模块中都有用到,所有封装成公共的Lib,但是这里需要配置清单文件的<application>标签指向BaseApplication,这又出现了清单文件的合并冲突的问题,这时如果要解决这个冲突可以在子模块的main文件夹下新建一个moudle文件夹,然后在此文件下创建BaseApplication的子类MoudleApplication,让<application>标签指向它就会解决冲突,如下:
sourceSets {
main {
if (isLib.toBoolean()) {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/moudle/AndroidManifest.xml'
java {
exclude 'moudle/**'
}
}
}
}
在这个子模块需要单独运行的时候,编译的时候将新建的 moudle文件夹下的类文件也编译了。
说到这里,组件化实现的基本原理就介绍完了,但是先不要走,下面来讲解CC组件化框架的实现原理,先来个美女镇楼休息一下
CC源码,想知道CC怎么用的小伙伴直接看作者的源码连接,作者对怎么使用介绍的很详细,这里简单的介绍一下
dependencies {
classpath 'com.billy.android:autoregister:1.2.0'
}
首先在根build.gradle中先引入autoregister插件,这个插件是干什么的呢?它的作用时在编译的时候利用ASM动态的改变注册码,这里的作用是动态的将你声明的所有组件注册到CC集合中,以便在跳转中使用,运用了gradle1.5之后出的Transform的api,至于TransForm怎么用,可以参考我的这一篇博客美团热修复Robust源码庖丁解牛(第一篇字节码插桩),至于ASM具体的用法在接下来的博客中,笔者会专门详细的介绍一下,其实学ASM看官方文档就好了,但是英文的让人头疼,这里提供中文文档下载,学习ASM的使用,看它就足够了ASM中文帮助文档。
好了接着看配置文件,接下来不管是在你的主模块还是子模块中都需要在build.gradle中加入:
apply from: 'https://raw.githubusercontent.com/luckybilly/CC/master/cc-settings.gradle'
这里配置是不是就相当于一个公共配置,如果想让子模块可以单独编译成apk的话需要在local.properties中添加配置
module_name=true #module_name为具体每个module的名称,设置为true代表以application方式编译
那么这个配置肯定会在cc-settings.gradle中使用到,来一起看看这个文件中的DSL配置
import java.util.regex.Pattern
//先加载local.properties文件
Properties localProperties = new Properties()
try {
def localFile = project.rootProject.file('local.properties')
if (localFile != null && localFile.exists()) {
localProperties.load(localFile.newDataInputStream())
}
} catch (Exception ignored) {
println("local.properties not found")
}
//读取build.gradle中的设置
// 2018-04-06修改:
// 为了更利于理解,将ext.runAsApp 改名为 ext.mainApp
// ext.mainApp的将仅代表是否作为主app,为true时以application方式编译,为false或未配置时以local.properties中的配置为准
// 兼容以前的runAsApp设置,ext.runAsApp的功能保持不变,runAsApp优先级高于local.properties
def runAsApp = ext.has('runAsApp')
if (runAsApp) {
runAsApp = ext.runAsApp
} else if(ext.has('mainApp') && ext.mainApp) { //ext.mainApp为true时,代表以app方式运行
runAsApp = true
} else {
//build.gradle中没有配置runAsApp,且ext.mainApp=false或未配置
//再从local.properties中读取配置,例如: demo_component_a=true
//注:如果采用local.properties读取配置,每次修改需要重新同步(Sync Project)一下
runAsApp = 'true' == localProperties.getProperty(project.name)
//设置到ext中,供module的build.gradle使用(例如用于设置sourceSets配置)
ext.runAsApp = runAsApp
}
//用来声明这个模块是lib还是单独运行的子模块
if (runAsApp) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
//对组件库的依赖格式: addComponent dependencyName [, realDependency]
// 使用示例见demo/build.gradle
// dependencyName: 组件库的名称,推荐直接使用使用module的名称
// realDependency(可选): 组件库对应的实际依赖,可以是module依赖,也可以是maven依赖
// 如果未配置realDependency,将自动依赖 project(":$dependencyName")
// realDependency可以为如下2种中的一种:
// module依赖 : project(':demo_component_b') //如果module名称跟dependencyName相同,可省略(推荐)
// maven依赖 : 'com.billy.demo:demoB:1.1.0' //如果使用了maven私服,请使用此方式
//声明闭包方法:
ext.addComponent = { dependencyName, realDependency = null ->
def curModuleIsBuildingApk = false //当前task是否为给本module打apk包
def taskNames = project.gradle.startParameter.taskNames
def regex = "((.*:)?${project.name.toUpperCase()}:)?((ASSEMBLE)|(INSTALL)).*"
def taskBuildApkPattern = Pattern.compile(regex)
for (String task : taskNames) {
if (taskBuildApkPattern.matcher(task.toUpperCase()).matches()) {
curModuleIsBuildingApk = true
break
}
}
//不是在为本app module打apk包,不添加对组件的依赖
if (!curModuleIsBuildingApk)
return
def componentProject = rootProject.subprojects.find { it.name == dependencyName }
def app //dependencyName指定的module是否为配置为以application方式编译
if (componentProject && componentProject.ext.has('runAsApp')) {
//兼容以前的ext.runAsApp=true的配置方式,runAsApp的优先级高
app = componentProject.ext.runAsApp
} else if (componentProject && componentProject.ext.has('mainApp') && componentProject.ext.mainApp) {
//仅ext.mainApp为true时,确定为application方式编译,若为false,则读取local.properties中的配置
app = true
} else {
//local.properties中配置为true代表该module以application方式编译
app = 'true' == localProperties.getProperty(dependencyName)
}
if (!app) {
def dependencyMode = (project.gradle.gradleVersion as float) >= 4.1F ? 'runtimeOnly' : 'apk'
if (realDependency) {
//通过参数传递的依赖方式,如:
// project(':moduleName')
// 或
// 'com.billy.demo:demoA:1.1.0'
project.dependencies.add(dependencyMode, realDependency)
println "CC >>>> add $realDependency to ${project.name}'s dependencies"
} else if (componentProject) {
//第二个参数未传,默认为按照module来进行依赖
project.dependencies.add(dependencyMode, project(":$dependencyName"))
println "CC >>>> add project(\":$dependencyName\") to ${project.name}'s dependencies"
} else {
throw new RuntimeException(
"CC >>>> add dependency by [ addComponent '$dependencyName' ] occurred an error:" +
"\n'$dependencyName' is not a module in current project" +
" and the 2nd param is not specified for realDependency" +
"\nPlease make sure the module name is '$dependencyName'" +
"\nelse" +
"\nyou can specify the real dependency via add the 2nd param, for example: " +
"addComponent '$dependencyName', 'com.billy.demo:demoB:1.1.0'")
}
}
}
repositories {
maven { url rootProject.file("repo-local") }
jcenter()
}
//默认配置了AndroidManifest.xml在library模式和application模式下的文件路径
android {
sourceSets {
main {
//默认的作为application运行时Manifest文件路径
def debugManifest = 'src/main/debug/AndroidManifest.xml'
if (runAsApp && project.file(debugManifest).exists()) {
manifest.srcFile debugManifest
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式下自动排除debug文件夹中的所有Java文件
// 可以将debug代码放在这个包内,例如:Application子类
java {
exclude 'debug/**'
}
}
// 注:2018-03-12推荐:将源码放到src/main/debug/java目录下
if (runAsApp) {
//debug模式下,如果存在src/main/debug/java,则自动将其添加到java源码目录
if (project.file('src/main/debug/java').exists()) {
java.srcDirs = ['src/main/java', 'src/main/debug/java']
}
//debug模式下,如果存在src/main/debug/res,则自动将其添加到资源目录
if (project.file('src/main/debug/res').exists()) {
res.srcDirs = ['src/main/res', 'src/main/debug/res']
}
}
}
}
}
dependencies {
compile "com.billy.android:cc:0.5.0"
// compile project(":cc")
}
//auto register extension:
// 源码地址:https://github.com/luckybilly/AutoRegister
// 功能介绍:
// 在编译期扫描将打到apk包中的所有类
// 将 scanInterface的实现类 或 scanSuperClasses的子类
// 并在 codeInsertToClassName 类的 codeInsertToMethodName 方法中生成如下代码:
// codeInsertToClassName.registerMethodName(scanInterface)
// 要点:
// 1. codeInsertToMethodName 若未指定,则默认为static块
// 2. codeInsertToMethodName 与 registerMethodName 需要同为static或非static
// 自动生成的代码示例:
/*
在com.billy.app_lib_interface.CategoryManager.class文件中
static
{
register(new CategoryA()); //scanInterface的实现类
register(new CategoryB()); //scanSuperClass的子类
}
*/
project.apply plugin: 'auto-register'
autoregister {
registerInfo = [
[
'scanInterface' : 'com.billy.cc.core.component.IComponent'
, 'codeInsertToClassName' : 'com.billy.cc.core.component.ComponentManager'
, 'registerMethodName' : 'registerComponent'
, 'exclude' : [//排除的类,支持正则表达式(包分隔符需要用/表示,不能用.)
'com.billy.cc.core.component.'.replaceAll("\\.", "/") + ".*"
]
]
]
}
可以看出,这个配置文件首先去加载local.properties,这里如果你在你配置了ext标签的话,ext.mainApp=true里的属性优先级大于local.properties属性的优先级,ext.mainApp属性用来标记当前模块是不是主模块,在主模块中需要配置这个属性,而子模块中需要配置runAsApp,为true时
可以单独作为app运行,为false时,将做为主模块的一部分打包进主模块的app中。而接下来这个闭包方法,是在调用的时候使用
ext.addComponent = { dependencyName, realDependency = null ->
这里既然声明了依赖,那么什么时候会依赖呢,当然是主模块需要和子模块一块打包的时候的依赖了,而在依赖的时候子模块是不可以单独
运行的,这里还要注意一个点只有当编译项目中有任务ASSEMBLE)或INSTALL才执行下面的依赖,这里寓意很明显,只要在执行编译资源成
apk的任务或执行安装apk任务(安装之前也得先编译哈)的时候才会执行下面的依赖。来看一下主项目中怎么调用这个闭包的:
addComponent 'demo_component_a'
addComponent 'demo_component_kt'
//单独运行demo_component_b时,只需在local.properties中添加demo_component_b=true即可
// 此处无需修改,再次运行demo:assembleXxx时将不会把demo_component_b打包进apk
addComponent 'demo_component_b', project(':demo_component_b') // 这里参数2可以省略
在调用这个方法的时候会判断子moudle的配置文件是标记它为application还是library,如果没application,打包的时候将不把这个
子模块依赖进来,从而加快编译速度。这里的依赖的方式需要注意一下:括号里面的是android studio3.x之后的依赖改变,括号外
为android studio2.x的依赖方式
provided(compileOnly)
只在编译时有效,不会参与打包
可以在自己的moudle中使用该方式依赖一些比如com.android.support,gson这些使用者常用的库,避免冲突。
apk(runtimeOnly)
只在生成apk的时候参与打包,编译时不会参与,很少用。
testCompile(testImplementation)
testCompile 只在单元测试代码的编译以及最终打包测试apk时有效。
debugCompile(debugImplementation)
debugCompile 只在debug模式的编译和最终的debug apk打包时有效
provided(compileOnly)
只在编译时有效,不会参与打包
可以在自己的moudle中使用该方式依赖一些比如com.android.support,gson这些使用者常用的库,避免冲突。
apk(runtimeOnly)
只在生成apk的时候参与打包,编译时不会参与,很少用。
testCompile(testImplementation)
testCompile 只在单元测试代码的编译以及最终打包测试apk时有效。
debugCompile(debugImplementation)
debugCompile 只在debug模式的编译和最终的debug apk打包时有效
releaseCompile(releaseImplementation)
Release compile 仅仅针对Release 模式的编译和最终的Release apk打包。
releaseCompile(releaseImplementation)
Release compile 仅仅针对Release 模式的编译和最终的Release apk打包。
最后的配置就是上面我们说的避免BaseApplication冲突和清单文件AndroidManifestx.ml的冲突,如下:
android {
sourceSets {
main {
//默认的作为application运行时Manifest文件路径
def debugManifest = 'src/main/debug/AndroidManifest.xml'
if (runAsApp && project.file(debugManifest).exists()) {
manifest.srcFile debugManifest
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式下自动排除debug文件夹中的所有Java文件
// 可以将debug代码放在这个包内,例如:Application子类
java {
exclude 'debug/**'
}
}
// 注:2018-03-12推荐:将源码放到src/main/debug/java目录下
if (runAsApp) {
//debug模式下,如果存在src/main/debug/java,则自动将其添加到java源码目录
if (project.file('src/main/debug/java').exists()) {
java.srcDirs = ['src/main/java', 'src/main/debug/java']
}
//debug模式下,如果存在src/main/debug/res,则自动将其添加到资源目录
if (project.file('src/main/debug/res').exists()) {
res.srcDirs = ['src/main/res', 'src/main/debug/res']
}
}
}
}
}
最后的配置就是应用字节码插桩的插件:
project.apply plugin: 'auto-register'
autoregister {
registerInfo = [
[
'scanInterface' : 'com.billy.cc.core.component.IComponent'
, 'codeInsertToClassName' : 'com.billy.cc.core.component.ComponentManager'
, 'registerMethodName' : 'registerComponent'
, 'exclude' : [//排除的类,支持正则表达式(包分隔符需要用/表示,不能用.)
'com.billy.cc.core.component.'.replaceAll("\\.", "/") + ".*"
]
]
]
}
意思就是在class混淆编译成dex之前,将所有实现IComponent接口的class文件全部扫描出来,然后在ComponentManager的class
文件中插入
static
{
register(new CategoryA()); //scanInterface的实现类
register(new CategoryB()); //scanSuperClass的子类
}
这些代码,就相当于把你实现的IComponent接口的所有组件类全部注册到组件表中,那么在跳转的组件的时候直接找到组件跳转就好了,
不需要手动注册了。也就是说每一个子模块的入口的Activity,你都需要为它单独写一个接口的实现子接口IComponent的类,这个类
相当于入口Activity的中介类,你再跳转子模块的时候不需要关心子模块的入口Activity,只需要和它的中介类IComponent实现类
打交道实现跳转就好了。下面看一下这个接口的简单实现:
public class ComponentA implements IComponent {
//需保留无参构造方法
@Override
public String getName() {
//组件的名称,调用此组件的方式:
// CC.obtainBuilder("ComponentA").build().callAsync()
return "ComponentA";
}
@Override
public boolean onCall(CC cc) {
Context context = cc.getContext();
Intent intent = new Intent(context, ActivityComponentA.class);
if (!(context instanceof Activity)) {
//调用方没有设置context或app间组件跳转,context为application
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
//发送组件调用的结果(返回信息)
CC.sendCCResult(cc.getCallId(), CCResult.success());
//返回值说明
// false: 组件同步实现(onCall方法执行完之前会将执行结果CCResult发送给CC)
// true: 组件异步实现(onCall方法执行完之后再将CCResult发送给CC,CC会持续等待组件调用CC.sendCCResult发送的结果,直至超时)
return false;
}
}
在onCall中还是实现了要跳转到哪个Activity的方法,也就是跳转到组件的入口Activity。这个框架还可以将Frament和View
组件化,但是笔者认为这样的话粒度太小,虽然易于维护,但是组件的边界太小了,本来组件化就是根据业务逻辑分模块的,如果
分的太多的话,反而显得杂乱无章,但是适度的分配还是好处多点。
配置和IComponent实现类都写完的话,接下来就是调用了,调用有如下四方种式:
//同步调用,直接返回结果
CCResult result = CC.obtainBuilder("ComponentA").build().call();
//或 异步调用,不需要回调结果
String callId = CC.obtainBuilder("ComponentA").build().callAsync();
//或 异步调用,在子线程执行回调
String callId = CC.obtainBuilder("ComponentA").build().callAsync(new IComponentCallback(){...});
//或 异步调用,在主线程执行回调
String callId = CC.obtainBuilder("ComponentA").build().callAsyncCallbackOnMainThread(new IComponentCallback(){...});
咱们这里只看最后一个方法,原理都是一样的吗,开看:
public String callAsyncCallbackOnMainThread(IComponentCallback callback) {
this.callbackOnMainThread = true;
return processCallAsync(callback);
}
这个方法只是简单的设置了callbackOnMainThread这个标志位true,代表回调的时候从主线程处理,接着看processCallAsync
方法:
private String processCallAsync(IComponentCallback callback) {
if (callback != null) {
this.callback = callback;
}
this.async = true;
//调用方未设置超时时间,默认为无超时时间
if (timeout < 0) {
timeout = 0;
}
setTimeoutAt();
this.callId = nextCallId();
this.canceled = false;
this.timeoutStatus = false;
if (VERBOSE_LOG) {
verboseLog(callId, "start to callAsync:" + this);
}
ComponentManager.call(this);
return callId;
}
这个方法的意思是将异步调用的标志设为true,然后判断有没有设置超时时间,也就是调用的超时时间(setTimeoutAt()):
private void setTimeoutAt() {
if (timeout > 0) {
timeoutAt = System.currentTimeMillis() + timeout;
} else {
timeoutAt = 0;
}
}
时间为当前时间加上设置的值,这里的值以毫秒为单位,然后获得调用的callId private String nextCallId() {
if (TextUtils.isEmpty(prefix)) {
Context context = getContext();
if (context != null) {
prefix = context.getPackageName() + ":";
} else {
return ":::" + index.getAndIncrement();
}
}
return prefix + index.getAndIncrement();
}
callId的值为当前的包名加上自增原子变量的值类似每次++a,然后将取消标志设为false,超时标记设为fasle,最后调用
ComponentManager的call方法,接着跟进:
static CCResult call(CC cc) {
String callId = cc.getCallId();
Chain chain = new Chain(cc);
chain.addInterceptor(ValidateInterceptor.getInstance());
chain.addInterceptors(cc.getInterceptors());
if (hasComponent(cc.getComponentName())) {
chain.addInterceptor(LocalCCInterceptor.getInstance());
} else {
chain.addInterceptor(new RemoteCCInterceptor(cc));
}
chain.addInterceptor(Wait4ResultInterceptor.getInstance());
ChainProcessor processor = new ChainProcessor(chain);
//异步调用,放到线程池中运行
if (cc.isAsync()) {
if (CC.VERBOSE_LOG) {
CC.verboseLog(callId, "put into thread pool");
}
CC_THREAD_POOL.submit(processor);
//异步调用时此方法返回null,CCResult通过callback回调
return null;
} else {
//同步调用,直接执行
CCResult ccResult;
try {
ccResult = processor.call();
} catch (Exception e) {
ccResult = CCResult.defaultExceptionResult(e);
}
if (CC.VERBOSE_LOG) {
CC.verboseLog(callId, "cc finished.CCResult:" + ccResult);
}
//同步调用的返回结果,永不为null,默认为CCResult.defaultNullResult()
return ccResult;
}
}
写法,首先将验证拦截器加入Chain里面,然后添加你在cc中自定义的拦截器,然后根据是否组件被注册进表中来添加本地调用拦截器或
添加远程调用拦截器,通信采用LocalServerSocket和广播的方式,最后添加等待结果拦截器、将链条Chain放入链条处理器ChainProcessor中,然后根据是否是异步调用采用同步或异步调用。
这里直接看异步调用,这里的线程池直接用的jdk1.5提供的api,如下:
static final ExecutorService CC_THREAD_POOL = new ThreadPoolExecutor(2, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), THREAD_FACTORY);
那么链条处理器肯定实现了Callable接口,接着来看ChainProcessor的实现
public CCResult call() throws Exception {
CC cc = chain.getCC();
String callId = cc.getCallId();
//从开始调用的时候就开始进行监控,也许时间设置的很短,可能都不需要执行拦截器调用链
CCMonitor.addMonitorFor(cc);
CCResult result;
try {
if (CC.VERBOSE_LOG) {
int poolSize = ((ThreadPoolExecutor) ComponentManager.CC_THREAD_POOL).getPoolSize();
CC.verboseLog(callId, "process cc at thread:"
+ Thread.currentThread().getName() + ", pool size=" + poolSize);
}
if (cc.isFinished()) {
//timeout, cancel, CC.sendCCResult(callId, ccResult)
result = cc.getResult();
} else {
try {
CC.verboseLog(callId, "start interceptor chain");
result = chain.proceed();
if (CC.VERBOSE_LOG) {
CC.verboseLog(callId, "end interceptor chain.CCResult:" + result);
}
} catch(Exception e) {
result = CCResult.defaultExceptionResult(e);
}
}
} catch(Exception e) {
result = CCResult.defaultExceptionResult(e);
} finally {
CCMonitor.removeById(callId);
}
//返回的结果,永不为null,默认为CCResult.defaultNullResult()
if (result == null) {
result = CCResult.defaultNullResult();
}
cc.setResult(result);
performCallback(cc, result);
return result;
}
直接设置默认结果,然后添加时间超时的线程监听,如果超时也没有必要继续调用了,下面是监测器的代码:
static void addMonitorFor(CC cc) {
if (cc != null) {
CC_MAP.put(cc.getCallId(), cc);
cc.addCancelOnFragmentDestroyIfSet();
long timeoutAt = cc.timeoutAt;
if (timeoutAt > 0) {
if (minTimeoutAt > timeoutAt) {
minTimeoutAt = timeoutAt;
//如果最小timeout时间有变化,且监控线程在wait,则唤醒监控线程
synchronized (LOCK) {
LOCK.notifyAll();
}
}
if (STOPPED.compareAndSet(true, false)) {
new TimeoutMonitorThread().start();
}
}
if (CC.VERBOSE_LOG) {
CC.verboseLog(cc.getCallId(), "totalCC count=" + CC_MAP.size()
+ ". add monitor for:" + cc);
}
}
}
而addCancelOnFragmentDestroyIfSet方法如下: void addCancelOnFragmentDestroyIfSet() {
if (cancelOnDestroyFragment == null) {
return;
}
Fragment fragment = cancelOnDestroyFragment.get();
if (fragment == null) {
return;
}
FragmentManager manager = fragment.getFragmentManager();
if (manager != null) {
manager.registerFragmentLifecycleCallbacks(
new CCMonitor.FragmentMonitor(this)
, false);
}
}
如果想将调用和Activity和Frament的生命周期绑定起来的话调用这面这两个方法:
public Builder cancelOnDestroyWith(Activity activity) {
if (activity != null) {
cr.cancelOnDestroyActivity = new WeakReference<>(activity);
}
return this;
}
/**
* 设置fragment.onDestroy时自动cancel
* @param fragment 监控此fragment的生命周期,在onDestroy方法被调用后若cc未执行完则自动cancel
* @return Builder自身
*/
public Builder cancelOnDestroyWith(Fragment fragment) {
if (fragment != null) {
cr.cancelOnDestroyFragment = new WeakReference<>(fragment);
}
return this;
}
绑定生命周期的好处就是用户不小心突然结束了当前Activity或Frament的话就没有必要调用组件并返回结果了。而超时时间的监测
就是监测调用组件到返回数据的这段时间是否超时了,如果超时则直接调用cc.timeout()方法,STOPPED原子变量保证超时线程只被
启动一次。
实现如下:
if (STOPPED.get()) {
return;
}
while(CC_MAP.size() > 0 || minTimeoutAt == Long.MAX_VALUE) {
try {
long millis = minTimeoutAt - System.currentTimeMillis();
if (millis > 0) {
synchronized (LOCK) {
LOCK.wait(millis);
}
}
//next cc timeout
long min = Long.MAX_VALUE;
long now = System.currentTimeMillis();
for (CC cc : CC_MAP.values()) {
if (!cc.isFinished()) {
long timeoutAt = cc.timeoutAt;
if (timeoutAt > 0) {
if (timeoutAt < now) {
cc.timeout();
} else if (timeoutAt < min) {
min = timeoutAt;
}
}
}
}
minTimeoutAt = min;
} catch (InterruptedException ignored) {
}
}
STOPPED.set(true);
}
public CCResult proceed() {
if (index >= interceptors.size()) {
return CCResult.defaultNullResult();
}
ICCInterceptor interceptor = interceptors.get(index++);
//处理异常情况:如果为拦截器为null,则执行下一个
if (interceptor == null) {
return proceed();
}
String name = interceptor.getClass().getName();
String callId = cc.getCallId();
CCResult result;
if (cc.isFinished()) {
//timeout, cancel, CC.sendCCResult(callId, ccResult), cc.setResult, etc...
result = cc.getResult();
} else {
if (CC.VERBOSE_LOG) {
CC.verboseLog(callId, "start interceptor:" + name);
}
try {
result = interceptor.intercept(this);
} catch(Throwable e) {
//防止拦截器抛出异常
result = CCResult.defaultExceptionResult(e);
}
if (CC.VERBOSE_LOG) {
CC.verboseLog(callId, "end interceptor:" + name + ".CCResult:" + result);
}
}
//拦截器理论上不应该返回null,但为了防止意外(自定义拦截器返回null,此处保持CCResult不为null
//消灭NPE
if (result == null) {
result = CCResult.defaultNullResult();
}
cc.setResult(result);
return result;
}
这个方法主要的工作就是执行链式的拦截器interceptor.intercept(this)从而获得返回结果,那么第一个要执行的拦截器就是验证拦截器
实现如下:
public CCResult intercept(Chain chain) {
CC cc = chain.getCC();
String componentName = cc.getComponentName();
int code = 0;
if (TextUtils.isEmpty(componentName)) {
//没有指定要调用的组件名称,中止运行
code = CCResult.CODE_ERROR_COMPONENT_NAME_EMPTY;
} else if (cc.getContext() == null) {
//context为null (没有设置context 且 CC中获取application失败)
code = CCResult.CODE_ERROR_CONTEXT_NULL;
} else {
boolean hasComponent = ComponentManager.hasComponent(componentName);
if (!hasComponent && !CC.CALL_REMOTE_CC_IF_NEED) {
//本app内没有改组件,并且设置了不会调用外部app的组件
code = CCResult.CODE_ERROR_NO_COMPONENT_FOUND;
CC.verboseLog(cc.getCallId(),"componentName=" + componentName
+ " is not exists and CC.enableRemoteCC is " + CC.CALL_REMOTE_CC_IF_NEED);
}
}
if (code != 0) {
return CCResult.error(code);
}
return chain.proceed();
}
这个验证拦截器用来验证组件的名字字符串必须合法,并且组件注册表中可以找到这个组件或者你将远程调用标识设置了为true的话则合格,
还有必须有当前上下文,最后继续执行链表中的下一个拦截器,假设这个调用是在一个进程中调用的,则接下来执行LocalCCInterceptor,如下:
public CCResult intercept(Chain chain) {
CC cc = chain.getCC();
IComponent component = ComponentManager.getComponentByName(cc.getComponentName());
if (component == null) {
CC.verboseLog(cc.getCallId(), "component not found in this app. maybe 2 reasons:"
+ "\n1. CC.enableRemoteCC changed to false"
+ "\n2. Component named \"%s\" is a IDynamicComponent but now is unregistered"
);
return CCResult.error(CCResult.CODE_ERROR_NO_COMPONENT_FOUND);
}
//是否需要wait:异步调用且未设置回调,则不需要wait
boolean callbackNecessary = !cc.isAsync() || cc.getCallback() != null;
try {
String callId = cc.getCallId();
boolean callbackDelay = component.onCall(cc);
if (CC.VERBOSE_LOG) {
CC.verboseLog(callId, component.getName() + ":"
+ component.getClass().getName()
+ ".onCall(cc) return:" + callbackDelay
);
}
//兼容异步调用时等待回调结果(同步调用时,此时CC.sendCCResult(callId, result)方法已调用)
if (!cc.isFinished() && callbackNecessary) {
//component.onCall(cc)没报exception并且指定了要延时回调结果才进入正常wait流程
if (callbackDelay) {
return chain.proceed();
} else {
CC.logError("component.onCall(cc) return false but CC.sendCCResult(...) not called!"
+ "\nmaybe: actionName error"
+ "\nor if-else not call CC.sendCCResult"
+ "\nor switch-case-default not call CC.sendCCResult"
+ "\nor try-catch block not call CC.sendCCResult."
);
//没有返回结果,且不是延时回调(也就是说不会收到结果了)
return CCResult.error(CCResult.CODE_ERROR_CALLBACK_NOT_INVOKED);
}
}
} catch(Exception e) {
return CCResult.defaultExceptionResult(e);
}
return cc.getResult();
}
这个方法的主要工作就是调用组件的component.onCall(cc);方法实现真正的跳转,也就是上面实现的这个方法:
Context context = cc.getContext();
Intent intent = new Intent(context, ActivityComponentA.class);
if (!(context instanceof Activity)) {
//调用方没有设置context或app间组件跳转,context为application
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
//发送组件调用的结果(返回信息)
CC.sendCCResult(cc.getCallId(), CCResult.success());
//返回值说明
// false: 组件同步实现(onCall方法执行完之前会将执行结果CCResult发送给CC)
// true: 组件异步实现(onCall方法执行完之后再将CCResult发送给CC,CC会持续等待组件调用CC.sendCCResult发送的结果,直至超时)
return false;
}
调用完之后,如果是异步调用则不用执行等待返回结果拦截器执行,如果不是异步调用或者有回调函数则执行返回结果的等待,也就是执行下面
这个拦截器方法:
public CCResult intercept(Chain chain) {
CC cc = chain.getCC();
cc.wait4Result();
return cc.getResult();
}
void wait4Result() {
//等待调用CC.sendCCResult(callId, result)
synchronized (wait4resultLock) {
if (!isFinished()) {
try {
verboseLog(callId, "start waiting for CC.sendCCResult(...)");
wait4resultLock.wait();
verboseLog(callId, "end waiting for CC.sendCCResult(...)");
} catch (InterruptedException ignored) {
}
}
}
}
当前线程堵塞,等待回调函数的执行。
最后返回结果后如果设置了在主线程中执行的话,则通过handler发消息执行,如下:
private static void performCallback(CC cc, CCResult result) {
IComponentCallback callback = cc.getCallback();
if (CC.VERBOSE_LOG) {
CC.verboseLog(cc.getCallId(), "perform callback:" + cc.getCallback()
+ ", CCResult:" + result);
}
if (callback == null) {
return;
}
if (cc.isCallbackOnMainThread()) {
HANDLER.post(new CallbackRunnable(callback, cc, result));
} else {
try {
callback.onResult(cc, result);
} catch(Exception e) {
e.printStackTrace();
}
}
}
最后来看一下远程拦截器的代码:
private static void performCallback(CC cc, CCResult result) {
IComponentCallback callback = cc.getCallback();
if (CC.VERBOSE_LOG) {
CC.verboseLog(cc.getCallId(), "perform callback:" + cc.getCallback()
+ ", CCResult:" + result);
}
if (callback == null) {
return;
}
if (cc.isCallbackOnMainThread()) {
HANDLER.post(new CallbackRunnable(callback, cc, result));
} else {
try {
callback.onResult(cc, result);
} catch(Exception e) {
e.printStackTrace();
}
}
}
这个方法首先执行进程之间的连接(前提是你必须安装并运行主模块和安装并运行了子模块(两个模块之间的通信)),接着来看一
下连接:
public void run() {
Context context = cc.getContext();
if (context == null) {
setResult(CCResult.error(CCResult.CODE_ERROR_CONTEXT_NULL));
return;
}
//retrieve ComponentBroadcastReceiver permission
if (TextUtils.isEmpty(receiverIntentFilterAction)) {
try{
ComponentName receiver = new ComponentName(context.getPackageName(), ComponentBroadcastReceiver.class.getName());
ActivityInfo receiverInfo = context.getPackageManager().getReceiverInfo(receiver, PackageManager.GET_META_DATA);
receiverPermission = receiverInfo.permission;
receiverIntentFilterAction = "cc.action.com.billy.cc.libs.component.REMOTE_CC";
} catch(Exception e) {
e.printStackTrace();
setResult(CCResult.error(CCResult.CODE_ERROR_CONNECT_FAILED));
return;
}
}
Intent intent = new Intent(receiverIntentFilterAction);
if (CC.DEBUG) {
intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
}
intent.putExtra(KEY_COMPONENT_NAME, cc.getComponentName());
intent.putExtra(KEY_ACTION_NAME, cc.getActionName());
intent.putExtra(KEY_TIMEOUT, cc.getTimeout());
JSONObject params = new JSONObject(cc.getParams());
intent.putExtra(KEY_PARAMS, params.toString());
String callId = cc.getCallId();
intent.putExtra(KEY_CALL_ID, callId);
socketName = "lss:" + callId;
intent.putExtra(KEY_SOCKET_NAME, socketName);
LocalServerSocket lss = null;
//send broadcast for remote cc connection
BufferedReader in = null;
try {
lss = new LocalServerSocket(socketName);
if (CC.VERBOSE_LOG) {
CC.verboseLog(callId, "sendBroadcast to call component from other App."
+ " permission:" + receiverPermission);
}
context.sendBroadcast(intent, receiverPermission);
ComponentManager.threadPool(new CheckConnectTask());
LocalSocket socket = lss.accept();
ccProcessing = true;
if (stopped) {
return;
}
connectLatch.countDown();
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
if (CC.VERBOSE_LOG) {
CC.verboseLog(callId, "localSocket connect success. " +
"start to wait for remote CCResult.");
}
//blocking for CCResult
String str = in.readLine();
if (CC.VERBOSE_LOG) {
CC.verboseLog(callId, "localSocket received remote CCResult:" + str);
}
setResult(CCResult.fromString(str));
} catch(Exception e) {
e.printStackTrace();
setResult(CCResult.error(CCResult.CODE_ERROR_CONNECT_FAILED));
} finally {
if (connectLatch.getCount() > 0) {
connectLatch.countDown();
}
if (lss != null) {
try {
lss.close();
} catch (Exception ignored) {
}
}
if (in != null) {
try {
in.close();
} catch (Exception ignored) {
}
}
}
}
这个方法的核心意思就是先通过广播向组件发一条信息,然后通过LocalSocket创建一个本地服务,等待子模块的连接: String action = intent.getAction();
CC.log("onReceive, packageName=" + context.getPackageName() + ", action=" + action);
if (!CC.RESPONSE_FOR_REMOTE_CC) {
CC.log("receive cc, but CC.enableRemoteCC() is set to false in this app");
return;
}
Bundle extras = intent.getExtras();
if (extras == null) {
return;
}
String componentName = extras.getString(RemoteCCInterceptor.KEY_COMPONENT_NAME);
if (TextUtils.isEmpty(componentName) ||
!ComponentManager.hasComponent(componentName)) {
//当前app中不包含此组件,直接返回(其它app会响应该调用)
return;
}
if (CC.VERBOSE_LOG) {
CC.verboseLog(extras.getString(RemoteCCInterceptor.KEY_CALL_ID)
, "receive remote cc, start service to perform it.");
}
Intent serviceIntent = new Intent(context, ComponentService.class);
serviceIntent.putExtras(extras);
context.startService(serviceIntent);
子模块通过广播接收到信息后(主模块服务的端口和域名(ip)),将启动一个后台service: String callId = intent.getStringExtra(RemoteCCInterceptor.KEY_CALL_ID);
String componentName = intent.getStringExtra(RemoteCCInterceptor.KEY_COMPONENT_NAME);
String socketName = intent.getStringExtra(RemoteCCInterceptor.KEY_SOCKET_NAME);
try {
socket = new LocalSocket();
socket.connect(new LocalSocketAddress(socketName));
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch(Exception e) {
e.printStackTrace();
}......省略若干行
然后子模块通过LocalSocket和主项目建立连接,然后通过在子模块调用跳转到子模块入口类,并把返回值通过socket通信返回给
主项目apk:
//由于RemoteCCInterceptor中已处理同步/异步调用的逻辑,此处直接同步调用即可
cc = CC.obtainBuilder(componentName)
.setActionName(actionName)
.setParams(params)
.setTimeout(timeout)
.build();
ComponentManager.threadPool(new ReceiveMsgFromRemoteCaller(cc, in));
CCResult ccResult = cc.call();
好了,CC当这里就介绍完了。