【Android】Google AutoService的使用与源码解析

本文出处:https://blog.csdn.net/devnn/article/details/126837081 转载请注明来源。

前言

AutoService是Google开发一个自动生成SPI清单文件的框架。看过一些基于APT的三方框架源码的读者应该有所了解。比如Arouter、EventBus等等。一般我们用它来自动帮我们注册APT文件(全称是Annotation Process Tool,或者叫注解处理器,AbstractProcessor的实现)。很多生成SPI文件的框架也是抄袭它的源码,可见它的作用还不小。

APT其实就是基于SPI一个工具,是JDK留给开发者的一个在编译前处理注解的接口。APT也是SPI的一个应用。而AutoService基于APT的一个应用。关于SPI和APT下文会详细讲到。

先讲一下它是如何使用的。

AutoService的使用

AutoService框架的作用是自动生成SPI清单文件(也就是META-INF/services目录下的文件)。如果不使用它就需要手动去创建这个文件、手动往这个文件里添加服务(接口实现),为了免去手动操作,才有了AutoService。SPI清单文件是java运行阶段由ServiceLoader这个类来读取的,后面会详细介绍SPI。

AutoService比较常用的场景是帮助注册APT(注解处理器)。下面以APT的例子来讲解它的使用。

开发APT需要在Java SE项目中开发,因为需要继承AbstractProcessor,AbstractProcessor作用在Java编译阶段。

先创建Java module,在Android Studio中也可以创建,然后在build.gradle中添加依赖,如下dependencies部分。

通过annotationProcessor添加注解处理器(AutoServiceProcessor.class),同时需要通过implementation添加annotation依赖,即AutoService.class。

plugins {
    id 'java-library'
}

dependencies {
    annotationProcessor 'com.google.auto.service:auto-service:1.0.1'
	//一般结合JavaPoet框架来生成Java代码,这里不对它进行阐述。
	//implementation 'com.squareup:javapoet:1.13.0' 
    implementation 'com.google.auto.service:auto-service-annotations:1.0.1'
}

然后在你处理注解处理器类上方添加@AutoService注解即可,value指定成javax.annotation.processing.Processor类,因为要生成的SPI清单文件(META-INF/services下的文件)名称是
javax.annotation.processing.Processor 这个Processor是Java内置的,Javac编译前默认的注解处理器接口。如果是我们自定义的接口就指定成自己的接口名。

@AutoService(value = {Processor.class})
public class MyProcessor extends AbstractProcessor {
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        System.out.println("MyProcessor------------init---------------");
        super.init(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        System.out.println("MyProcessor------------process---------------");
        return false;
    }
}

AbstractProcessor是继承自Processor接口:

public abstract class AbstractProcessor implements Processor {
	...
}

AbstractProcessor这个类是JDK SE中的,Android SDK将它删除了(因为不需要也用不着),所以Android Module里面是不存在的。这也说明为什么创建Java SE项目来编写APT代码。

AutoService注解的声明如下,它的value是一个class集合,可以指定多个value。

@Documented
@Retention(CLASS)
@Target(TYPE)
public @interface AutoService {
  /** Returns the interfaces implemented by this service provider. */
  Class<?>[] value();
}

以上示例中MyProcessor的作用是处理项目的自定义注解,比如Arouter框架会利用它来处理@Aouter注解,并自动生成路由注册类。

编译这个Java项目后就会自动将MyProcessor添加到APT的SPI注册文件中。

要注意的是,这个时候MyProcessor是没有起作用的,init和process方法都不会执行。因为注解处理阶段它并不在SPI注册文件中,注解处理阶段完成后它才注册进去。将Java项目打包成jar,这个MyProcessor才会在SPI注册文件中。别的项目依赖这个jar,MyProcessor的代码才会执行。

以上是AutoService的使用。讲了这些,可能有人看不懂。没关系,先了解一下SPI技术。

SPI技术

什么是SPI呢,了解SPI是读懂AutoService的基础。

SPI是Service Provider Interface的简称,是JDK默认提供的一种将接口和实现类进行分离的机制。这种机制能将接口和实现进行解耦,大大提升系统的可扩展性。

SPI机制约定:当一个Jar包需要提供一个接口的实现类时,这个Jar包需要在META-INF/services目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该Jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

SPI示例

比如有一个接口IMyService

package com.devnn.demo.interface
  
public interface IMyService {

    void hello();
}

它的实现类有:

package com.devnn.demo.impl
import com.devnn.demo.interfaces.IMyService;
  
public class MyServiceImpl_1 implements IMyService {

