组件化开发的新选择【AutoService】

/ 0评 / 11

前言

在我们进行组件化开发的时候,最需要解决的就是组件间通信,一般来说,我们一般将一个组件分为两个工程,一个只包含组件对外提供的接口另一个则是实现,当然,为了彻底解耦,我们应该让外部调用者完全感知不到实现,在开发时候时候也无法引用到实现中的其他方法,这就需要我们在编译期间动态添加依赖,由于本篇博客的重点不在这里,所以不多介绍,具体可以查看这里

通过上面的介绍我们知道,当开发的时候,我们只能知道接口而不能知道实现类,所以无法访问,当然国内不缺少牛人,所以解决方案大致有以下几种。

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都会创建一个实例,这点需要注意

参考地址

https://mp.weixin.qq.com/s/AyR4irlka77aJ8KdEdYbrw

https://github.com/google/auto/tree/master/service

 

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注