Android 中使用 ServiceLoader、AutoService 摔坑记录

 

ServiceLoader Demo:https://github.com/mengzhinan/ServiceLoader_test

AutoService Demo:https://github.com/mengzhinan/AutoService_test

 

对 ServiceLoader 和 AutoService 早有耳闻,因各种原因而未对相关技术深入了解。

这两天在整理二者时踩到了无数硬坑,浏览器搜索发现全世界都是某一篇文章的集聚性传播,无法解决我的问题。

耗时 2 天死磕终于完成 Demo,并收获无数坑,现记录于此回报社会。

 

如果想对项目业务之间进行解耦、模块化开发;能使业务组件之间没有强依赖,彼此互相隐藏;且能够随时剔除某一个业务而不影响其他部分。就需要用到组件间通过接口进行业务交叉通讯了。如下图:

Android 中使用 ServiceLoader、AutoService 摔坑记录_第1张图片

1、app 层与下层通讯通过接口,当业务变动需要剔除 A 业务时,对整个工程丝毫不需要修改变动。但是 app 层需要依赖 A 业务组件,目的是为了把代码打进输出包,而非代码层面的依赖调用。

2、A、B 业务层之间互相隐藏,避免业务调用耦合太高。

3、interface 中间层起到上下层隔离和联系作用,方便业务之间的交互。

上图即 Demo 的代码结构,参考 Demo。

 

一、环境说明:

1、Kotlin 开发,版本 1.3.72

2、AS 版本: 4.0

3、Gradle 版本:6.1.1

4、JDK 版本:1.8.0_192

即全部为最新版本,不存在网上说的版本兼容问题。

 

二、ServiceLoader 使用。

在 app 层通过 ServiceLoader.load 方法,扫描 /resources/META-INF/services/packageName.InterfaceName 文件名和内容,找到接口对应的实现类,然后调用。

按照上图的代码结构,还需要在 A 和 B 组件内新建对应的接口和实现类映射文件。简单不多说了,具体依赖和diamante细节看 Demo。

 

三、AutoService 使用。

在上述 Demo 基础上,你会发现还要我们手动新建 /resources/META-INF/... 映射文件,太麻烦了还容易写错。故 Google 爹爹给我们提供了 AutoServcie 工具,通过标记注解,然后使用注解处理器扫描数注解,并代替我们生成  /resources/META-INF/... 文件。其他的与上面方式一样。 代码细节参考 DEMO。

在试验 AutoService 过程中遇到的坑:

1、只在使用到 @AutoService 注解的模块中添加 AutoService 依赖。

注意:下面两行代码不可缺少,否则运行无效果的。

既然使用了 AutoService,就不需要新建 /resources/META-INF/servcies/... 映射文件了。

AutoServcieProcessor 的映射文件,Google 爹爹已经在依赖库中帮你做了。

// 依赖 autoService 库
implementation 'com.google.auto.service:auto-service:1.0-rc7'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'

 2、注意,新建 Android Project 时,默认的 minSdkVersion = 16。此处需要修改可执行的 app 模块 minSdkVersion = 26,否则运行报如下问题。

Invoke-customs are only supported starting with Android O (--min-api 26)