    @Override
    public void hello() {
        System.out.println("Hi,I am MyServiceImpl_1");
    }
}
package com.devnn.demo.impl;
import com.devnn.demo.interfaces.IMyService;

public class MyServiceImpl_2 implements IMyService {
    @Override
    public void hello() {
        System.out.println("Hi,I am MyServiceImpl_2");
    }
}

resource/META-INF/services目录下创建文件com.devnn.demo.interface.IMyService,内容为所有实现类的完整名称:

com.devnn.demo.impl.MyServiceImpl_1
com.devnn.demo.impl.MyServiceImpl_2

项目结构:
在这里插入图片描述

加载IMyService接口的所有子类:

public class SPI_Demo {
  
    public static void main(String[] agrs) {
      
       //使用jdk提供的类ServiceLoader来加载IMyService的子类
       ServiceLoader<IMyService> loaders = ServiceLoader.load(IMyService.class);
      
       //遍历并调用子类方法
        for (IMyService service : loaders) { 
            service.hello();
        }
    }
}

运行就会打印:

Hi,I am MyServiceImpl_1
Hi,I am MyServiceImpl_2

是不是很神奇,通过一个接口,就可以找到它的实现类,这就是SPI的作用。

使用AutoService注解自动创建SPI清单文件

以上是手动创建的SPI清单文件:
resource/META-INF/services/com.devnn.demo.interface.IMyService
如果不想手动去创建这个文件,就使用AutoService注解。代码如下。

package com.devnn.demo.impl
import com.devnn.demo.interfaces.IMyService;

@AutoService(value = {IMyService.class}) 
public class MyServiceImpl_1 implements IMyService {

    @Override
    public void hello() {
        System.out.println("Hi,I am MyServiceImpl_1");
    }
}
package com.devnn.demo.impl;
import com.devnn.demo.interfaces.IMyService;

@AutoService(value = {IMyService.class}) 
public class MyServiceImpl_2 implements IMyService {
    @Override
    public void hello() {
        System.out.println("Hi,I am MyServiceImpl_2");
    }
}

然后使用ServiceLoader去加载IMyService接口的所有实现,结果是一样的。

APT技术

本文开头说了APT是SPI的一个应用。为什么这么说呢?APT其实就是Java给我们提供的内置的SPI接口,作用是在编译java前处理java源码中的注解。
APT的服务接口就是这个
javax.annotation.processing.Processor
META_INF/service下的文件名是一致的。

Java编译器运行javac编译java类之前读取这个清单文件,加载实现Processor接口的所有类,执行里面的注解处理逻辑,执行注解处理逻辑后再编译java代码。

AutoService原理

然后再回到AutoService,结合源码对它进行剖析,AutoService主要代码就一个类,即AutoServiceProcessor.java,为了方便阅读,笔者先将它原封不动copy在这里,后面再对它进行解析。

AutoService源码
/*
 * Copyright 2008 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.auto.service.processor;

import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
import static com.google.auto.common.MoreElements.getAnnotationMirror;
import static com.google.auto.common.MoreStreams.toImmutableSet;
import static com.google.common.base.Throwables.getStackTraceAsString;

import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

/**
 * Processes {@link AutoService} annotations and generates the service provider
 * configuration files described in {@link java.util.ServiceLoader}.
 * <p>
 * Processor Options:<ul>
 *   <li>{@code -Adebug} - turns on debug statements</li>
 *   <li>{@code -Averify=true} - turns on extra verification</li>
 * </ul>
 */
@SupportedOptions({"debug", "verify"})
public class AutoServiceProcessor extends AbstractProcessor {

  @VisibleForTesting
  static final String MISSING_SERVICES_ERROR = "No service interfaces provided for element!";

  private final List<String> exceptionStacks = Collections.synchronizedList(new ArrayList<>());

  /**
   * Maps the class names of service provider interfaces to the
   * class names of the concrete classes which implement them.
   * <p>
   * For example,
   *   {@code "com.google.apphosting.LocalRpcService" ->
   *   "com.google.apphosting.datastore.LocalDatastoreService"}
   */
  private final Multimap<String, String> providers = HashMultimap.create();

  @Override
  public ImmutableSet<String> getSupportedAnnotationTypes() {
    return ImmutableSet.of(AutoService.class.getName());
  }

