AndroidStudio制作Unity 2020.3插件的新坑

在Unity还是4.x 5.x的时代,为了做一些安卓原生功能,如获取电量,wifi信号强度等功能,或者是为了接入某些原生SDK,经常需要制作Unity的Android原生插件,当时基本就是基于Unity的安卓classes.jar制作plugin.jar,然后自定义AndroidManifest.xml,一起放到Unity项目的Assets/Plugins/Android下,然后Build安卓apk即可,简单粗暴。最近又有了做安卓插件的需求,这次换上了2020版的Unity,本以为还是当年熟悉的操作,没成想来了个大人,时代变了,踩了不少新坑:

首先,当年好像还是用Eclipse做Android开发,现在早就是Android Studio的天下了,我用的版本是:Chipmunk 2021.2.1 Patch 2,按着网上找到的教程(对,很久没用了,都快忘了怎么建项目了),创建了Android Library项目,又从Unity的“D:\Program Files\Unity\Hub\Editor\2020.3.30f1c1\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes”位置,拿到了熟悉的classes.jar,注意这里有一些变量,比如mono,现在还有了il2cpp的选择,Release对应还有Debug,根据实际情况进行选择吧。然后,随便写了几个简单的静态方法,Unity C#里调用了下,nice,没有问题,于是开始进行下一步,自定义入口activity,然后就开始踩坑了:

按照之前的经验,自定义入口activity的操作,基本就是写个activity类,然后extends UnityPlayerActivity,然后再自己写个AndroidManifest.xml,指定自己的activity为启动入口,这些基本也是Unity官方文档上有说明的:https://docs.unity3d.com/2020.3/Documentation/Manual/AndroidUnityPlayerActivity.html ,一通操作过后,神奇的发现Unity 2020的classes.jar中居然import不到UnityPlayerActivity了,导致插件库编译报错,于是找到了这个参考:Unity Android 重写UnityPlayerActivity https://zhuanlan.zhihu.com/p/451547547 ,文中提到,Unity 2019.3之后classes.jar中就不再包含原版入口activity类了,不明觉厉,于是又顺利在“D:\Program Files\Unity\Hub\Editor\2020.3.30f1c1\Editor\Data\PlaybackEngines\AndroidPlayer\Source\com\unity3d\player”找到了UnityPlayerActivity.java这个文件,看来是直接把源码扔出来了,放进Android Studio项目里,这下编译构建ok了,拿着输出的aar(对,这个也是新的,以前就是出个jar就行了,unity官方文档上也提到过这个,也不什么时候开始Android Studio的库项目就出的是aar了,其实就是原来的jar加上各种资源、jni库等,理应是更方便了吧),AndroidManifest.xml,放到Unity项目里,结果Build时报了这个错:

注: D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\unityLibrary\src\main\java\com\unity3d\player\UnityPlayerActivity.java使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\unityLibrary\build\.transforms\17536f9632ccf2b42cf1f00304f1b9b6\classes\classes.dex: D8: Type com.unity3d.player.UnityPlayerActivity is defined multiple times: D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\unityLibrary\build\.transforms\17536f9632ccf2b42cf1f00304f1b9b6\classes\classes.dex, D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\launcher\build\intermediates\external_libs_dex\debug\mergeExtDexDebug\classes.dex
com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: 
Type com.unity3d.player.UnityPlayerActivity is defined multiple times: D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\unityLibrary\build\.transforms\17536f9632ccf2b42cf1f00304f1b9b6\classes\classes.dex, D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\launcher\build\intermediates\external_libs_dex\debug\mergeExtDexDebug\classes.dex
Learn how to resolve the issue at https://developer.android.com/studio/build/dependencies#duplicate_classes.
	at com.android.builder.dexing.D8DexArchiveMerger.getExceptionToRethrow(D8DexArchiveMerger.java:132)
	at com.android.builder.dexing.D8DexArchiveMerger.mergeDexArchives(D8DexArchiveMerger.java:119)
	at com.android.build.gradle.internal.transforms.DexMergerTransformCallable.call(DexMergerTransformCallable.java:102)
	at com.android.build.gradle.internal.tasks.DexMergingTaskRunnable.run(DexMergingTask.kt:441)
	at com.android.build.gradle.internal.tasks.Workers$ActionFacade.run(Workers.kt:242)
	at org.gradle.workers.internal.AdapterWorkAction.execute(AdapterWorkAction.java:50)
	at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:50)
	at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:63)
	at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:59)
	at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:98)
	at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:59)
	at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
	at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102)
	at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
	at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
	at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:53)
	at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$2(DefaultWorkerExecutor.java:200)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:215)
	at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
	at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:131)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	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)