Invoke-customs are only supported starting with Android O (--min-api 26)
Stack trace:
com.android.tools.r8.a: Invoke-customs are only supported starting with Android O (--min-api 26)
	at com.android.tools.r8.dex.r.a(:289)
	at com.android.tools.r8.dex.r.a(:98)
	at com.android.tools.r8.dex.r.b(:188)
	at com.android.tools.r8.dex.b.a(:63)
	at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.runInterruptibly(TrustedListenableFutureTask.java:125)
	at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:69)
	at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:78)
	at com.google.common.util.concurrent.MoreExecutors$DirectExecutorService.execute(MoreExecutors.java:322)
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
	at com.google.common.util.concurrent.AbstractListeningExecutorService.submit(AbstractListeningExecutorService.java:66)
	at com.google.common.util.concurrent.AbstractListeningExecutorService.submit(AbstractListeningExecutorService.java:36)
	at com.android.tools.r8.dex.b.b(:46)
	at com.android.tools.r8.D8.d(:87)
	at com.android.tools.r8.D8.b(:1)
	at com.android.tools.r8.utils.W.a(:30)
	at com.android.tools.r8.D8.run(:11)
	at com.android.builder.dexing.D8DexArchiveBuilder.convert(D8DexArchiveBuilder.java:116)
	at com.android.build.gradle.internal.dependency.BaseDexingTransform.process(DexingTransform.kt:296)
	at com.android.build.gradle.internal.dependency.BaseDexingTransform.processNonIncrementally(DexingTransform.kt:243)
	at com.android.build.gradle.internal.dependency.BaseDexingTransform.doTransform(DexingTransform.kt:153)
	at com.android.build.gradle.internal.dependency.BaseDexingTransform.access$doTransform(DexingTransform.kt:69)
	at com.android.build.gradle.internal.dependency.BaseDexingTransform$transform$1.invoke(DexingTransform.kt:104)
	....................................................
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.lang.Thread.run(Thread.java:748)
	Suppressed: java.util.concurrent.ExecutionException: com.android.tools.r8.a: Invoke-customs are only supported starting with Android O (--min-api 26)
		at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:552)
		at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:513)
		at com.google.common.util.concurrent.FluentFuture$TrustedFuture.get(FluentFuture.java:86)
		at com.android.tools.r8.utils.S0.a(:14)
		at com.android.tools.r8.dex.b.b(:101)
		... 120 more
	[CIRCULAR REFERENCE:com.android.tools.r8.a: Invoke-customs are only supported starting with Android O (--min-api 26)]

3、发现现在使用 AS 新建 Java 类时,默认没有 public 关键字。此处使用 ServiceLoader.load 调用的接口的实现类必须是 public 类型的,且必须有公共无参的构造函数,否则报下面错误:

java.util.ServiceConfigurationError: com.duke.libinterface.IBook: Provider com.duke.liba.BookPythonImpl could not be instantiated

2020-06-21 18:31:20.459 9537-9537/com.duke.autoservice_test E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.duke.autoservice_test, PID: 9537
    java.util.ServiceConfigurationError: com.duke.libinterface.IBook: 
Provider com.duke.liba.BookPythonImpl could not be instantiated
        at java.util.ServiceLoader.fail(ServiceLoader.java:233)
        at java.util.ServiceLoader.access$100(ServiceLoader.java:183)
        at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:392)
        at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:416)
        at java.util.ServiceLoader$1.next(ServiceLoader.java:494)
        at com.duke.autoservice_test.ServiceLoaderHelper.loadServices(ServiceLoaderHelper.kt:21)
        at com.duke.autoservice_test.MainActivity.onResume(MainActivity.kt:27)
        at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1456)
        at android.app.Activity.performResume(Activity.java:8119)
        at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4333)
        at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4375)
        at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2049)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7523)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:941)
     Caused by: java.lang.IllegalAccessException: java.lang.Class is not accessible from java.lang.Class
        at java.lang.Class.newInstance(Native Method)

同时不要忘记了使用 AutoService 注解,正确代码:

@AutoService(IBook.class)
public class BookTCPIPImpl implements IBook {
    @Nullable
    @Override
    public String getBookName() {
        return "book b";
    }
}

 

4、最后天坑,不报错也无效果。看我的 Demo 后会发现,虽然代码不多,但是都是使用 kotlin 写的,只有接口的实现类是用 java 写的(如上面实现类代码)。否则的话运行无效果,还会让你怀疑代码写错了、环境配置有问题。

为什么实现类不能用 kotlin 写?我不知道,可能是 kotlin 版本的问题、AS 的问题、AutoServcie 的问题、ServcieLoader 的问题。这个坑卡了我 2 天,否则不会消耗我那么久的时间,本来技术点就不难。

大佬不信?你试试,把上面的实现类自动转换为 kotlin 代码后再运行。

 

ServiceLoader Demo:https://github.com/mengzhinan/ServiceLoader_test

AutoService Demo:https://github.com/mengzhinan/AutoService_test

 

 

你可能感兴趣的:(Android)