  @Override
  public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
  }

  /**
   * <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
      String trace = getStackTraceAsString(e);
      exceptionStacks.add(trace);
      fatalError(trace);
    }
    return false;
  }

  ImmutableList<String> exceptionStacks() {
    return ImmutableList.copyOf(exceptionStacks);
  }

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

    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);
      AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();
      Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
      if (providerInterfaces.isEmpty()) {
        error(MISSING_SERVICES_ERROR, e, annotationMirror);
        continue;
      }
      for (DeclaredType providerInterface : providerInterfaces) {
        TypeElement providerType = MoreTypes.asTypeElement(providerInterface);

        log("provider interface: " + providerType.getQualifiedName());
        log("provider implementer: " + providerImplementer.getQualifiedName());

        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);
        }
      }
    }
  }

  private void generateConfigFiles() {
    Filer filer = processingEnv.getFiler();

    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.
          FileObject existingFile =
              filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
          log("Looking for existing resource file at " + existingFile.toUri());
          Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
          log("Existing service entries: " + oldServices);
          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.");
        }

        Set<String> newServices = new HashSet<>(providers.get(providerInterface));
        if (!allServices.addAll(newServices)) {
          log("No new service entries being added.");
          continue;
        }

        log("New service file contents: " + allServices);
        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;
      }
    }
  }

  /**
   * Verifies {@link ServiceProvider} constraints on the concrete provider class. Note that these
   * constraints are enforced at runtime via the ServiceLoader, we're just checking them at compile
   * time to be extra nice to our users.
   */
  private boolean checkImplementer(
      TypeElement providerImplementer,
      TypeElement providerType,
      AnnotationMirror annotationMirror) {

    String verify = processingEnv.getOptions().get("verify");
    if (verify == null || !Boolean.parseBoolean(verify)) {
      return true;
    }

    // TODO: We're currently only enforcing the subtype relationship
    // constraint. It would be nice to enforce them all.

    Types types = processingEnv.getTypeUtils();

    if (types.isSubtype(providerImplementer.asType(), providerType.asType())) {
      return true;
    }

    // Maybe the provider has generic type, but the argument to @AutoService can't be generic.
    // So we allow that with a warning, which can be suppressed with @SuppressWarnings("rawtypes").
    // See https://github.com/google/auto/issues/870.
    if (types.isSubtype(providerImplementer.asType(), types.erasure(providerType.asType()))) {
      if (!rawTypesSuppressed(providerImplementer)) {
        warning(
            "Service provider "
                + providerType
                + " is generic, so it can't be named exactly by @AutoService."
                + " If this is OK, add @SuppressWarnings(\"rawtypes\").",
            providerImplementer,
            annotationMirror);
      }
      return true;
    }

    return false;
  }

  private static boolean rawTypesSuppressed(Element element) {
    for (; element != null; element = element.getEnclosingElement()) {
      SuppressWarnings suppress = element.getAnnotation(SuppressWarnings.class);
      if (suppress != null && Arrays.asList(suppress.value()).contains("rawtypes")) {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns the binary name of a reference type. For example,
   * {@code com.google.Foo$Bar}, instead of {@code com.google.Foo.Bar}.
   *
   */
  private String getBinaryName(TypeElement element) {
    return getBinaryNameImpl(element, element.getSimpleName().toString());
  }

  private String getBinaryNameImpl(TypeElement element, String className) {
    Element enclosingElement = element.getEnclosingElement();

    if (enclosingElement instanceof PackageElement) {
      PackageElement pkg = MoreElements.asPackage(enclosingElement);
      if (pkg.isUnnamed()) {
        return className;
      }
      return pkg.getQualifiedName() + "." + className;
    }

    TypeElement typeElement = MoreElements.asType(enclosingElement);
    return getBinaryNameImpl(typeElement, typeElement.getSimpleName() + "$" + className);
  }

  /**
   * Returns the contents of a {@code Class[]}-typed "value" field in a given {@code
   * annotationMirror}.
   */
  private ImmutableSet<DeclaredType> getValueFieldOfClasses(AnnotationMirror annotationMirror) {
    return getAnnotationValue(annotationMirror, "value")
        .accept(
            new SimpleAnnotationValueVisitor8<ImmutableSet<DeclaredType>, Void>(ImmutableSet.of()) {
              @Override
              public ImmutableSet<DeclaredType> visitType(TypeMirror typeMirror, Void v) {
                // TODO(ronshapiro): class literals may not always be declared types, i.e.
                // int.class, int[].class
                return ImmutableSet.of(MoreTypes.asDeclared(typeMirror));
              }

              @Override
              public ImmutableSet<DeclaredType> visitArray(
                  List<? extends AnnotationValue> values, Void v) {
                return values.stream()
                    .flatMap(value -> value.accept(this, null).stream())
                    .collect(toImmutableSet());
              }
            },
            null);
  }

  private void log(String msg) {
    if (processingEnv.getOptions().containsKey("debug")) {
      processingEnv.getMessager().printMessage(Kind.NOTE, msg);
    }
  }

  private void warning(String msg, Element element, AnnotationMirror annotation) {
    processingEnv.getMessager().printMessage(Kind.WARNING, msg, element, annotation);
  }

  private void error(String msg, Element element, AnnotationMirror annotation) {
    processingEnv.getMessager().printMessage(Kind.ERROR, msg, element, annotation);
  }

  private void fatalError(String msg) {
    processingEnv.getMessager().printMessage(Kind.ERROR, "FATAL ERROR: " + msg);
  }
}

AutoService源码解读

主要逻辑在process方法中,通过实现AbstractProcessor的process方法来实现功能。

process调用了processImpl:

 private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
   if (roundEnv.processingOver()) { //本轮注解处理完毕
      generateConfigFiles();//生成SPI注册文件
    } else { //未处理完毕,继续处理
      processAnnotations(annotations, roundEnv);//整理需要注册的文件,放入缓存
    }
 }

再看processAnnotations方法,笔者已经加了注释:

private void processAnnotations(
      Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

	//获取所有加了AutoService注解的类
    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
    
    for (Element e : elements) {
      //将Element转成TypeElement
      TypeElement providerImplementer = MoreElements.asType(e);
      //获取AutoServce注解指定的value
      AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();
      //获取value集合
      Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
      //如果没有指定value,报错
      if (providerInterfaces.isEmpty()) {
        error(MISSING_SERVICES_ERROR, e, annotationMirror);
        continue;
      }
      //遍历所有的value,获取value的完整类名(例如javax.annotation.processing.Processor)
      for (DeclaredType providerInterface : providerInterfaces) {
        TypeElement providerType = MoreTypes.asTypeElement(providerInterface);

		//判断是否是继承关系,是则放入providers缓存起来,否则报错
        if (checkImplementer(providerImplementer, providerType, annotationMirror)) {
          providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));
        } else {
         //报错代码,略
        }
      }
    }
  }

