Android-SPI学习笔记
作者:苍耳 发布时间:2022-05-15 17:35:33
目录
概述
基本使用
1. 在低层 module_common 中声明服务
2. 在上层 module 中实现服务
3. 在其它上层 module 中使用服务
ServiceLoader.load
ServiceLoader实例创建
LazyIterator
总结
SPI的优点
SPI的缺点
概述
SPI(Service Provider Interface, 服务提供方接口),服务通常是指一个接口或者一个抽象类,服务提供方是对这个接口或者抽象类的具体实现,由第三方来实现接口提供具体的服务。通过解耦服务与其具体实现类,使得程序的可扩展性大大增强,甚至可插拔。基于服务的注册与发现机制,服务提供者向系统注册服务,服务使用者通过查找发现服务,可以达到服务的提供与使用的分离。
可以将 SPI 应用到 Android 组件化中,很少直接使用 SPI,不过可基于它来扩展其功能,简化使用步骤。
基本使用
1. 在低层 module_common 中声明服务
public interface IPrinter {
void print();
}
2. 在上层 module 中实现服务
// module_a -- implementation project(':module_common')
// com.hearing.modulea.APrinter
public class APrinter implements IPrinter {
@Override
public void print() {
Log.d("LLL", "APrinter");
}
}
// src/main/resources/META-INF/services/com.hearing.common.IPrinter
// 可以配置多个实现类
com.hearing.modulea.APrinter
// ----------------------------------------------------------------//
// module_b -- implementation project(':module_common')
// com.hearing.moduleb.BPrinter
public class BPrinter implements IPrinter {
@Override
public void print() {
Log.d("LLL", "BPrinter");
}
}
// src/main/resources/META-INF/services/com.hearing.common.IPrinter
com.hearing.moduleb.BPrinter
3. 在其它上层 module 中使用服务
// implementation project(':module_common')
ServiceLoader<IPrinter> printers = ServiceLoader.load(IPrinter.class);
for (IPrinter printer : printers) {
printer.print();
}
ServiceLoader.load
ServiceLoader 的原理解析从 load 方法开始:
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前线程的类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
// 创建 ServiceLoader 实例
return new ServiceLoader<>(service, loader);
}
ServiceLoader实例创建
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
reload();
}
// Clear this loader's provider cache so that all providers will be reloaded.
public void reload() {
providers.clear();
// 创建了一个懒迭代器
lookupIterator = new LazyIterator(service, loader);
}
LazyIterator
ServiceLoader 实现了 Iterable 接口,可以使用 iterator/forEach 方法来迭代元素,其 iterator 方法实现如下:
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext()) return true;
return lookupIterator.hasNext();
}
public S next() {
// 如果 knownProviders 缓存中已经存在,则直接返回,否则加载
if (knownProviders.hasNext()) return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
上面使用了懒加载的方式,不至于一开始便去加载所有服务实现,否则反射影响性能。LazyIterator 类如下:
private static final String PREFIX = "META-INF/services/";
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 获取服务配置文件
String fullName = PREFIX + service.getName();
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;
}
private S nextService() {
if (!hasNextService()) throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 反射通过类加载器加载指定服务
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
// throw Exception
}
if (!service.isAssignableFrom(c)) {
// throw Exception
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
// throw Exception
}
throw new Error(); // This cannot happen
}
public boolean hasNext() {
return hasNextService();
}
public S next() {
return nextService();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
总结
ServiceLoader 的原理比较简单,其实就是使用一个懒迭代器,用时加载的方式可以减少性能损耗,在加载新服务的时候通过解析服务配置文件获取配置的服务,然后通过类加载器去加载配置的服务实现类,最后将其实例返回。
SPI的优点
只提供服务接口,具体服务由其他组件实现,接口和具体实现分离。
SPI的缺点
配置过于繁琐
具体服务的实例化由ServiceLoader反射完成,生命周期不可控
当存在多个实现类对象时,ServiceLoader只提供了一个Iterator,无法精确拿到具体的实现类对象
需要读取解析配置文件,性能损耗
来源:https://ljd1996.github.io/2020/09/16/Android-SPI%E7%AC%94%E8%AE%B0/


猜你喜欢
- 本文实例讲述了java读取properties文件的方法。分享给大家供大家参考。具体实现方法如下:package com.test.demo
- using System;using System.Collections;using System.IO;namespace Consol
- 作者: juky_huang 事件的简单解释: 事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交互(例如
- 前言众所周知Java提供File类,让我们对文件进行操作,下面就来简单整理了一下File类的用法。 话不多说了,来一起看看详细的介绍吧1.基
- ApplicationContext简述ApplicationContext代表IOC容器,在SpringIOC容器中读取Bean配置创建B
- 1.屏幕是否亮屏:PowerManager powerManager = (PowerManager) context.getS
- 本文实例为大家分享了Unity实现俄罗斯方块第3部分,供大家参考,具体内容如下解决穿透问题逻辑部分1、在物体进行移动的过程中更新格子的信息,
- 在ibatis的xml文件里,我们去写sql语句,对应mapper类的方法,这些sql语句与控制台上没什么两样,但在有些功能上需要注意,如w
- Hadoop环境搭建详见此文章https://www.jb51.net/article/33649.htm。我们已经知道Hadoop能够通过
- 这个小游戏是我和我姐们儿的JAVA课程设计,也是我做的第一个JAVA项目,适合初学者,希望能帮到那些被JAVA课设所困扰的孩纸们~~~一、该
- 实习一段时间了,一直想写点技术总结,但一直没找到合适的主题。刚好,最近版本中我负责的模块遇到了个线程相关问题(之前一直画界面,做点基础功能,
- JVM自带的类加载器:其关系如下:其中,类加载器在加载类的时候是使用了所谓的“父委托”机制。其中,除了根类加载器以外,其他的类加载器都有且只
- 一,栈1,概念在我们软件应用 ,栈这种后进先出数据结构的应用是非常普遍的。比如你用浏 览器上网时不管什么浏览器都有 个"后退&qu
- 本文的目的是把json串转成map键值对存储,而且只存储叶节点的数据比如json数据如下:{responseHeader:{status:0
- 定义:动态给一个对象添加一些额外的职责,就象在墙上刷油漆.使用Decorator模式相比用生成子类方式达到功能的扩充显得更为灵活。设计初衷:
- 在X86的环境下,var wParam = (int)msg.WParam;工作得很好。在X64的环境下,快速滚动滚轮会出现msg.WPar
- 1.选择一个WebService接口作测试假设 WebService url 为 http://ws.webxml.com.cn/WebSe
- 实际开发中我们需要很多情况需要判断某个activity是否位于栈顶,也许会给新的小伙伴带来困扰,那么直接上代码吧,也没几行/** * *
- 1.使用API设置主题如下所示,在Activity中使用setThemesetTheme(R.style.MyTheme1);2.调用API
- 1、pom.xml文件添加distributionManagement节点。模块项目中如果存在父子项目,且父子项目的jar包都需要上传到 *