Android-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,无法精确拿到具体的实现类对象
  • 需要读取解析配置文件,性能损耗

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

(0)

相关推荐

  • 解决Android原生定位的坑

    Android原生定位的代码网上已经很多了,就不贴出来. 简单了解下: GPS_PROVIDER:通过手机内置的GPS芯片,利用卫星获取定位信息.位置监听.卫星状态监听很耗电且室内定位很不准确. NETWORK_PROVIDER:网络定位通过基站和WiFi节点,利用节点id在定位数据服务器查询位置信息.但是国内网络不允许,且有消息称Google已不提供该服务.so网上出现的此种方式获取定位信息不可用,也就是说NETWORK_PROVIDER在国内不可用. PASSIVE_PROVIDER:被动定

  • Android Handler内存泄漏原因及解决方案

    目录: 1.须知: 主线程Looper生命周期和Activity的生命周期一致. 非静态内部类,或者匿名内部类.默认持有外部类引用. 2.原因: Handler造成内存泄露的原因.非静态内部类,或者匿名内部类.使得Handler默认持有外部类的引用.在Activity销毁时,由于Handler可能有未执行完/正在执行的Message.导致Handler持有Activity的引用.进而导致GC无法回收Activity. 3.可能造成内存泄漏 匿名内部类: //匿名内部类 Handler handl

  • Android如何让APP无法在指定的系统版本上运行(实现方法)

    随着市面上越来越多三方APP的出现,某些手机厂商也开始对这些APP进行了安装限制或者运行限制,或者三方APP自身的版本过低,无法被特定的系统版本所支持. 今天我将要模拟实现一个"由于APP自身版本过低.导致无法在当前的系统版本上运行"的功能效果. 实现思路如下: 要获得APP的目标运行版本,也要知道系统的编译版本 通过版本比较,在进入该APP时,给用户做出"不支持运行"的提示 用户确认提示后,直接退出该APP 关键点是 targetSdkVersion 的使用,源码

  • Retrofit和OkHttp如何实现Android网络缓存

    前提: 没做过网络缓存这方面的功能,所以想学习下.上网看了很多的文章,也看了部分视频.想把自己的一些小小的心德分享一下.如何能够稍微帮助到别人,那对我来说就算是有意义了.废话不多说了.进入正题. 1.网路请求 网络请求用的是Retrofit.用过的人,都知道优点.没用过的人就照着图示或者Demo去写就好了,而且网上的文章一大堆,本人就不赘述了. 网络请求的写法 2.添加缓存 重点来了,在网上看了许多的缓存方法.到最后还是选择了,OkHttp添加拦截器的这种方法.貌似用这种方法的人最多. 先来两张

  • Android Filterable实现Recyclerview筛选功能的示例代码

    原先碰到筛选这种功能时,后端的接口都会让上传一个字段,根据字段来返回相应的数据.后来一次和别人对接时,接口直接返回全部数据,而且还要实现筛选功能.我...我说不就是一条sql语句的事,改接口多方便,我苦心劝导,然后被怼回来,切,不就是筛选嘛,求人不如自己搞. 1. 效果图 2. 思路 既然是筛选,那就少不了比较.也没有什么好的办法,无非就是循环对比,然后将适配器进行数据更新.页面刷新即可.但筛选的调用要方便,怎么比较才方便我们调用呢?偶然间看到了Filterable,使Adapter继承自该接口

  • Android 绕过反射黑名单的方法

    限制原理 Google 从 Android P 开始引入了针对非公开 API 的限制,这一点可以从 Native 相关的源码中找到限制的原理,从而从中找到解决办法,不过非必要原因不太建议去挑战这种限制,毕竟不清楚在后续的版本中会不会做限制,维护起来挺麻烦的. 在 Native 层有几个访问级别: class HiddenApiAccessFlags { public: enum ApiList { kWhitelist = 0, kLightGreylist, kDarkGreylist, kB

  • SpringBoot结合ProGuard实现代码混淆(最新版)

    前言 研究ProGuard也花了两天时间,其实最主要的时间花在前面proguard读取jar包的时候相关jar冲突的问题,但是总的来说不用拆分SpringBoot项目并且实现代码混淆已经很舒服了. ProGuard集成  1.maven的配置 具体配置如下: <build> <finalName>${artifactId}</finalName> <plugins> <plugin> <groupId>com.github.wveng

  • Android 一些常用的混淆Proguard

    一些公共的模板 ############################################# # # 对于一些基本指令的添加 # ############################################# # 代码混淆压缩比,在 0~7 之间,默认为 5,一般不做修改 -optimizationpasses 5 # 混合时不使用大小写混合,混合后的类名为小写 -dontusemixedcaseclassnames # 指定不去忽略非公共库的类 -dontskipno

  • Android开发学习笔记之通过API接口将LaTex数学函数表达式转化为图片形式

    本文将讲解如何通过codecogs.com和Google.com提供的API接口来将LaTeX数学函数表达式转化为图片形式.具体思路如下: (1)通过EditText获取用户输入的LaTeX数学表达式,然后对表达式格式化使之便于网络传输. (2)将格式化之后的字符串,通过Http请求发送至codecogs.com或者Google.com. (3)获取网站返回的数据流,将其转化为图片,并显示在ImageView上. 具体过程为: 1.获取并格式化LaTeX数学表达式 首先,我们在这个网站输入LaT

  • Android开发学习笔记 Gallery和GridView浅析

    一.Gallery的简介 Gallery(画廊)是一个锁定中心条目并且拥有水平滚动列表的视图,一般用来浏览图片,并且可以响应事件显示信息.Gallery还可以和ImageSwitcher组件结合使用来实现一个通过缩略图来浏览图片的效果. Gallery常用的XML属性 属性名称 描述 android:animationDuration 设置布局变化时动画的转换所需的时间(毫秒级).仅在动画开始时计时.该值必须是整数,比如:100. android:gravity 指定在对象的X和Y轴上如何放置内

  • Android动画学习笔记之补间动画

    本文实例为大家分享了Android补间动画展示的具体代码,供大家参考,具体内容如下 首先看看补间动画的共同属性: Duration:动画持续的时间(单位:毫秒)   fillAfter:设置为true,动画转化在动画被结束后被应用  fillBefore:设置为true,动画转化在动画开始前被应用  interpolator:动画插入器(加速.减速插入器)  repeatCount:动画重复的次数  repeatMode:顺序动画(restart)/倒序动画(reverse)  startOff

  • Android开发学习笔记 浅谈WebView

    第一种方法的步骤: 1.在要Activity中实例化WebView组件:WebView webView = new WebView(this); 2.调用WebView的loadUrl()方法,设置WevView要显示的网页:   互联网用:webView.loadUrl("http://www.google.com");   本地文件用:webView.loadUrl("file:///android_asset/XX.html"); 本地文件存放在:assets

  • Android学习笔记(二)App工程文件分析

    App工程文件分析 关于如何创建一个最简单的Android App请参照链接: < Android学习笔记(一)环境安装及第一个hello world > http://www.jb51.net/article/52593.htm 创建完的工程文件如下图所示,本文对一些主要的文件进行分析. src文件分析 App源文件如图: 打开源文件 MainActivity.java 可看到如下代码: 源码主要功能如下: App源文件目录 package com.example.firstapp; 导入A

  • Android学习笔记45之gson解析json

    JSON即JavaScript Object Natation, 是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,为Web应用开发提供了一种理想的数据交换格式. JSON对象: JSON中对象(Object)以"{"开始, 以"}"结束. 对象中的每一个item都是一个key-value对, 表现为"key:value"的形式, key-value对之间使用逗号分隔. 如:{"name":"coolxing

  • Android开发艺术探索学习笔记(七)

    第七章 Android动画深入分析 Android的动画分为三种:View动画,帧动画,属性动画.帧动画属于View动画. 7.1 View动画 View动画的作用对象是View,共有四种动画效果:平移(Translate),缩放(Scale),旋转(Rotate),透明度(Alpha). 7.1.1 View动画的种类 View动画的保存路径:res/anim/filename.xml.XML格式语法如下: <?xml version="1.0" encoding="

  • Android学习笔记之应用单元测试实例分析

    本文实例讲述了Android学习笔记之应用单元测试.分享给大家供大家参考,具体如下: 第一步:在AndroidManifest.xml中加入如下两段代码: <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.pccw" android:versionCode="1" android:versionName="1.0&qu

  • Android学习笔记之蓝牙功能

    本文实例为大家分享了Android学习笔记之蓝牙功能的具体代码,供大家参考,具体内容如下 蓝牙:短距离无线通讯技术标准.蓝牙协议分为4层,即核心协议层.电缆替代协议层.电话控制协议层和其他协议层.其中核心协议层包括基带.链路管理.逻辑链路控制和适应协议四部分.链路管理(LMP)负责蓝牙组件间的建立.逻辑链路控制与适应协议(L2CAP)位于基带协议层上,属于数据链路层,是一个高层传输和应用层协议屏蔽基带协议的适配协议. 1).第一种打开蓝牙的方式: Intent enableIntent = ne

  • Android学习笔记(二)之电话拨号器

    目前Android已经在只能手机市场已经具有强大的霸主地位,也吸引了越来越多的追捧者.Android的学习也越来越火.但是,报名费用确实大多人望而却步 一.新建项目CallPhone 1.1.建立项目 二.设置界面与项目名称 2.1.更改项目名称 res/values下strings.xml中更改app_name电话拔号器 string.xml <?xml version="1.0" encoding="utf-8"?> <resources>

随机推荐