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/

        标签:Android,spi
        0
        投稿

        猜你喜欢

      • springmvc图片上传及json数据转换过程详解

        2022-02-25 17:11:14
      • C#使用NPOI将List数据导出到Excel文档

        2022-12-18 12:28:09
      • Spring事务失效的一种原因关于this调用的问题

        2022-07-19 20:59:21
      • IDEA2020.1使用LeetCode插件运行并调试本地样例的方法详解

        2022-02-28 09:44:47
      • 不用IDE写C#的Hello World的方法

        2022-01-25 19:24:58
      • Java While循环 do-while循环用法

        2021-06-12 17:46:54
      • Eclipse的Debug调试技巧大全(总结)

        2023-11-25 06:14:06
      • c#自带缓存使用方法 c#移除清理缓存

        2021-09-07 10:20:15
      • Java如何获取对象属性及对应值

        2022-03-30 07:03:05
      • c# 生成二维码的示例

        2021-09-17 14:03:26
      • 四步五分钟Spring4快速集成Swagger

        2022-02-18 08:24:15
      • 深入浅出讲解Java集合之Map接口

        2023-10-14 20:52:46
      • Springboot整合mybatis开启二级缓存的实现示例

        2023-02-24 13:07:18
      • C# 爬虫简单教程

        2022-04-24 14:10:53
      • CentOS 7系统下配置自定义JDK的教程

        2022-02-27 13:46:26
      • SpringMVC用JsonSerialize日期转换方法

        2021-12-06 10:59:59
      • 浅谈关于Mybatis的mapper-locations配置问题

        2023-09-24 06:06:16
      • mybatis if传入字符串数字踩坑记录及解决

        2022-07-18 00:41:18
      • mybatis中映射文件(mapper)中的使用规则

        2021-06-28 20:09:08
      • java使用ftp上传文件示例分享

        2021-10-23 08:33:03
      • asp之家 软件编程 m.aspxhome.com