Android组件化利器-SPI

关键词:SPI、ServiceLoader、AutoService、组件化、APT

对于APT(注解处理器),之前写过一篇,使用自定义注解自己实现ButterKnife功能。最近接手的新项目中用了一个新的东西,就是SPI,拿来研究下。

SPI, service provider interface,使用接口、配置文件和策略模式,实现了解耦、模块化和组件化。

ServiceLoader的使用

  1. 在公共模块或者接口模块中定义一个接口类; com.aya.demo.common.ILogInterface.java

    java 复制代码
    public interface ILogInterface{
        void logD(String msg)
    }
  2. 在实际业务模块中,写一个接口的实现类; com.aya.demo.LogImpl.java

    typescript 复制代码
    public class LogImpl implements ILogInterface{
        @Override
        public void logD(String msg){
            Log.d("AYA", msg);
        }
    }
  3. 在该业务模块中,在src/main/resources文件夹下创建/META-INF/services目录,并新建一个文件,文件名是接口的全路径,文件的内容是接口实现类的全路径; 文件路径如下: src/main/resources/META-INF/services/com.aya.demo.common.ILogInterface 文件内容是:

    复制代码
    com.aya.demo.LogImpl
  4. 写一个serverloader工具类。

    SPIUtils.java

    swift 复制代码
     /**
         * 如果有多个实现,取第一个
         */
        public static <T> T getSpiImpl(Class<T> t) {
            try {
                ServiceLoader<T> loader = ServiceLoader.load(t, CommonHolder.ctx.getClassLoader());
                Iterator<T> iter = loader.iterator();
                if (iter.hasNext()) {
                    return iter.next();
                }
            } catch (Exception e) {
                //
            }
            return null;
        }
    
        /**
         * 返回所有实现
         */
        public static <T> List<T> getSpiImpls(Class<T> t) {
            List<T> impls = new ArrayList<>();
            try {
                ServiceLoader<T> loader = ServiceLoader.load(t, CommonHolder.ctx.getClassLoader());
                Iterator<T> iter = loader.iterator();
                while (iter.hasNext()) {
                    impls.add(iter.next());
                }
            } catch (Exception e) {
                //
            }
            return impls;
        }
  5. 使用接口类

    ini 复制代码
    ILogInterface iLog =  SPIUtils.getSpiImpl(ILogInterface.class);
    
    iLog.d("测试下");

使用SPI可以很好的实现解耦合组件化隔离,但是这样做有一个缺点,就是每次新建一个实现类,就需要在/META-INF/services目录下创建一个文件或者修改已有的文件,不能动态添加,有点麻烦而且容易写错。针对这种麻烦的事情,程序员们肯定会想办法处理掉的,所以Google又给我们提供了AutoServcie工具。

AutoService的使用

  1. 首先需要引入依赖

    注意:这个依赖的引入是有讲究的,接口类所在的模块要全部引入

    arduino 复制代码
    // 依赖 autoService 库
    implementation 'com.google.auto.service:auto-service:1.0'
    annotationProcessor 'com.google.auto.service:auto-service:1.0'

    而实现类所在的模块只需要引入注解处理器即可:

    arduino 复制代码
    // 依赖 autoService 库
    annotationProcessor 'com.google.auto.service:auto-service:1.0'

    注意 :如果实现类使用的是kotlin编写的,则在引入依赖时需要使用 kapt,即

    arduino 复制代码
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-kapt'
    
    dependencies {
        kapt 'com.google.auto.service:auto-service:1.0'
    }
  2. 原先的接口类不用动,只需修改实现类 只需要在具体的实现类上面加上一个@AutoService注解,参数则为接口的class类。

    com.aya.demo.LogImpl.java

    typescript 复制代码
    @AutoService(ILogInterface.class)
    public class LogImpl implements ILogInterface{
        @Override
        public void logD(String msg){
            Log.d("AYA", msg);
        }
    }
  3. 写一个serverloader工具类SPIUtils.java,同上。

这样,就可以像之前一样使用接口了。

AutoService原理

AutoService的原理就是借助自定义注解,在编译期使用注解处理器扫描注解,并自动生成/resources/META-INF/... 文件。

AutoService工具关键类是 AutoServiceProcessor.java 关键代码如下,加了一些注释:

scss 复制代码
public class AutoServiceProcessor extends AbstractProcessor {

    ...
    //Multimap:key可以重复
    private final Multimap<String, String> providers = HashMultimap.create();
    ...
        
