前言
在我们进行组件化开发的时候,最需要解决的就是组件间通信,一般来说,我们一般将一个组件分为两个工程,一个只包含组件对外提供的接口另一个则是实现,当然,为了彻底解耦,我们应该让外部调用者完全感知不到实现,在开发时候时候也无法引用到实现中的其他方法,这就需要我们在编译期间动态添加依赖,由于本篇博客的重点不在这里,所以不多介绍,具体可以查看这里。
通过上面的介绍我们知道,当开发的时候,我们只能知道接口而不能知道实现类,所以无法访问,当然国内不缺少牛人,所以解决方案大致有以下几种。
1、遍历dex文件找到实现类并动态注册
代表:ARouter默认实现
缺点:遍历dex可能由于Android版本的不同导致找不到实现类并且会增加耗时,并且使用的反射去实例化对象
2、修改字节码动态插入
代表:AutoRegister
缺点:可能会导致打包慢
优点:兼容性好一劳永逸,性能高
3、使用AutoService+ServiceLoader,本篇博客主要就是介绍此方案
缺点:使用的反射去实例化对象
优点:易配置,易调试,上手快
AutoService
AutoService是Google开源的用来方便生成符合ServiceLoader规范的开源库,使用非常的简单。
Github地址:https://github.com/google/auto/tree/master/service
引入:
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc5' implementation 'com.google.auto.service:auto-service-annotations:1.0-rc5'
首先定义一个接口
public interface ISay { String say(); }
然后使用AutoService注解在实现上面声明即可。下面的例子展示了同时声明多个接口
@AutoService({ISay.class, IFly.class}) public class BirdSay implements ISay, IFly { private static final String TAG = "BirdSay"; @Override public String say() { return "i am bird"; } @Override public void fly() { Log.i(TAG, "fly: "); } }
然后就轮到我们的ServiceLoader出场了
ServiceLoader<ISay> load = ServiceLoader.load(ISay.class); for (ISay item : load) { Log.i(TAG, "onCreate: " + next.say()); }
这样我们就能获得具体的实现类了,我们也可以将此段逻辑封装为一个工具类。
原理
在我们使用AutoService注解以后到底发生了什么呢?
首先会在我们的apk的META-INF文件夹中新建一个services文件夹,然后在下面为我们生成一个配置文件(app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\META-INF\services\)。
文件名为接口的全包名+类名,里面的内容为实现类的全包名+类名,一行为一个实现类,可以同时有多行。
这就是AutoService为我们做的全部的处理了。类的加载其实使用的java的类加载机制。使用ClassLoader读取到META-INF\services下指定文件的内容,然后使用反射去实例化对象。具体的可以自行查看ServiceLoader的源码。
private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { //PREFIX 为 "META-INF/services/" //service 为 传入的class对象 //fullName就是文件相对路径META-INF\services\com.corbin.autoservice.ISay String fullName = PREFIX + service.getName(); //使用ClassLoader获取 if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } //将指定文件里面的每一行解析出来 pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; }
其他
1、如果项目中开启了混淆,那么需要保证实现类不被删除以及类名不被混淆,但是成员变量可以被混淆
2、同一个接口可以有多个实现,多个实现也可以在不同的Module中,Android Studio会自动为我们合并生成的文件的内容
3、最好将Service加载封装起来,便于切换实现
4、ServiceLoader不会为我们缓存对象,每一次使用ServiceLoader都会创建一个实例,这点需要注意
参考地址