2024火爆来袭!阿里P8大牛斥百万巨资录制Android高级工程师进阶系列视频-教你打造一个Android组件化开发框架!

二者结合,可以做一个gradle插件,在编译时自动扫描所有组件类(IComponent接口实现类),然后修改字节码,生成代码调用扫描到的所有组件类的构造方法将其注册到一个组件管理类(ComponentManager)中,生成组件名称与组件对象的映射表。

此gradle插件被命名为:AutoRegister,现已开源,并将功能升级为编译时自动扫描任意指定的接口实现类(或类的子类)并自动注册到指定类的指定方法中。只需要在app/build.gradle中配置一下扫描的参数,没有任何代码侵入,原理详细介绍:

http://blog.csdn.net/cdecde111/article/details/78074692

如何兼容同步/异步方式调用组件?

通过实现 java.util.concurrent.Callable 接口同步返回结果来兼容同步/异步调用:

  • 同步调用时,直接调用 CCResult result = Callable.call() 来获取返回结果

  • 异步调用时,将其放入线程池中运行,执行完成后调用回调对象返回结果: IComponentCallback.onResult(cc, result)

ExecutorService.submit(callable)

如何兼容同步/异步方式实现组件?

调用组件的 onCall方法 时,可能需要异步实现,并不能同步返回结果,但同步调用时又需要返回结果,这是一对矛盾。

此处用到了 Object 的wait-notify机制,当组件需要异步返回结果时,在CC框架内部进行阻塞,等到结果返回时,通过notify中止阻塞,返回结果给调用方

注意,这里要求在实现一个组件时,必须确保组件一定会回调结果,即:需要确保每一种导致调用流程结束的逻辑分支上(包括if-else/try-catch/Activity.finish()-back键-返回按钮等等)都会回调结果,否则会导致调用方一直阻塞等待结果,直至超时。类似于向服务器发送一个网络请求后服务器必须返回请求结果一样,否则会导致请求超时。

如何进行跨进程组件任意功能的调用(不只是启动Activity)?

市面上常见的组件化框架采用的通信解决方案有:

URLScheme(例如:ActivityRouter、ARouter等)

  • 优势有:基因中自带支持从webview中调用不用互相注册(不用知道需要调用的app的进程名称等信息)

  • 劣势有:只能单向地给组件发送信息,适用于启动Activity和发送指令,不适用于获取数据(例如:获取用户组件的当前用户登录信息)需要有个额外的中转Activity来统一处理URLScheme

如果设备上安装了多个使用相同URLScheme的app,会弹出选择框(多个组件作为app同时安装到设备上时会出现这个问题)

无法进行权限设置,无法进行开关设置,存在安全性风险

AIDL (例如:ModularizationArchitecture)

  • 优势有:可以传递Parcelable类型的对象效率高

可以设置跨app调用的开关

  • 劣势有:调用组件之前需要提前知道该组件在那个进程,否则无法建立ServiceConnection组件在作为独立app和作为lib打包到主app时,进程名称不同,维护成本高

设计此功能时,我的出发点是:作为组件化开发框架基础库,想尽量让跨进程调用与在进程内部调用的功能一致,对使用此框架的开发者在切换app模式和lib模式时尽量简单,另外需要尽量不影响产品安全性。因此,跨组件间通信实现的同时,应该满足以下条件:

  • 每个app都能给其它app调用

  • app可以设置是否对外提供跨进程组件调用的支持

  • 组件调用的请求发出去之后,能自动探测当前设备上是否有支持此次调用的app

  • 支持超时、取消

基于这些需求,我最终选择了 BroadcastReceiver + Service + LocalSocket 来作为最终解决方案:

如果 appA 内发起了一个当前app内不存在的组件:Component1,则建立一个LocalServerSocket,同时发送广播给设备上安装的其它同样使用了此框架的 app,同时,若某个 appB 内支持此组件,则根据广播中带来的信息与 LocalServerSocket 建立连接,并在 appB 内调用组件 Component1,并将结果通过 LocalSocket 发送给 appA。

