ServiceLoader Demo:https://github.com/mengzhinan/ServiceLoader_test
AutoService Demo:https://github.com/mengzhinan/AutoService_test
对 ServiceLoader 和 AutoService 早有耳闻,因各种原因而未对相关技术深入了解。
这两天在整理二者时踩到了无数硬坑,浏览器搜索发现全世界都是某一篇文章的集聚性传播,无法解决我的问题。
耗时 2 天死磕终于完成 Demo,并收获无数坑,现记录于此回报社会。
如果想对项目业务之间进行解耦、模块化开发;能使业务组件之间没有强依赖,彼此互相隐藏;且能够随时剔除某一个业务而不影响其他部分。就需要用到组件间通过接口进行业务交叉通讯了。如下图:
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