使用ServiceLoader和AutoService实现插(组)件_ruiurrui的博客-程序员ITS203_serviceloader + autoservice

技术标签: AutoService  ServiceLoad  Android  

ServiceLoader

最近看滴滴开源的Dokit库源码官网地址),发现Dokit使用了ServiceLoaderAutoService库实现的组件/插件机制,就研究了一下,感觉平时如果开发一个SDK框架应该用得上。

ServiceLoader可以理解为是一个接口或类的加载器,通过接口或抽象类能够找到实现该接口或抽象类的子类。

ServiceLoader的使用需要以下几个步骤:

  • 创建一个接口文件
  • 在resources资源目录下创建META-INF/services文件夹
  • 在services文件夹中创建文件,以接口全名命名
  • 创建接口实现类

这样使用比较麻烦,而google提供了一个库来简化这些操作,那就是AutoService库。

使用AutoService库

开源库地址:AutoService

1、添加该库依赖:

implementation 'com.google.auto.service:auto-service:1.0'
kapt 'com.google.auto.service:auto-service:1.0'

2、创建插件接口IPlugin

interface IPlugin {
    
    void init();
}

3、创建抽象类AbsPlugin并实现IPlugin接口

abstract class AbsPlugin : IPlugin {
    
}

4、创建插件实现类,需要继承AbsPlugin抽象类和添加AutoService注解,注解中的参数就是定义的抽象类

@AutoService(AbsPlugin::class)
class LogPlugin : AbsPlugin() {
    

    override fun init() {
    
        Log.e("init: ", "LogPlugin")
    }
}

再创建一个插件:

@AutoService(AbsPlugin::class)
class NetworkPlugin : AbsPlugin() {
    

    override fun init() {
    
        Log.e("init: ", "NetworkPlugin")
    }
}

5、最后在MainActivity中使用ServiceLoader来加载抽象类,得到的是继承AbsPlugin的子类实例

// 得到的是继承AbsPlugin的子类实例
val list = ServiceLoader.load(AbsPlugin::class.java, javaClass.classLoader).toList();
list.forEach {
    
    Log.e("onCreate: ", it.toString())
    // 执行init方法
    it.init();
}

打印结果:

E/onCreate:: com.learn.serviceloader.plugins.LogPlugin@db3bda9
E/init:: LogPlugin
E/onCreate:: com.learn.serviceloader.plugins.NetworkPlugin@3076f2e
E/init:: NetworkPlugin

最后的结果显示可以得到继承了AbsPlugin抽象类的所有子类的实例,然后直接调用该实例的成员方法即可。

点击查看示例源码地址

ServiceLoader原理

在ServiceLoader.load的时候,根据传入的接口类,遍历META-INF/services目录下的以该类命名的文件中的所有类,并实例化返回,下面可以看一下ServiceLoader源码来验证一下:

1、load方法会new ServiceLoader类;

  public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
    
        return new ServiceLoader<>(service, loader);
    }

2、new ServiceLoader类的时候会调用reload方法;

 private ServiceLoader(Class<S> svc, ClassLoader cl) {
    
      service = Objects.requireNonNull(svc, "Service interface cannot be null");
      loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
      reload();
  }

3、reload方法中new LazyIterator()

public void reload() {
    
     providers.clear();
     lookupIterator = new LazyIterator(service, loader);
 }

4、LazyIterator表示懒加载的迭代器,这个迭代器会存储遍历好的类,可以使用next来获取迭代器中的值;

private class LazyIteratorimplements Iterator<S>{
    
    private LazyIterator(Class<S> service, ClassLoader loader) {
    }
    private boolean hasNextService() {
    }
    private S nextService() {
    }
    public boolean hasNext() {
    
            return hasNextService();
    }
    public S next() {
    
            return nextService();
    }
    public void remove() {
    }
}

5、重点看一下nextService和hasNextService方法中的实现;

源码中next方法调用nextServicenextService调用hasNextService

先看一下hasNextService源码:

private boolean hasNextService() {
    
     //......
     // 获取service资源文件路径
     String fullName = PREFIX + service.getName();
             if (loader == null)
             	// 加载资源文件
                 configs = ClassLoader.getSystemResources(fullName);
             else
                 configs = loader.getResources(fullName);
     // ......
     while ((pending == null) || !pending.hasNext()) {
    
         if (!configs.hasMoreElements()) {
    
             return false;
         }
         pending = parse(service, configs.nextElement());
     }
     nextName = pending.next();
     return true;
 }