BroadcastReceiver 是 android 四大组件之一,可以设置接收权限,能避免外部恶意调用。并且可以设置开关,接收到此广播后决定是否响应(假装没接收到…)。

之所以建立 LocalSocket 链接,是为了能继续给这次组件调用请求发送超时和取消的指令。

用这种方式实现时,遇到了3个问题:

  • 由于广播接收器定义在基础库中,所有app内都有,当用户在主线程中同步调用跨app的组件时,调用方主线程被阻塞,广播接收器也在需要主线程中运行,导致广播接收器无法运行,直至timeout,组件调用失败。将广播接收器放到子进程中运行问题得到解决

  • 被调用的app未启动或被手动结束进程,遇到广播接收不到的问题这个问题暂时未很好的解决,但考虑到组件化开发只在开发期间需要用到跨进程通信,开发者可以通过手动在系统设置中给对应的app赋予自启动权限来解决问题

  • 跨进程调用时,只能传递基本数据类型,无法获取Fragment等java对象这个问题在app内部调用时不存在,app内部来回传递的都是Map,可以传递任何数据类型。但由于进程间通信是通过字符串来回发送的,暂时支持不了非基本数据类型,未来可以考虑支持Serializable

组件如何更方便地在application和library之间切换?

关于切换方式在网络上有很多文章介绍,基本上都是一个思路:在 module 的 build.gradle 中设置一个变量来控制切换 apply plugin: ‘com.android.application’ 或 apply plugin: ‘com.android.library’ 以及 sourceSets 的切换。

为了避免在每个 module 的 build.gradle 中配置太多重复代码,我做了个封装,默认为 library模式,提供2种方式切换为application模式:在module的build.gradle中添加 ext.runAsApp = true 或在工程根目录中 local.properties 中添加 module_name=true

使用这个封装只需一行代码:

//将原来的 apply plugin: 'com.android.application’或apply plugin: ‘com.android.library’

//替换为下面这一行

apply from: ‘https://raw.githubusercontent.com/luckybilly/CC/master/cc-settings.gradle’

如何实现startActivityForResult?

android 的 startActivityForResult 的设计也是为了页面传值,在CC组件化框架中,页面传值根本不需要用到 startActivityForResult,直接作为异步实现的组件来处理(在原来 setResult 的地方调用 CC.sendCCResult(callId, ccResult),另外需要注意:按back键及返回按钮的情况也要回调结果)即可。

如果是原来项目中存在大量的 startActivityForResult 代码,改造成本较大,可以用下面这种方式来保留原来的 onActivityResult(…) 及 activity 中 setResult 相关的代码:

  • 在原来调用 startActivityForResult 的地方,改用CC方式调用,将当前context传给组件

CC.obtainBuilder(“demo.ComponentA”)

.setContext(context)

.addParams(“requestCode”, requestCode)

.build()

.callAsync();

  • 在组件的 onCall(cc)方法 中用 startActivityForResult 的方式打开 Activity

@Override

public boolean onCall(CC cc) {

Context context = cc.getContext();

Object code = cc.getParams().get(“requestCode”);

Intent intent = new Intent(context, ActivityA.class);

if (!(context instanceof Activity)) {

//调用方没有设置context或app间组件跳转,context为application

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

}

if (context instanceof Activity && code != null && code instanceof Integer) {

((Activity)context).startActivityForResult(intent, (Integer)code);

} else {

context.startActivity(intent);

}

CC.sendCCResult(cc.getCallId(), CCResult.success());

return false;

}

如何阻止非法的外部调用?