注解处理完毕,就会生成SPI注册文件。如果SPI路径上文件已经存在,先要把已存在的SPI清单读进内存,再把新的provider加进去,然后全部写出,覆盖原来的文件。这部分逻辑如下:

  private void generateConfigFiles() {
    Filer filer = processingEnv.getFiler();//获取文件工具类,processingEnv是AbstractProcessor的成员变量,直接拿来用。

	//遍历之前解析的providers缓存
    for (String providerInterface : providers.keySet()) {
     //providerInterface就是value字段指定的接口,例如javax.annotation.processing.Processor
      String resourceFile = "META-INF/services/" + providerInterface;
      log("Working on resource file: " + resourceFile);
      try {
        SortedSet<String> allServices = Sets.newTreeSet();
        try {
        //已经存在的SPI文件
          FileObject existingFile =
              filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
          //SPI文件中的service条目清单
          Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
          log("Existing service entries: " + oldServices);
          allServices.addAll(oldServices);
        } catch (IOException e) {
          log("Resource file did not already exist.");
        }

		//新的service条目清单
        Set<String> newServices = new HashSet<>(providers.get(providerInterface));
        //如果已经存在,则不处理
        if (!allServices.addAll(newServices)) {
          log("No new service entries being added.");
          continue;
        }
		//以下是将缓存的services写入文件中。
        log("New service file contents: " + allServices);
        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;
      }
    }
  }

可见AutoServiceProcessor的主要功能就是将加了AutoService注解的类,加到SPI注册文件中。SPI文件名称(或者叫服务)可以通过value指定。

下面将AutoService从mavenCentral仓库中下载下来(一个jar包),解压查看它的内容:
在这里插入图片描述

可以看到它里面内容并不多,主要就是一个AutoServiceProcessor类和一个APT清单文件。打开这个清单文件,里面就是AutoServiceProcessor类的全路径:

在这里插入图片描述

所以我们将AutoService加到java项目中,其实就是引入了AutoServiceProcessor这个注解处理器,帮助我们处理@AutoService注解,将我们的服务(一般是APT类,也可以是其它的类,通过value指定)自动注册进SPI文件中。

看到这里,不知道读者有没有领悟。

AutoService是一个注解处理器,我们自己开发的APT也是注解处理器,它们都是注解处理器,AutoSevice是自动帮我们注册注解处理器的注解处理器。是不是有点绕?

