概述

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 printers = ServiceLoader.load(IPrinter.class);

for (IPrinter printer : printers) {

printer.print();

}

ServiceLoader.load

ServiceLoader 的原理解析从 load 方法开始:

public static ServiceLoader load(Class service) {

// 获取当前线程的类加载器

ClassLoader cl = Thread.currentThread().getContextClassLoader();

return ServiceLoader.load(service, cl);

}

public static ServiceLoader load(Class service, ClassLoader loader) {

// 创建 ServiceLoader 实例

return new ServiceLoader<>(service, loader);

}

ServiceLoader实例创建

private LinkedHashMap providers = new LinkedHashMap<>();

private ServiceLoader(Class 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 iterator() {

return new Iterator() {

Iterator> 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 {

Class service;

ClassLoader loader;

Enumeration configs = null;

Iterator pending = null;

String nextName = null;

private LazyIterator(Class 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,无法精确拿到具体的实现类对象

需要读取解析配置文件,性能损耗

以上就是Android-SPI学习笔记的详细内容,更多关于Android-SPI的资料请关注脚本之家其它相关文章!

android spi读写不通,Android-SPI学习笔记相关推荐

  1. Android 《第一行代码》学习笔记

    目录 第一章 准备工作 一.Android 系统架构 二.Android 开发特点 1.四大组件 2.系统控件丰富 3.SQLite数据库 4.多媒体功能强大 5.地理位置定位 三.日志工具的使用 第 ...

  2. android 注册动态广播 注销_Android学习笔记(十一)BroadcastReceiver动态注册、注销示例...

    在上一篇博文中简单介绍了一下BroadcastReceiver的相关知识点,本篇举一个在代码中动态的注册.注销BroadcastReceiver的栗子. 1.首先创建一个MyReceiver并继承Br ...

  3. android如何适配,Android适配全攻略(学习笔记总结)

    Android适配全攻略(学习笔记总结) 一.为什么要进行屏幕适配 某厂商统计如下数据 2012年,支持Android的设备共有3997种 2013年,支持Android的设备共有11868种 201 ...

  4. 《Android平台开发之旅》学习笔记

    第三章:Android应用程序组件 3.2 Android应用程序组件 Activity 活动--形象大使 Service 服务--老黄牛 BroadcastReceiver 广播接收器 --倾听者 ...

  5. android 点击edittext全选,学习笔记--EditText点击全选效果

    需要上,EditText是有内容的,用户点击后就全选,最终达到方便编辑的效果. 方案一: 百度了一下,原来的思路是通过点击事件实现,但是效果很差,有时间点击中间能全选,有时候只是选中光标而已.估计是第 ...

  6. 《黑马程序员Android移动应用基础教程》学习笔记(2)

    文章目录 Ch 2 Android常见界面布局 2.1 View视图 2.2 界面布局编写方式 2.2.1 在XML文件中编写布局 2.2.2 在Java代码中编写布局 2.3 常见界面布局 2.3. ...

  7. eeprom stm8l 擦除 读写_STM8L探索套件学习笔记-EEPROM(十一)

    原标题:STM8L探索套件学习笔记-EEPROM(十一) 上节将到官方例程使用 EEPROM存储外围电路消耗的电流值,今天我们对 STM8L的EEPROM介绍下.STM8L带有的32K的FLASH和1 ...

  8. android源代码 abi,Android内核源码Abi目录学习笔记

    好记性不如烂笔头,今天要学习的是Android中Abi目录下的代码.下面是基本的学习的笔记的汇总. 首先是include中的头文件的说明. 在cxxabi的头文件中主要需要掌握下面的几个点: 1.这个 ...

  9. Android的LitePal开源数据库框架学习笔记

    LitePal是一款非常好用的数据库开源框架,它采用了对象关系映射(ORM)的模式,使用这个框架从创建数据库到操作数据库都变成极其简单,那么我们就开始学习吧.它的开源地址:https://github ...

最新文章

  1. jquery radio 取值
  2. RStudio中为R创建Notebook文件
  3. Jsoup:用Java也可以爬虫,怎么使用Java进行爬虫,用Java爬取网页数据,使用Jsoup爬取数据,爬虫举例:京东搜索
  4. IBASE read hierarchy
  5. MFC界面编程新思路--模仿MATLAB式的界面
  6. 使用RxJava从多个数据源获取数据
  7. python中decode的用法_python中list怎么使用decode方法设置编码
  8. 配置接口IP地址并通过默认路由配置实现全网互通。
  9. Java中getclass(),class()与iinstanceof的区别与联系
  10. JS 微信emoji表情数组
  11. 阿里datav地图json地址
  12. python求不规则图形面积_python计算不规则图形面积算法
  13. 克服弱点,愈发完美-自我篇——《人性的弱点》读后感
  14. 精准填报志愿、一分不浪费……靠谱吗?
  15. Joint Pose and Expression Modeling for Facial Expression Recognition 论文翻译
  16. HTG审核CODE键盘:老式建筑满足现代设施
  17. 刘备学Android目录
  18. 中国探月工程首席科学家欧阳自远:“嫦娥”月面图千真万确
  19. AI留给人类的时间已不多:《未来简史》作者赫拉利Nature来信
  20. 隐私计算头条周刊(12.11-12.17)

热门文章

  1. 舍弃Python,为什么知乎选用Go重构推荐系统?
  2. 洛谷P3803 【模板】多项式乘法(FFT)
  3. C# 计算代码执行时间
  4. [IOS]开源库RegexKitLite正则表达式的使用
  5. Debian Gnu/Linux 9 安装remmina软件过程记录
  6. 数据库连接池之_c3p0
  7. 【容器云】十分钟快速构建 Influxdb+cadvisor+grafana 监控
  8. 公司邮箱发邮件的util类
  9. 广州市岑村教练场考科目二,惊险通过,经验总结
  10. [iOS]swift版内购