Spi机制在Android开发的应用示例详解

  
目录
  • Spi机制介绍
  • 举个例子
  • ServiceLoader.load
  • 在Android中的应用
  • 总结

Spi机制介绍

SPI 全称是 Service Provider Interface,是一种将服务接口与服务实现分离以达到解耦、可以提升程序可扩展性的机制。嘿嘿,看到这个概念很多人肯定是一头雾水了,没事,我们直接就可以简单理解为是一种反射机制,即我们不需要知道具体的实现方,只要定义好接口,我们就能够在运行时找到一个实现接口的类,我们具体看一下官方定义。

举个例子

加入我是一个库设计者,我希望把一个接口暴露给使用者实现具体的逻辑,那么我肯定不能够写死实现类对吧,不然我们怎么扩展嘛!比如我们有以下接口

package com.example.newtestproject
interface TestSpi {
    fun getSpi();
}

如果我在使用的过程中,想不关心具体的实现类/又或者想兼容多个实现,那么怎么办呢?(比如日常开发的gradle,如果我想兼容多个agp版本在自己库的运行,一个个写死肯定是一个非常糟糕的实现,如果出了新版本,那么我们还要更改代码),我们能不能只定义一个规范,即上面的TestSpi就可以不关心以后的扩展呢?很简单,我们只需要在resource/META-INF/services目录下定义一个以该接口为名称的文件,文件内容是具体的接口实现类即可,如图

内容是实现类的全名称,假如我们有以下实现类

class TheTestSpi:TestSpi {
    override fun getSpi() {
        Log.i("hello","i am the interface implementation from TheTestSpi")
    }
}

那么文件只需要写入com.example.newtestproject.TheTestSpi即可。

在我们想要用到TestSpi的功能的时候,就可以通过以下方式进行使用,从而不用关心具体的实现,达到了解耦合的目的

// spi test
val load = ServiceLoader.load(TestSpi::class.java)
load.forEach {
    it.getSpi()
    if(it is TheTestSpi){
        Log.i("hello","theTestSpi")
    }
}

ServiceLoader.load

从上面我们可以看到,最关键的是调用了ServiceLoader.load方法,这个就是Spi具体的实现了,本质是什么呢?相信都能够猜到了,其实就是反射,我们来跟一下源代码

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取了一个当前类的classloader,做好了准备
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

紧接着就会调用到


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

我们可以看到,执行的时候有这么一句String fullName = PREFIX + service.getName();,很容易想到,如果我们要反射的话,是不是需要类全称,PREFIX 其实就是

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

可以看到,之所以我们需要在resource下定一个文件路径是META-INF/services,是因为在ServiceLoader中定义好了,所以ServiceLoader会按照约定的路径,去该文件夹下查找service.getName()(TestSpi)的实现类,最后通过

private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class&lt;?&gt; c = null;
    try {
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
             // Android-changed: Let the ServiceConfigurationError have a cause.
             "Provider " + cn + " not found", x);
             // "Provider " + cn + " not found");
    }
    .....

Class.forName(cn, false, loader) 去进行了类查找操作,因为我们通过foreach去遍历,其实就是通过迭代器去获取了每个实例,所以才能够调用到了实现类TheTestSpi的getSpi() 方法。

在Android中的应用

SPI机制在很多库的设计上都有应用,比如Coroutine(kotlin协程库)就有用到,比如我们经常用到的 MainCoroutineDispatcher,如果我们希望一个任务运行在主线程,在Android就可以通过handler的方式去向主线程post一个消息,那如果在其他环境呢?

kotlin不仅仅是想在android的世界立足,还有很多比如如果在native环境呢?在服务器环境呢?多平台环境呢(如KMM),那就不一定有Handler这个概念对不对!但是都有一个主线程的概念,所以Coroutine把这部分就通过SPI的方式去实现了,如

定义了这个接口 MainDispatcherFactory::class.java 用于给具体环境的主线程实现类进行实现,具体的实现类就是

internal class AndroidDispatcherFactory : MainDispatcherFactory {
    override fun createDispatcher(allFactories: List&lt;MainDispatcherFactory&gt;) =
        HandlerContext(Looper.getMainLooper().asHandler(async = true))
    override fun hintOnError(): String? = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
    override val loadPriority: Int
        get() = Int.MAX_VALUE / 2
}

可以看到,在Android中就通过了Handler去实现,最后我们可以在源码中看到,SPI相关的注册信息

总结

通过SPI技术去实现的解耦合工作的出色工程还有很多很多,比如我们用的APT,还有didi开源的Booster,都有用到这方面的知识。

以上就是Spi机制在Android开发的应用示例详解的详细内容,更多关于Android开发Spi机制的资料请关注我们其它相关文章!

相关文章