当然AutoService的作用不仅在于注册APT,还可以注册其它服务。只是注册APT我们比较常见。

本文出处:https://blog.csdn.net/devnn/article/details/126837081 转载请注明来源。

AutoService在组件化架构中的应用

在组件化架构app中,有一个主Module和若干业务Module,如何在主Module中初始化各个业务Module?为了避免手动往主Module中注册业务Module的初始化器,可以使用SPI技术,同时使用AutoService框架帮助我们自动注册SPI。首先将初始化功能抽象成接口,在每个业务Module中创建一个初始化类实现这个共同的接口,然后在这个类上加AutoService注解,注解的value指定成这个共同的接口。在主Module中就可以通过SPI机制加载这些业务Module的初始化类,调用初始化接口。具体操作如下:

baseLibrary将业务Module的初始化抽象成接口,比如IService接口:

//baseLibrary
package com.devnn.baselibrary

interface IService {
    fun init()
}

library1 module中有一个Lib1Service实现了IService接口:

//library1
package com.devnn.library1

import android.util.Log
import com.devnn.baselibrary.IService
import com.google.auto.service.AutoService

@AutoService(IService::class)
class Lib1Service:IService{
    override fun init() {
        Log.d("IService", "Lib1Service_init")
    }

}

library2 module中有一个Lib2Service实现了IService接口:

//library2
package com.devnn.library2

import android.util.Log
import com.devnn.baselibrary.IService
import com.google.auto.service.AutoService

@AutoService(IService::class)
class Lib2Service:IService {
    override fun init() {
        Log.d("IService", "Lib2Service_init")
    }
}

library1library2build.gradle文件中都需要添加AutoService依赖:

dependencies {
	...
    kapt 'com.google.auto.service:auto-service:1.0.1'
    implementation 'com.google.auto.service:auto-service-annotations:1.0.1'
    ...
    }

注意:kotlin用的是kapt,java用的是AnnotationProcessor

然后在主module中去加载这两个Service的实现:

val serviceLoader: ServiceLoader<IService> = ServiceLoader.load(IService::class.java)
val iterator = serviceLoader.iterator()
while (iterator.hasNext()) {
     val service: IService = iterator.next()
     service.init()
}

运行时Logcat打印日志如下:

2022-12-18 22:40:21.325 12177-12177/com.devnn.demo D/IService: Lib1Service_init
2022-12-18 22:40:21.326 12177-12177/com.devnn.demo D/IService: Lib2Service_init

这样就会自动初始化业务Module,不用业务Module的开发人员还要维护主module。

总结

APT是编译时技术,SPI是运行时技术。AutoService是APT的实现(或者叫应用),它运用了APT技术来解析程序中@AutoService注解并且自动生成SPI清单文件,SPI清单文件中保存了接口(服务)的实现,SPI清单文件是在程序运行时由ServiceLoader读取。

AutoService不仅是一个自动注册SPI的框架,它还是一个SPI技术的模板,有时候我们需要自己开发一个基于APT同时又要注册自定义service的框架,它的源码是一个很好的参考。AutoServiceProcessor里面的大部分代码是可以复制拿来用。再比如,ServiceFiles.java是SPI资源文件读取和写入的工具类,直接复制到我们项目中即可。

Java中的autoservice注解是一种用于服务发现的机制,它能够自动将服务的实现类注册到指定的配置文件中,一般是META-INF/services目录下的文件。 然而,的确有一些情况下autoservice注解无法生成META-INF文件。这可能是由于以下几种原因导致的: 1. 编译问题:可能是由于编译配置的问题,导致编译器无法正确处理autoservice注解。这种情况下,我们可以尝试检查编译器的配置,或者使用其他IDE或编译工具进行尝试。 2. 库或框架限制:有些库或框架可能不支持autoservice注解,或者在使用autoservice注解时会有特殊的要求。我们可以查看相关库或框架的文档,了解其对autoservice注解的支持情况,或者尝试其他方式实现服务发现。 3. 配置文件缺失或错误:autoservice注解生成META-INF文件的前提是META-INF目录存在,并且配置文件的名称和路径正确。如果自动生成的META-INF文件不存在,或者文件名或路径有误,就无法实现服务发现。我们需要检查项目的文件结构,确认META-INF目录是否存在,并且配置文件的名称和路径是否正确。 总之,虽然autoservice注解通常可以自动将服务实现类注册到META-INF文件中,但在某些情况下可能会遇到无法生成META-INF文件的问题。我们需要仔细检查编译配置、库或框架限制,以及配置文件是否正确等因素,以找出问题所在,并采取相应的解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值