Android中SPI的使用

本文介绍了Android中SPI(Service Provider Interface)的工作原理、使用步骤、优缺点,并提供了针对Android平台的SPI优化思路,包括在编译时生成服务类列表以避免运行时性能损失。
部署运行你感兴趣的模型镜像

转载注明出处:http://blog.youkuaiyun.com/xiaohanluo/article/details/71515678

1. 简介

SPI全称是Service Provider Interface,服务提供方接口,服务通常是指一个接口或者一个抽象类,服务提供方是对这个接口或者抽象类的具体实现,由第三方来实现接口提供具体的服务。SPI提供了一种动态的对应用程序进行扩展的机制,通常用作框架服务的拓展或者可替换的服务组件,但是在Android中并没有广泛的使用。

SPI运用场景主要有:

官方介绍文档:https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html

2. SPI机制

  • 在resources/META-INF/services/目录中创建以服务全限定名命名的文件,该文件内容为服务的具体实现类的全限定名,文件中可以写多个服务的具体实现类
  • 使用ServiceLoader类动态加载服务的具体实现类
  • 服务具体的实现类必须有一个不带参数的构造方法
  • 如果服务的具体实现类在Jar包中,则需要放在主程序的classPath中

3. SPI的使用

SPI在Android工程中很少被使用到,主要因为SPI是针对厂商或者第三方服务商设置的机制,在平常的项目中很少会用到SPI。在业务越来越复杂的今天,很多应用趋向于组件化,项目中会依赖很多组件,这样会有一些问题,主工程和组件怎么通信,组件之间又怎么通信。当然有很多可以解决通信的方法,这里举个SPI例子。


图-1 举例示意图

主工程中依赖有三个组件,分别提供三个服务ABC,在用户切换后台后,主程序想要关闭定位,但是由于服务ABC可能对定位信息变化非常敏感,需要由服务的具体场景来告诉主程序是否能够关闭定位。

3.1 定义接口或者抽象类

定义接口

public interface IService {
    boolean keep();
}

3.2 实现接口

定义三个服务实现该接口,简化逻辑直接返回。

public class AService implements IService {
    @Override
    public boolean keep() {
        return false;
    }
}

public class BService implements IService {
    @Override
    public boolean keep() {
        return false;
    }
}

public class CService implements IService {
    @Override
    public boolean keep() {
        return true;
    }
}

3.3 创建spi描述文件

在工程的main目录下新建目录resources/META-INF/services,以服务接口名为文件名新建spi描述文件,内容为具体的服务实现类权限定名,可以有多个。


图-2 目录结构图

文件内容如下:

com.spi.demo.AService
com.spi.demo.BService
com.spi.demo.CService

3.4 调用具体服务

使用ServiceLoader去加载具体服务类,然后遍历具体的实现类,ServiceLoader其实就是去META-INFO/services目录下读取文件内容,然后实例化。

mBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ServiceLoader<IService> loader = ServiceLoader.load(IService.class);
        Iterator<IService> iterator = loader.iterator();
        boolean isKeepLoc = false;

        while (iterator.hasNext()) {
            if(iterator.next().keep()){
                isKeepLoc = true;
                break;
            }
        }

        Toast.makeText(MainActivity.this, "Keep location : " + isKeepLoc, Toast.LENGTH_SHORT).show();
    }
});

4. SPI的优点

只提供服务接口,具体服务由其他组件实现,接口和具体实现分离,同时能够通过系统的ServiceLoader拿到这些实现类的集合,统一处理。

5. SPI的缺点

  • Java中SPI是随jar发布的,每个不同的jar都可以包含一系列的SPI配置,而Android平台上,应用在构建的时候最终会将所有的jar合并,这样很容易造成相同的SPI冲突,常见的问题是DuplicatedZipEntryException异常
  • 读取SPI配置信息是在运行时从jar包中读取,由于apk是签过名的,在从jar中读取的时候,签名校验的耗时问题会造成性能损失

6. 优化思路

Java中使用ServiceLoader去读取SPI配置信息是在程序运行时,我们可以将这个读取配置信息提前,在编译时候就搞定,通过gradle插件,去扫描class文件,找到具体的服务类(可以通过标注来确定),然后生成新的java文件,这个文件中包含了具体的实现类。