Caused by: com.android.tools.r8.CompilationFailedException: Compilation failed to complete
	at com.android.tools.r8.utils.W.a(:87)
	at com.android.tools.r8.D8.run(:11)
	at com.android.builder.dexing.D8DexArchiveMerger.mergeDexArchives(D8DexArchiveMerger.java:117)
	... 33 more
Caused by: com.android.tools.r8.utils.b: Error: D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\unityLibrary\build\.transforms\17536f9632ccf2b42cf1f00304f1b9b6\classes\classes.dex, Type com.unity3d.player.UnityPlayerActivity is defined multiple times: D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\unityLibrary\build\.transforms\17536f9632ccf2b42cf1f00304f1b9b6\classes\classes.dex, D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\launcher\build\intermediates\external_libs_dex\debug\mergeExtDexDebug\classes.dex
	at Version.fakeStackEntry(Version_2.0.88.java:0)
	at com.android.tools.r8.utils.O0.a(:21)
	at com.android.tools.r8.utils.N0.b(:7)
	at com.android.tools.r8.utils.N0.a(:27)
	at com.android.tools.r8.utils.N0.a(:10)
	at java.util.concurrent.ConcurrentHashMap.merge(ConcurrentHashMap.java:1990)
	at com.android.tools.r8.utils.N0.a(:6)
	at com.android.tools.r8.graph.D0$c.f(:3)
	at com.android.tools.r8.dex.a.a(:83)
	at com.android.tools.r8.dex.a.a(:10)
	at com.android.tools.r8.D8.d(:6)
	at com.android.tools.r8.D8.b(:1)
	at com.android.tools.r8.utils.W.a(:30)
	... 35 more


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':launcher:mergeDexDebug'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
   > com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: 
     Type com.unity3d.player.UnityPlayerActivity is defined multiple times: D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\unityLibrary\build\.transforms\17536f9632ccf2b42cf1f00304f1b9b6\classes\classes.dex, D:\Projects\Unity2020\pw_iot2\Temp\gradleOut\launcher\build\intermediates\external_libs_dex\debug\mergeExtDexDebug\classes.dex
     Learn how to resolve the issue at https://developer.android.com/studio/build/dependencies#duplicate_classes.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 23s
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8

UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)

大致看了一下,就明白怎么回事了,原来的UnityPlayerActivity在classes.jar里,以.class形式存在,然后呢,我们的库项目只是使用classes.jar作为引用库编译,最终Unity构建整个apk时还会加上实际的classes.jar一起编译,而把UnityPlayerActivity.java放进我们的项目里,生成的插件aar中的jar里便会包含一份UnityPlayerActivity.class,这时Unity最终构建apk时这个class就会重复定义报错!解决的方法嘛,可以按照上面的参考知乎里提到的手动删jar里的class,再重新打包,我目前的做法是将UnityPlayerActivity改个名,比如OriginalUnityPlayerActivity,然后再extends,或者还看到网上有说把UnityPlayerActivity先打个库jar包,然后和classes.jar一起“compileOnly files(‘libs/unity-activity.jar’)”引用,虽然有点麻烦,但理论上可行,又或者按Unity官方文档里提到的方法,干脆直接改这个.java。

不过这几种做法感觉都不是那么的“优雅”,而且官方文档里也没提到现在这种应该是挺大众的做法下,怎么解决重复定义问题,本来我想研究下gradle脚本操作,看能不能自定义下jar包的打包逻辑,排除指定.class文件,无奈gradle实在太复杂,试了半天也没成功,先这样临时解决下,以后有时间再研究更加“优雅”的处理方法吧。

博主友情提示:

如您在评论中需要提及如QQ号、电子邮件地址或其他隐私敏感信息,欢迎使用>>博主专用加密工具v3<<处理后发布,原文只有博主可以看到。