概述 SPI(Service Provider Interface, 服务提供方接口),服务通常是指一个接口或者一个抽象类,服务提供方是对这个接口或者抽象类的具体实现,由第三方来实现接口提供具体的服务。通过解耦服务与其具体实现类,使得程序的可扩展性大大增强,甚至可插拔。基于服务的注册与发现机制,服务提供者向系统注册服务,服务使用者通过查找发现服务,可以达到服务的提供与使用的分离。
可以将 SPI 应用到 Android 组件化中,很少直接使用 SPI,不过可基于它来扩展其功能,简化使用步骤。
基本使用 1. 在低层 module_common 中声明服务
1 2 3 public interface IPrinter { void print () ; }
2. 在上层 module 中实现服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class APrinter implements IPrinter { @Override public void print () { Log.d("LLL" , "APrinter" ); } } com.hearing.modulea.APrinter public class BPrinter implements IPrinter { @Override public void print () { Log.d("LLL" , "BPrinter" ); } } com.hearing.moduleb.BPrinter
3. 在其它上层 module 中使用服务
1 2 3 4 5 ServiceLoader<IPrinter> printers = ServiceLoader.load(IPrinter.class); for (IPrinter printer : printers) { printer.print(); }
ServiceLoader.load ServiceLoader 的原理解析从 load 方法开始:
1 2 3 4 5 6 7 8 9 10 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) { return new ServiceLoader<>(service, loader); }
ServiceLoader实例创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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(); } public void reload () { providers.clear(); lookupIterator = new LazyIterator(service, loader); }
LazyIterator ServiceLoader 实现了 Iterable 接口,可以使用 iterator/forEach 方法来迭代元素,其 iterator 方法实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 () { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove () { throw new UnsupportedOperationException(); } }; }
上面使用了懒加载的方式,不至于一开始便去加载所有服务实现,否则反射影响性能。LazyIterator 类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 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) { } if (!service.isAssignableFrom(c)) { } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { } throw new Error(); } public boolean hasNext () { return hasNextService(); } public S next () { return nextService(); } public void remove () { throw new UnsupportedOperationException(); } }
总结 ServiceLoader 的原理比较简单,其实就是使用一个懒迭代器,用时加载的方式可以减少性能损耗,在加载新服务的时候通过解析服务配置文件获取配置的服务,然后通过类加载器去加载配置的服务实现类,最后将其实例返回。
SPI的优点
只提供服务接口,具体服务由其他组件实现,接口和具体实现分离。
SPI的缺点
配置过于繁琐
具体服务的实例化由ServiceLoader反射完成,生命周期不可控
当存在多个实现类对象时,ServiceLoader只提供了一个Iterator,无法精确拿到具体的实现类对象
需要读取解析配置文件,性能损耗
可以将这个读取配置的步骤通过 Gradle 插件放在编译期间搞定,可以找到具体的服务类(通过注解等),然后生成新的java文件,这个文件中包含了具体的实现类信息。SPI是java中原有的机制,在Android中可以直接使用,但是有上述提到的一些缺点,可以参考Java中的SPI机制,结合Android的环境,输出一套新的SPI机制。