转载注明出处:http://www.jianshu.com/p/a9a8ddcd265b
1. 简介
SPI全称是Service Provider Interface,服务提供方接口,服务通常是指一个接口或者一个抽象类,服务提供方是对这个接口或者抽象类的具体实现,由第三方来实现接口提供具体的服务。SPI提供了一种动态的对应用程序进行扩展的机制,通常用作框架服务的拓展或者可替换的服务组件,但是在Android中并没有广泛的使用。
SPI运用场景主要有:
- Java Database Connectivity
- Java Cryptography Extension
- Java Naming and Directory Interface
- Java API for XML Processing
- Java Business Integration
- Java Sound
- Java Image I/O
- Java File Systems
官方介绍文档:https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html
2. SPI机制
- 在resources/META-INF/services/目录中创建以
服务全限定名
命名的文件,该文件内容为服务的具体实现类的全限定名
,文件中可以写多个服务的具体实现类 - 使用ServiceLoader类动态加载服务的具体实现类
- 服务具体的实现类必须有一个不带参数的构造方法
- 如果服务的具体实现类在Jar包中,则需要放在主程序的classPath中
3. SPI的使用
SPI在Android工程中很少被使用到,主要因为SPI是针对厂商或者第三方服务商设置的机制,在平常的项目中很少会用到SPI。在业务越来越复杂的今天,很多应用趋向于组件化,项目中会依赖很多组件,这样会有一些问题,主工程和组件怎么通信,组件之间又怎么通信。当然有很多可以解决通信的方法,这里举个SPI例子。
图-1 举例示意图主工程中依赖有三个组件,分别提供三个服务ABC,在用户切换后台后,主程序想要关闭定位,但是由于服务ABC可能对定位信息变化非常敏感,需要由服务的具体场景来告诉主程序是否能够关闭定位。
3.1 定义接口或者抽象类
定义接口
public interface IService {
boolean keep();
}
3.2 实现接口
定义三个服务实现该接口,简化逻辑直接返回。
public class AService implements IService {
@Override
public boolean keep() {
return false;
}
}
public class BService implements IService {
@Override
public boolean keep() {
return false;
}
}
public class CService implements IService {
@Override
public boolean keep() {
return true;
}
}
3.3 创建spi描述文件
在工程的main目录下新建目录resources/META-INF/services
,以服务接口名为文件名新建spi描述文件,内容为具体的服务实现类权限定名,可以有多个。
文件内容如下:
com.spi.demo.AService
com.spi.demo.BService
com.spi.demo.CService
3.4 调用具体服务
使用ServiceLoader去加载具体服务类,然后遍历具体的实现类,ServiceLoader其实就是去META-INFO/services
目录下读取文件内容,然后实例化。
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ServiceLoader<IService> loader = ServiceLoader.load(IService.class);
Iterator<IService> iterator = loader.iterator();
boolean isKeepLoc = false;
while (iterator.hasNext()) {
if(iterator.next().keep()){
isKeepLoc = true;
break;
}
}
Toast.makeText(MainActivity.this, "Keep location : " + isKeepLoc, Toast.LENGTH_SHORT).show();
}
});
4. SPI的优点
只提供服务接口,具体服务由其他组件实现,接口和具体实现分离,同时能够通过系统的ServiceLoader拿到这些实现类的集合,统一处理。
5. SPI的缺点
- Java中SPI是随jar发布的,每个不同的jar都可以包含一系列的SPI配置,而Android平台上,应用在构建的时候最终会将所有的jar合并,这样很容易造成相同的SPI冲突,常见的问题是DuplicatedZipEntryException异常
- 读取SPI配置信息是在运行时从jar包中读取,由于apk是签过名的,在从jar中读取的时候,签名校验的耗时问题会造成性能损失
6. 优化思路
Java中使用ServiceLoader去读取SPI配置信息是在程序运行时,我们可以将这个读取配置信息提前,在编译时候就搞定,通过gradle插件,去扫描class文件,找到具体的服务类(可以通过标注来确定),然后生成新的java文件,这个文件中包含了具体的实现类。
这样程序在运行时,就已经知道了所有的具体服务类,缺点就是编译时间会加长,自己需要重新写一套读取SPI信息、生成java文件等逻辑。
经过优化后,SPI已经偏离了原本的初衷,但是可以做更多的事,可以将业务服务分离,通过SPI找到业务服务入口,业务组件化,抽成单独的aar,独立成工程。
7. 总结
SPI是java中原有的机制,在Android中可以直接使用,但是有上述提到的一些缺点,我们可以参考Java中的SPI机制,结合Android的环境,输出一套新的SPI机制。
网友评论