      /**
       * <ol>
       *  <li> For each class annotated with {@link AutoService}<ul>
       *      <li> Verify the {@link AutoService} interface value is correct
       *      <li> Categorize the class by its service interface
       *      </ul>
       *
       *  <li> For each {@link AutoService} interface <ul>
       *       <li> Create a file named {@code META-INF/services/<interface>}
       *       <li> For each {@link AutoService} annotated class for this interface <ul>
       *           <li> Create an entry in the file
       *           </ul>
       *       </ul>
       * </ol>
       */
      @Override
      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
      //该方法数注解处理器的入口
        try {
          processImpl(annotations, roundEnv);
        } catch (RuntimeException e) {
          // We don't allow exceptions of any kind to propagate to the compiler
          fatalError(getStackTraceAsString(e));
        }
        return false;
      }
    
      private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (roundEnv.processingOver()) {
            //如果已经处理结束,则去生成注册文件
            generateConfigFiles();
        } else {
            //处理注解
            processAnnotations(annotations, roundEnv);
        }
      }
    
        //处理注解
      private void processAnnotations(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //获取所有添加AutoService注解的类
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
    
        log(annotations.toString());
        log(elements.toString());
    
        for (Element e : elements) {
          // TODO(gak): check for error trees?
          TypeElement providerImplementer = MoreElements.asType(e);
          //获取AutoService注解value,即声明实现类时注解括号里指定的接口
          AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();
          //获取value的集合
          Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
          if (providerInterfaces.isEmpty()) {
            //如果集合为空,也就是没有指定value,报错,不处理
            error(MISSING_SERVICES_ERROR, e, annotationMirror);
            continue;
          }
          //遍历所有value,获取value的完整类名
          for (DeclaredType providerInterface : providerInterfaces) {
            TypeElement providerType = MoreTypes.asTypeElement(providerInterface);
    
            log("provider interface: " + providerType.getQualifiedName());
            log("provider implementer: " + providerImplementer.getQualifiedName());
    
            //判断是否为接口的实现类,是,则存入Multimap类型的providers缓存中,其中key为接口的全路径,value为实现类的全路径;否则报错
            if (checkImplementer(providerImplementer, providerType, annotationMirror)) {
              providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));
            } else {
              String message = "ServiceProviders must implement their service provider interface. "
                  + providerImplementer.getQualifiedName() + " does not implement "
                  + providerType.getQualifiedName();
              error(message, e, annotationMirror);
            }
          }
        }
      }
    
        //生成spi文件
      private void generateConfigFiles() {
        Filer filer = processingEnv.getFiler();
        //遍历providers的key值
        for (String providerInterface : providers.keySet()) {
          String resourceFile = "META-INF/services/" + providerInterface;
          log("Working on resource file: " + resourceFile);
          try {
            SortedSet<String> allServices = Sets.newTreeSet();
            try {
              // would like to be able to print the full path
              // before we attempt to get the resource in case the behavior
              // of filer.getResource does change to match the spec, but there's
              // no good way to resolve CLASS_OUTPUT without first getting a resource.
              //"META-INF/services/**"文件夹下已经存在SPI文件
              FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
                  resourceFile);
              log("Looking for existing resource file at " + existingFile.toUri());
              // 该SPI文件中的service集合
              Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
              log("Existing service entries: " + oldServices);
              //将spi文件中的service写入缓存
              allServices.addAll(oldServices);
            } catch (IOException e) {
              // According to the javadoc, Filer.getResource throws an exception
              // if the file doesn't already exist.  In practice this doesn't
              // appear to be the case.  Filer.getResource will happily return a
              // FileObject that refers to a non-existent file but will throw
              // IOException if you try to open an input stream for it.
              log("Resource file did not already exist.");
            }
    
            //根据providers缓存创建service集合
            Set<String> newServices = new HashSet<>(providers.get(providerInterface));
            //如果新建的都已经存在,则直接返回不处理
            if (allServices.containsAll(newServices)) {
              log("No new service entries being added.");
              return;
            }
    
            //将新建的service也加入缓存
            allServices.addAll(newServices);
            log("New service file contents: " + allServices);
            //将所有的service写入文件
            FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
                resourceFile);
            try (OutputStream out = fileObject.openOutputStream()) {
              ServicesFiles.writeServiceFile(allServices, out);
            }
            log("Wrote to: " + fileObject.toUri());
          } catch (IOException e) {
            fatalError("Unable to create " + resourceFile + ", " + e);
            return;
          }
        }
      }
      
      ...
}

写SPI的File工具类ServicesFiles.java

java 复制代码
final class ServicesFiles {
  public static final String SERVICES_PATH = "META-INF/services";

  private ServicesFiles() { }
    
    ...
    
  /**
   * Writes the set of service class names to a service file.
   *
   * @param output not {@code null}. Not closed after use.
   * @param services a not {@code null Collection} of service class names.
   * @throws IOException
   */
  static void writeServiceFile(Collection<String> services, OutputStream output)
      throws IOException {
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
    for (String service : services) {
      writer.write(service);
      writer.newLine();
    }
    writer.flush();
  }
}

看了源码后,可以发现,AutoServiceProcessor的主要功能就是将加了AutoService注解的类写到SPI文件中去,而SPI文件的名称根据注解时value的内容来定的,即接口的全路径,SPI文件的内容是实现类的全路径,每个实现类占一行。

使用AutoService时需要注意:

  1. minSdkVersion最低为26
  2. 接口的实现类必须是public的

总结一下吧,SPI可以更好的实现组件化,结合AUtoService和ServiceLoader来实现,很方便。当然也可以自己开发一个sdk,把SPIUtils放进去,就可以更方便的使用了。

APT的应用,除了ButterKnife、AUtoService,还有阿里的ARouter,都是程序员的智慧结晶!佩服这些大牛。

相关推荐
雨白41 分钟前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk43 分钟前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING1 小时前
RN容器启动优化实践
android·react native
恋猫de小郭4 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker9 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴9 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭19 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab20 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos