龙空技术网

3分钟代码自定义一套Android通用的跨组件服务调用框架

IT工匠 144

前言:

而今小伙伴们对“android javapoet”大致比较注意,小伙伴们都想要剖析一些“android javapoet”的相关知识。那么小编同时在网摘上网罗了一些关于“android javapoet””的相关资讯,希望你们能喜欢,看官们快快来了解一下吧!

概述

本文主要就组件化中服务实现类的实例化方法做简要探究,希望可以探索出一种简洁易用的组件化框架,本文到的主要技术有:

编译时注解javapoet的使用反射的使用

本文目录如下:

问题的引入

在软件开发中,当一款软件的规模和功能不断增多、丰富,原先的“一勺烩”架构往往显得捉襟见肘,为了便于团队协作、便于维护、便于升级,我们往往需要将一个软件划分为若干个模块(即我们所说的模块化),而这若干个模块又是依赖于很多个组件的(即我们所说的组件化),这里涉及了两个概念,即模块化和组件化,经常有读者反映搞不清楚这二者的区别,这里我帮大家总结了一下模块化与组件化的区别与联系:

以抖音APP为例,其可以这样进行模块和组件的划分:

如上图所示,按照业务,可以将抖音分为首页模块、直播模块、视频模块、消息模块4个模块(实际肯定比这个分的多,这里只是为了说明问题),每个模块完成了一定的业务逻辑,而这4个模块又是基于下面的视频播放组件、IO组件、网络组件等若干个组件来实现自己的业务逻辑的,这些组件反映在Android项目中就是若干个 ‘com.android.library’,组件为模块提供服务(Service),模块并不关心组件是如何实现服务的,只关心组件提供了什么服务,反映在代码上就是,使用组件的模块只知道组件暴露出来的接口,不清楚对应接口在组件中是由谁实现的(实现类是什么),这样就有一个问题,组件的使用者在使用组件的时候应该如何实例化对象?因为接口是不能被实例化的,比如:

组件A提供服务IServiceA,将接口IServiceA暴露给外界,IServiceA的实现类为ServiceAImpl,组件的使用者模块B要使用组件A提供的服务IServiceA。按照常理来说,最直接的方法就是模块B使用new关键字实例化一个ServiceAImpl类的对象,然后基于这个对象调用IServiceA提供的各个功能(方法),但是问题是模块B只知道自己所需要的功能是由组件A的IServiceA接口定义的,它根本不知道IServiceA具体的实现类是什么,而IServiceA是不能使用new关键字进行对象的实例化的的的(因为不能实例化接口),那么怎么解决这个问题呢?

问题的解决思路

我们可以使用编译时注解,为服务的实现类添加我们自定义的注解,然后在编译时对所有添加我们自定义注解的类进行遍历,将遍历的结果写入文件,然后在运行时将文件读出,这样模块就可以知晓服务的具体实现类,自然可以成功实例化,整体思路如下图所示:

具体到代码层面,思路如下:

1:定义注解Component,用来修饰服务(接口)的实现类,其接收一个Class类型的参数,这个参数的意义是该类实现的服务(接口)的Class类

2:自定义AbstractProcessor类,在该类的process()方法中遍历被@Component修饰的类(称作Service类),并拿到注解对应的参数即Service类对应的服务接口(称作IService),然后将IService-Service作为键值对添加到Map中,最终解析完所有被@Component注解修饰的类后将Map的内容转换为json字符串(记为jsonString)

3:使用代码生成一个ComponentResource类:

3.1:将上一步生成的jsonString作为ComponentResource的成员变量

3.2:在ComponentResource类的构造方法中将jsonString解析为map,并且将map作为ComponentResource的成员变量

3.3:为ComponentResource类生成String getServiceImplUrl(String iServiceClassName)方法,即根据IService的类名查找到其实现类的URL并返回,方法的内容自然是返回map.get(iServiceClassName)

4:定义ServiceManager类,即我们框架的管理类:

4.1:定义register(Application application)方法,利用反射实例化编译期间生成的ComponentResource类(生成的实例作为ServiceManager的成员变量)

4.2:定义getService(Class<T> clazz)方法,在该方法中首先调用ComponentResource.getServiceImplUrl(clazz.getCanonicalName())方法获取当前IService对应实现类的URL,然后利用反射实例化该URL对应的类,然后将实例返回

5: 在应用启动的时候(比如Application的onCreate方法中),手动调用ServiceManager的register(Application application)方法

6:当某模块需要IService实现类的实例时,调用ServiceManager的getService(Class<T> clazz)方法,获得接口类对应的实现类的实例

将以上步骤形象化可以表示为:

代码实现

定义注解

注解的定义很简单,需要注意的是,注解的@Target要设置为TYPE,因为我们定义的这个注解是要应用到类上的,另外一点,我们定义的这个注解接收一个Class类型的参数,我们希望将接口类的class传递进来,以便下一步生成键值对:

定义AbstractProcessor类

要想在编译时解析被特定注解修饰的类,我们就需要使用AbstractProcessor,该类在编译时会被自动执行,java api会调用AbstractProcessor的process()方法并传入相关参数,我们只需要在process方法中找到被@Component注解修饰的类,并且拿到注解中的参数即可:

我们来一步一步进行代码的实现,首先是寻找并解析被@Component注解修饰的类,并将类和接口以键值对的形式塞进map中,我们定义一个findAndParseTargets()方法进行实现:

generateJsonString()方法的主要作用是将map转为json字符串,是借助gson实现的,代码比较简单,这里不再赘述,然后我们看看brawJava()方法的实现:

这样就可以生成一个包含IService与ServiceImpl键值对的ComponentResource类文件,就像这样:

然后我们需要定义一个ServiceManager来将用户和ComponentResource连接起来:

在使用的时候我们只需要:

即可获得IServiceB的实现类的实例。

改进

上面的实现思路是先将Map转化为json String,然后写入ComponentResource类文件,当实例化ComponentResource的时候再将json String解析为Map,这样由于json的解析比较耗时,势必导致编译速度过慢,改进方法是略去map与String互转的步骤,直接将map的内容写在ComponentResource的构造方法中,即将生成ComponentResource的brewJava()方法改进为:

最后生成的代码就像这样:

我们来测试一下改进前后的区别:

运行效果如下:

可见改进后的速度比改进前快了不止一点点。

待完成

1:为@Component增加参数,使其适应一个借口多个实现类的场景下,按照用户传入参数的不同实例化不同的实现类的返回给用户

总结

本文探索了一种跨组件实例化方法的解决方案,各位读者如果有改进建议或者意见欢迎积极与我联系和交流。

标签: #android javapoet