这样程序在运行时,就已经知道了所有的具体服务类,缺点就是编译时间会加长,自己需要重新写一套读取SPI信息、生成java文件等逻辑。

经过优化后,SPI已经偏离了原本的初衷,但是可以做更多的事,可以将业务服务分离,通过SPI找到业务服务入口,业务组件化,抽成单独的aar,独立成工程。

7. 总结

SPI是java中原有的机制,在Android中可以直接使用,但是有上述提到的一些缺点,我们可以参考Java中的SPI机制,结合Android的环境,输出一套新的SPI机制。

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

### Android Studio中实现SPI通信的方式及示例 在Android Studio中实现SPI通信,通常需要结合JNI(Java Native Interface)调用底层C/C++代码来完成。这是因为Android系统本身并未直接提供高层的SPI接口支持,而是通过底层驱动程序实现。以下是一个完整的实现方式和示例。 #### 1. JNI层实现 通过JNI调用底层C/C++代码,可以访问Linux内核中的SPI设备。以下是一个简单的JNI方法定义: ```java public class SpiHelper { static { System.loadLibrary("native-spi"); } public native long openSpi(int busNum, int csNum); // 打开SPI设备 public native boolean writeData(long handle, byte[] data); // 写数据到SPI设备 public native byte[] readData(long handle, int length); // 从SPI设备读取数据 public native void closeSpi(long handle); // 关闭SPI设备 } ``` #### 2. C/C++层实现 在C/C++层,可以通过`/dev/spidevX.Y`文件与SPI设备交互。以下是一个示例实现: ```cpp #include <jni.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/spi/spidev.h> extern "C" { JNIEXPORT jlong JNICALL Java_com_example_spi_SpiHelper_openSpi(JNIEnv *env, jobject obj, jint busNum, jint csNum) { char device[32]; snprintf(device, sizeof(device), "/dev/spidev%d.%d", busNum, csNum); int fd = open(device, O_RDWR); if (fd < 0) { return -1; } return (jlong)fd; } JNIEXPORT jboolean JNICALL Java_com_example_spi_SpiHelper_writeData(JNIEnv *env, jobject obj, jlong handle, jbyteArray data) { jbyte *buffer = env->GetByteArrayElements(data, NULL); int length = env->GetArrayLength(data); int result = write((int)handle, buffer, length); env->ReleaseByteArrayElements(data, buffer, 0); return result == length; } JNIEXPORT jbyteArray JNICALL Java_com_example_spi_SpiHelper_readData(JNIEnv *env, jobject obj, jlong handle, jint length) { jbyteArray result = env->NewByteArray(length); jbyte *buffer = env->GetByteArrayElements(result, NULL); int bytesRead = read((int)handle, buffer, length); env->ReleaseByteArrayElements(result, buffer, 0); return result; } JNIEXPORT void JNICALL Java_com_example_spi_SpiHelper_closeSpi(JNIEnv *env, jobject obj, jlong handle) { close((int)handle); } } ``` #### 3. 使用示例 以下是在Java层调用上述JNI方法的示例: ```java public class MainActivity extends AppCompatActivity { private SpiHelper spiHelper = new SpiHelper(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); long spiHandle = spiHelper.openSpi(0, 0); // 打开SPI设备 if (spiHandle != -1) { byte[] dataToSend = {0x01, 0x02, 0x03}; boolean success = spiHelper.writeData(spiHandle, dataToSend); // 写数据 if (success) { byte[] receivedData = spiHelper.readData(spiHandle, 3); // 读数据 for (byte b : receivedData) { Log.d("SPI", "Received: " + String.format("0x%02X", b)); } } spiHelper.closeSpi(spiHandle); // 关闭SPI设备 } else { Log.e("SPI", "Failed to open SPI device."); } } } ``` #### 注意事项 - 确保目标设备已经正确配置了SPI驱动,并且`/dev/spidevX.Y`设备节点存在[^1]。 - 在使用JNI时,确保C/C++代码正确编译为共享库(`.so`文件),并加载到项目中。 - 需要设备具有root权限或适当权限以访问SPI设备[^4]。 #### 相关知识点 - SPI通信的基本原理和配置参数,如时钟频率、数据位数等[^5]。 - Android NDK开发环境的搭建和JNI调用机制[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值