Use ServiceLoader and AutoService in Android to crash the record

 

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

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

 

I have heard about ServiceLoader and AutoService for a long time, but have not had a deep understanding of related technologies due to various reasons.

In the past two days, I have stepped on countless hard pits when sorting out the two. A browser search found that the whole world is a concentrated dissemination of a certain article, which cannot solve my problem.

It took 2 days to die and finally completed the Demo, and gained countless pits, which are now recorded here to return to the society.

 

If you want to decouple and modularize the project business; you can make the business components have no strong dependencies and hide each other; and you can remove a business at any time without affecting other parts. Need to use the business cross communication through the interface between the components. As shown below:

1. The app layer communicates with the lower layer through the interface. When business changes need to be removed from the A business, there is no need to modify the entire project at all. But the app layer needs to rely on the A business component, the purpose is to put the code into the output package, rather than relying on calls at the code level.

2. The A and B business layers hide each other to avoid too high coupling of business calls.

3. The interface middle layer plays the role of isolation and connection between upper and lower layers, facilitating the interaction between businesses.

The figure above is the code structure of Demo, please refer to Demo.

 

1. Environmental description:

1. Kotlin development, version 1.3.72

2. AS version: 4.0

3. Gradle version: 6.1.1

4. JDK version: 1.8.0_192

That is, all are the latest versions, and there are no version compatibility issues mentioned on the Internet.

 

Second, the use of ServiceLoader.

At the app layer, use the ServiceLoader.load method to scan the file name and content of /resources/META-INF/services/packageName.InterfaceName, find the implementation class corresponding to the interface, and then call it.

According to the code structure in the figure above, it is also necessary to create new corresponding interface and implementation class mapping files in components A and B. Not much to say, see Demo for specific dependencies and diamante details.

 

Third, the use of AutoService.

Based on the above Demo, you will find that we have to manually create a new /resources/META-INF/... mapping file, which is too troublesome and easy to write wrong. Therefore, Google Daddy provided us with the AutoServcie tool, by marking the annotations, and then using the annotation processor to scan the annotations, and instead of us generating the /resources/META-INF/... file. Others are the same as above. Refer to DEMO for code details.

Pitfalls encountered in the process of testing AutoService:

1. Only add AutoService dependencies to modules that use @AutoService annotations.

Note: The following two lines of code are indispensable, otherwise they will have no effect.

Since AutoService is used, there is no need to create a new /resources/META-INF/servcies/... mapping file.

The mapping file of AutoServcieProcessor, Google Daddy has already done it for you in the dependency library.

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

 2. Note that when creating a new Android Project, the default minSdkVersion = 16. Here you need to modify the executable app module minSdkVersion = 26, otherwise the following problems will be reported when running.

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. It is found that when using AS to create a new Java class, there is no public keyword by default. The implementation class of the interface called by ServiceLoader.load must be public, and there must be a public constructor without parameters, otherwise the following error will be reported:

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<com.duke.liba.BookPythonImpl> is not accessible from java.lang.Class<java.util.ServiceLoader$LazyIterator>
        at java.lang.Class.newInstance(Native Method)

At the same time, don't forget to use AutoService annotation, correct code:

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

 

4. In the last tiankeng, no error is reported and no effect . After looking at my Demo, you will find that although the code is not much, it is written in Kotlin, and only the implementation class of the interface is written in java (such as the implementation class code above). Otherwise, the operation has no effect, and it will make you suspect that the code is wrong and the environment configuration is wrong.

Why can't the implementation class be written in Kotlin? I don't know, it may be the problem of Kotlin version, AS problem, AutoServcie problem, ServcieLoader problem. This pit stuck me for 2 days, otherwise it won't consume me so long, the technical point is not difficult.

Don't believe me? You try, the above implementation class is automatically converted to kotlin code before running.

 

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

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

 

 

Guess you like

Origin blog.csdn.net/fesdgasdgasdg/article/details/106888077