hasNextService源码中会获取资源文件的路径fullName,这里看一下拼接fullName的常量PREFIX

private static final String PREFIX = "META-INF/services/";

所以最终路径应该是:META-INF/services/接口全类名路径文件名。

然后再看一下nextService源码:

 private S nextService() {
    
         if (!hasNextService())throw new NoSuchElementException();
         String cn = nextName;
         nextName = null;
         Class<?> c = null;
         // ......
         // 反射创建类实例
       	c = Class.forName(cn, false, loader);
       	// ......
       	// 调用强转方法,将父类对象强制转换为子类对象
         S p = service.cast(c.newInstance());
         // 存储到LinkedHashMap中
         providers.put(cn, p);
         return p;
     }

nextService方法中主要通过反射创建获取到的类实例,然后强转为子类对象,最后存储到LinkedHashMap中。

AutoService原理

AutoService处理注解,在使用的时候通过注解来收集子类信息,主要在processAnnotations方法中:

private void processAnnotations(
    Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
    // 获取AutoService注解的节点
    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
    // 遍历节点
    for (Element e : elements) {
    
      TypeElement providerImplementer = MoreElements.asType(e);
      AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();
      // 获取被注解的类
      Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
	  ...
	  // 遍历类节点
      for (DeclaredType providerInterface : providerInterfaces) {
    
        TypeElement providerType = MoreTypes.asTypeElement(providerInterface);
        if (checkImplementer(providerImplementer, providerType, annotationMirror)) {
    
         // 存储类节点
          providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));
        }   
      }
      ...
    }
  }

AutoService通过注解收集到被注解的子类,然后再来完成resources目录下创建META-INF/services接口文件的功能。
直接看源码AutoServiceProcessor.java类,在该类中的process方法中调用了processImpl方法,在该方法中又调用了generateConfigFiles()方法:

private void generateConfigFiles() {
    
    // 获取文件操作对象
    Filer filer = processingEnv.getFiler();
    // 遍历接口
    for (String providerInterface : providers.keySet()) {
    
        // 获取META-INF/services/下的接口文件路径
        String resourceFile = "META-INF/services/" + providerInterface;
        SortedSet<String> allServices = Sets.newTreeSet();
        // 获取编译输出路径
        FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
        Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
        allServices.addAll(oldServices);
        Set<String> newServices = new HashSet<>(providers.get(providerInterface));
        FileObject fileObject =filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
        try (OutputStream out = fileObject.openOutputStream()) {
    
         // 写入到编译输出目录
          ServicesFiles.writeServiceFile(allServices, out);
        }
      } 
    }
  }

AutoService也常用来做ARouter组件化库和BufferKnife注解处理来创建编译时文件。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u010982507/article/details/123674943

智能推荐

VC2013中“GetVersionExW函数被声明为已否决”_bytxl的博客-程序员ITS203_getversionexa被声明为已否决

尝试这个1.Project Properties > Configuration Properties > C/C++ > General > SDL checks关掉其他方法:2.#pragma warning(disable: 4996)          3。/wd 4996第二和第三招不知道还管用不http://bbs.csdn.net/topics/3

Science Robotics | 美国造“自我意识”机器人?还能自我复制?_小高robot的博客-程序员ITS203

来源 | 机器人大讲堂原创 | 佳丽导读今天,要向大家介绍的是人类尝试AI理解自我并组织行为的一个初步成果,有人把它通俗的称作:一个机器人(机械臂)搞懂了“我是谁,我在哪,我应该干什么”这几个哲学问题……知乎上有这样一个话题讨论:一旦出现拥有自我意识的AI/机器人,人类应该如何应对?小编找到七种回答,给大家概括一下:1、自我意识本身是不可知的,是永远无法证明的,这是个...

Hive不支持update、delete解决方案_刘李404not found的博客-程序员ITS203

一、报错一:Attempt to do update or delete using transaction manager that does not support these operations.CM修改hive配置服务端hive.compactor.initiator.on – truehive.compactor.worker.threads – 1hive.txn.man..._1671465600

史上最全的Web安全相关网址汇总【转帖】_一头老毅的博客-程序员ITS203