为了适应不同需求,有2个安全级别可以设置:

  • 权限验证(给进程间通信的广播设置权限,一般可设置为签名级权限校验),步骤如下:

  • 新建一个module

  • 在该module的build.gradle中添加对基础库的依赖,如: compile ‘com.billy.android:cc:0.3.0′

  • 在该module的src/main/AndroidManifest.xml中设置权限及权限的级别,参考component_protect_demo

  • 其它每个module都额外依赖此module,或自定义一个全局的cc-settings.gradle,参考cc-settings-demo-b.gradle

  • 外部调用是否响应的开关设置(这种方式使用起来更简单一些)

  • 在Application.onCreate()中调用CC.enableRemoteCC(false)可关闭响应外部调用

为了方便开发者接入,默认是开启了对外部组件调用的支持,并且不需要权限验证。app正式发布前,建议调用 CC.enableRemoteCC(false) 来关闭响应外部调用本app的组件。

如何与Activity、Fragment的生命周期关联起来

背景:在使用异步调用时,由于callback对象一般是使用匿名内部类,会持有外部类对象的引用,容易引起内存泄露,这种内存泄露的情况在各种异步回调中比较常见,如Handler.post(runnable)、Retrofit的Call.enqueue(callback)等。

为了避免内存泄露及页面退出后取消执行不必要的任务,CC添加了生命周期关联的功能,在onDestroy方法被调用时自动cancel页面内所有未完成的组件调用

  • Activity生命周期关联

在api level 14 (android 4.0)以上可以通过注册全局activity生命周期回调监听,在onActivityDestroyed方法中找出所有此activity关联且未完成的cc对象,并自动调用取消功能:

application.registerActivityLifecycleCallbacks(lifecycleCallback);

  • android.support.v4.app.Fragment生命周期关联

support库从25.1.0开始支持给fragment设置生命周期监听:

FragmentManager.registerFragmentLifecycleCallbacks(callback)

可在其 onFragmentDestroyed 方法中取消未完成的cc调用

  • andorid.app.Fragment生命周期关联(暂不支持)

CC执行流程详细解析

组件间通信采用了组件总线的方式,在基础库的组件管理类(ComponentMananger)中注册了所有组件对象,ComponentMananger通过查找映射表找到组件对象并调用。

当ComponentMananger接收到组件的调用请求时,查找当前app内组件清单中是否含有当前需要调用的组件

有:执行App内部CC调用的流程:

没有:执行App之间CC调用的流程

组件的同步/异步实现和组件的同步/异步调用原理

组件实现时,当组件调用的相关功能结束后,通过CC.sendCCResult(callId, ccResult)将调用结果发送给框架

IComponent实现类(组件入口类)onCall(cc)方法的返回值代表是否异步回调结果:

  • true: 将异步调用CC.sendCCResult(callId, ccResult)

  • false: 将同步调用CC.sendCCResult(callId, ccResult)。意味着在onCal方法执行完之前会调用此方法将结果发给框架

当IComponent.onCall(cc)返回 false 时,直接获取CCResult并返回给调用方

当IComponent.onCall(cc)返回true时,将进入wait()阻塞,知道获得CCResult后通过notify()中止阻塞,继续运行,将CCResult返回给调用方

通过ComponentManager调用组件时,创建一个实现了java.util.concurrent.Callable接口ChainProcessor类来负责具体组件的调用

  • 同步调用时,直接执行ChainProcessor.call()来调用组件,并将CCResult直接返回给调用方

  • 异步调用时,将ChainProcessor放入线程池中执行,通过IComponentCallback.onResult(cc, ccResult)将CCResult回调给调用方

执行过程如下图所示:

自定义拦截器(ICCInterceptor)实现原理

所有拦截器按顺序存放在调用链(Chain)中

在自定义拦截器之前有1个CC框架自身的拦截器:

  • ValidateInterceptor

在自定义拦截器之后有2个CC框架自身的拦截器:

  • LocalCCInterceptor(或RemoteCCInterceptor)

  • Wait4ResultInterceptor

小结

有了这么多优秀的开发工具,可以做出更高质量的Android应用。

当然了,“打铁还需自身硬”,想要写出优秀的代码,最重要的一点还是自身的技术水平,不然用再好的工具也不能发挥出它的全部实力。

在这里我也分享一份大佬自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点。

总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值