在浏览 readium kotlin-toolkit 项目时注意到 README 里面提了一句:
If you target Android devices running below API 26, you must enable core library desugaring in your application module.
脱糖(desugaring) 是 Android 编程中支持在旧设备上使用新的 Java API 的方式。README 中提到的 API 26 对应的是 Android 8,先看 Android 8 Updated Java language support :
Android 8.0 (API level 26) adds support for several additional OpenJDK Java APIs:
java.time
from OpenJDK 8.
java.nio.file
and java.lang.invoke
from OpenJDK 7.
根据以上信息在代码里搜索,可知代码使用了 java.nio.file
包下的类:
1 2 3 4 5 6 7 8 readium\shared\src\main\java\org\readium\r2\shared\util\zip\compress\utils\MultiReadOnlySeekableByteChannel.java 27 :import java.nio.file.Path;readium\shared\src\main\java\org\readium\r2\shared\util\zip\compress\archivers\zip\ZipSplitReadOnlySeekableByteChannel.java 29 :import java.nio.file.Path;readium\shared\src\main\java\org\readium\r2\shared\util\zip\compress\archivers\zip\ZipArchiveEntry.java 31 :import java.nio.file.attribute.FileTime;
字节码探究 找到 org.readium.kotlin-toolkit:readium-shared:3.0.3
的 class.jar
文件,注意到 readium-shared-3.0.3\classes\org\readium\r2\shared\util\zip\compress\utils\MultiReadOnlySeekableByteChannel.class
文件的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ... Constant pool: ... #141 = Class #142 // java/nio/file/Path #142 = Utf8 java/nio/file/Path ... static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=1, locals=0, args_size=0 0: iconst_0 1: anewarray #141 // class java/nio/file/Path 4: putstatic #143 // Field EMPTY_PATH_ARRAY:[Ljava/nio/file/Path; 7: return LineNumberTable: line 46: 0 ...
字节码中指示程序分配一个 java.nio.file.Path
的数组,这要求系统上存在 java.nio.file.Path
类。
Desugaring 虽然库提供的 jar 内没有看到明显的脱糖内容(从下文的脱糖流程图中可以得到解释,因 Android 脱糖的步骤在于从原始的 .class
文件生成脱糖 .dex
文件),但 AAR 内的 aar-metadata.properties
内写明了相关信息:
1 2 3 4 5 6 7 aarFormatVersion=1.0 aarMetadataVersion=1.0 minCompileSdk=1 minCompileSdkExtension=0 minAndroidGradlePluginVersion=1.0.0 coreLibraryDesugaringEnabled=true desugarJdkLib=com.android.tools:desugar_jdk_libs:2.0.4
不开启 isCoreLibraryDesugaringEnabled
则不能通过 AAR metadata 检查,而要通过编译还需要增加脱糖依赖 coreLibraryDesugaring("com.android.tools:desugar_jdk_libs_nio:2.0.4")
。
Desugaring 的流程如下:
gradlew assembleRelease
后可以发现 apk 中有一个 j$
包下包含了大量脱糖代码,这些代码原本是在 java
这个父包下,因此 java.nio.file.Path
现在在 apk 内就是 j$.nio.file.Path
。注意到,对应的 MultiReadOnlySeekableByteChannel 下的包引入也产生了变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package org.readium.r2.shared.util.zip.compress.utils;import j$.nio.file.Path; import java.io.IOException;import java.nio.ByteBuffer;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.Collections;import java.util.Iterator;import java.util.List;import java.util.Objects;import org.readium.r2.shared.util.zip.jvm.ClosedChannelException;import org.readium.r2.shared.util.zip.jvm.NonWritableChannelException;import org.readium.r2.shared.util.zip.jvm.SeekableByteChannel;public class MultiReadOnlySeekableByteChannel implements SeekableByteChannel { }
这是可以理解的:这个包名是一个与系统上其他任何包都不一样的名字,这样做可以保证当新系统升级后提供了对应的原始类支持时,j$
包名下的脱糖类和原类之间不会产生同名的冲突。
结语 我开发 App 都在用 Kotlin,目前并不直接使用 Java 8+ 的 API(用也是 Kotlin 封好了的,或者 SDK 在更低),但是用到的库可能用的是 Java 并用到了 Java 8+ API(虽然 Kotlin 自己的版本问题也会引来混乱(经典的如 kotlin-dsl
自带 kotlin))。但不管怎么样,明白了脱糖的基本运作方式,下一次碰到相关问题就有能力解决了。
参考 Jake Wharton - D8 Library Desugaring
闲话:不脱糖的后果 1 2 3 4 5 6 7 8 9 Process: me.galiren.desugartest, PID: 6422 java.lang.NoClassDefFoundError: Failed resolution of: [Ljava/nio/file/Path; at org.readium.r2.shared.util.zip.compress.utils.MultiReadOnlySeekableByteChannel.<clinit>(MultiReadOnlySeekableByteChannel.java:46) at me.galiren.desugartest.MainActivity.onCreate(MainActivity.kt:21) ... Caused by: java.lang.ClassNotFoundException: Didn't find class "java.nio.file.Path" on path: DexPathList[[zip file "/data/app/me.galiren.desugartest-1/base.apk"],nativeLibraryDirectories=[/data/app/me.galiren.desugartest-1/lib/x86, /system/lib, /vendor/lib]] at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56) at java.lang.ClassLoader.loadClass(ClassLoader.java:380) at java.lang.ClassLoader.loadClass(ClassLoader.java:312)