本文出处:http://www.owasp.org/index.php/Phoenix/Tools老外收集的,佩服!LiveCDsMonday, January 29, 2007 4:02 PM 828569600 AOC_Labrat-ALPHA-0010.iso - http://www.packetfocus.com/hackos/DVL (Damn Vulnerable Lin

Lattice系列FPGA入门相关1(Lattice系列FPGA简介)_Times_poem的博客-程序员ITS203

需求说明:Lattice系统FPGA入门内容       :Lattice系列FPGA简介来自       :时间的诗1.为什么Lattice在进入FPGA市场的第一年就能取得这么好的成绩?我想这里面可能有三个层次的深层原因:第一,针对Altera和Xilinx在高端有Stratix和Virtex、在低端有Cyclone和Spartan产品的情况

OpenGL 房子_xws117123的博客-程序员ITS203

元旦上的图形学的外教Opengl编程  前几个实验挺简单的  实现一些基本的点 线 面 多年变形的绘制   颜色的变化  位置的改变 等最后一个实验 绝对的坑  到现在也没懂那是什么大作业为house  要有房子,树,太阳等我做出来的效果如下//#include //头文件 到时候需要改一下 此头文件在codeblocks环境下运行#include

随便推点

使用NDK交叉编译ffmpeg+libx264找不到libx264的解决方法_薛定谔机器猫的博客-程序员ITS203

使用NDK交叉编译ffmpeg+libx2641,编译libx264wget http://download.videolan.org/pub/x264/snapshots/last_x264.tar.bz2tar xjvf last_x264.tar.bz2cd x264-snapshot*使用如下confiureexport ARM_ROOT=/home/zangcf/

javascriptDOM对象之scrollTo()方法,滚动到页面指定位置_KELION1234的博客-程序员ITS203

scrollTo是一个神奇的方法,常用于篇幅过长的页面,制作一个回顶部的按钮,我这里简单的实现以下当然没有一个过渡的js效果scrollTo(xpos,ypos)参数描述xpos必需。要在窗口文档显示区左上角显示的文档的 x 坐标。ypos必需。要在窗口文档显示区左上角显示的文档的 y 坐标。我就不布局了,就简单的让页面篇幅...

【图像融合】基于matlab小波变换灰色图像融合(含相关性、信噪比)【含Matlab源码 1841期】_海神之光的博客-程序员ITS203_matlab小波变换图像融合

1 案例背景图像融合,指通过对同一目标或同一场景用不同的传感器(或用同一传感器采用不同的方式)进行图像采集得到多幅图像,对这些图像进行合成得到单幅合成图像,而该合成图像是单传感器无法采集得到的。图像融合所输出的合成图像往往能够保持多幅原始图像中的关键信息,进而为对目标或场景进行更精确、更全面的分析和判断提供条件。图像融合属于数据融合范畴,是数据融合的子集,兼具数据融合和图像可视化的优点。

js中获取当前系统的时间,格式2020-10-30 14:05:38_尔嵘的博客-程序员ITS203

js中实现获取当前的具体时间function getCurrentTime() { let nowTime = new Date(), year = nowTime.getFullYear(), month = nowTime.getMonth() + 1 &gt;= 10 ? nowTime.getMonth() + 1 : '0' + (nowTime.getMonth() + 1), day = nowTime.getDate() &gt;= 10 ? nowTime.get

Web前端学习笔记——HTML5与CSS3之H5-DOM扩展、H5新增API_唯恋殊雨的博客-程序员ITS203

H5-dom扩展获取元素document.getElementsByClassName ('class'); //通过类名获取元素,以伪数组形式存在。document.querySelector('selector');//通过CSS选择器获取元素,符合匹配条件的第1个元素。document.querySelectorAll('selector'); //通过CSS选择器获取元素...

区块,链条的关系是什么,其含义是什么?_链客区块链技术问答社区的博客-程序员ITS203_网络链条是什么意思

想知道更多关于区块链技术知识,请百度【链客区块链技术问答社区】链客,有问必答!!区块链顾名思义有两个概念区块、链条,即数据存储在一个个区块内,区块按照时间顺序相连方式组合成的链式数据结构,链式数据结构完整的相同的存储在网络的多个节点。一、区块链的特点“区块链的存在形式”图发现区块链是分布式数据存储、点对点交互,通过共识机制实现信息的开放性、自治性,数据信息一经被达成共识写入链式结构将不可...

推荐文章

热门文章

相关标签