android脱糖
前言
另外一篇相关文章:https://androidblog.blog.csdn.net/article/details/148574130
通过sourceCompatibility
和targetCompatibility
可以实现低版本写代码高版本输出(详情可查看我的另一篇文件中的:验证各种Java版本 > 一、纯Java项目 > 3. 疑问 这个章节进行查看),但是这种需求并不常用,而高版本写代码低版本输出呢,这才是常用的,而且我们Adnroid开发可以说天天都在这么用,那这是怎么实现的?比如Android 5
、Adnroid 6
系统原生只支持到Java 7
,Android 7
原生仅支持部分Java 8
特性(比如Lambda表达式),到了Android 8
则完整支持Java 8
了。我们写Android项目时,对于兼容性,一般来说指定minSdk
就可以了,比如minSdk
设置为21,对应为Android 5
,也就是说这个项目需要兼容的最低版本是Android 5
,假设我们把targetCompatibility
和jvmTarget
设置为11,那生成的字节码是Java 11
版本的字节码,为什么可以在Android 5
上运行?Android 5
系统原生只支持到Java 7
啊,为什么运行没问题?这是因为Android并不是直接运行.class字节码文件的,Android运行的是dex
,所以在Java 11
字节码文件转换为dex
的时候,转换工具就动了手脚,它会根据你声明的minSdk
来转换为对应Java
版本可以支持的dex
,这种操作叫“脱糖”,比如你在代码中使用了Lambda表达式,这是Java 8的特性,脱糖会把Lambda转换为接口的对应实现,简单理解就是:本来你用的是高版本的特性实现的一些功能,脱糖就是给你转换为低版本实现的对应功能。
用Kotlin写代码,Kotlin语言就有很多的语法糖,但是Kotlin最终是要编译为字节码文件的,那这些语法糖就会全部被脱掉,举个例子,比如Java 8是2014年发布的,Java 8才有Lambda表达式,而Kotlin是2011年推出的,假设你在2011年时使用kotlin,那时就可以使用kotlin写Lambda表达式了,但是那时的java并不支持Lambda表达式啊,所以那时kotlin的Lambda就是语法糖,当它编译为java字节码的时候,字节码里面肯定就没有Lambda了,Lambda被转换为对应的接口实现了。
Android不依赖JVM,Android设备本身也没有JVM,Android App是运行在 ART(Android Runtime) 或旧版的 Dalvik
虚拟机 上,而非标准 JVM。ART
或Dalvik
执行的是 DEX
字节码(.dex
文件),而非 Java 的 .class
文件。编译过程:Java/Kotlin 代码 → Java 字节码(.class)→ D8/R8 编译器 → DEX 字节码(针对 Android 优化)。所以,脱糖就是在D8/R8
阶段把.class
转换为dex
时把不兼容的字节码转换为兼容的字节码,比如把Java 11字节码转换为等效的Java 7字节码。
例如:
// Java 11 语法(Lambda)
Runnable task = () -> System.out.println("Hello");
会被脱糖为:
// Java 7 兼容形式(匿名内部类)
Runnable task = new Runnable() {@Override public void run() { System.out.println("Hello"); }
};
除了脱糖,也可以使用一些第三方库代替,比如Java 8的 java.time
,如果你的Android设备不支持Java8,可以通过依赖Jake Wharton 的 ThreeTenABP库,该库就包含了 java.time
的相关功能,再比如对于Lambda
表达式,如果不使用脱糖,则可以用Retrolambda + Gradle 插件
解决。不过既然现在脱糖能解决肯定是优选使用脱糖功能。
Android 脱糖(Desugaring)全面解析
1. 基本概念
在 Android 开发中,“脱糖”(Desugaring)并非仅指语法糖的脱糖,它包含两个核心维度:
- 语言特性脱糖(Language Feature Desugaring)
- API 脱糖(API/Library Desugaring)
脱糖的目的是解决旧版本的 Android 系统对 新版本的Java兼容性不足的问题。
语言特性脱糖 与 API脱糖 对比:
类型 | 语言特性脱糖 | API 脱糖 |
---|---|---|
目标 | 转换语法结构 → 兼容低版本 JVM | 提供缺失 API → 兼容低版本 Android |
实现层级 | 编译期字节码重写 | 编译期重定向 + 运行时库嵌入 |
依赖 | 编译器内置能力 | 需显式引入脱糖库 |
- 语言特性脱糖:解决语法糖在低版本 Android 的兼容性问题
- API 脱糖:解决新版 Java API 在旧系统上的缺失问题
- 不是所有高版特性或API都支持脱糖的。
2. 语言特性脱糖(Language Feature Desugaring)
2.1 定义
针对 Java 引入的新语法特性,在编译阶段转换为低版本 Android 系统可识别的字节码结构。
2.2 作用对象
Java 8+ 的语言特性,例如:
- Lambda 表达式(如
() -> {}
) - 接口默认方法(
default
方法) - 方法引用(如
Object::toString
) try-with-resources
语句- 变量声明
var
2.3 实现原理
编译器(如 D8/R8)在生成 .dex
文件前,将这些语法糖重写为等效的匿名内部类或传统代码结构。例如 Lambda 会被转换为实现接口的匿名类。
2.4 配置示例
在模块的 build.gradle
中启用:
android {compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}
}
3. API 脱糖(API/Library Desugaring)
3.1 定义
针对 Java 新版本新增的 SDK API
(如 java.util.stream
、java.time
),在旧 Android 系统(API < 24)上提供等效实现。
3.2 作用对象
Java 8+ 新增的类和方法,例如:
- 流式处理(
Stream API
) - 新版日期时间 API(
java.time
) - 函数式接口(如
java.util.function
)
3.3 实现原理
- 编译器将代码中对新 API 的调用重定向到脱糖库(如
com.android.tools:desugar_jdk_libs
)中的实现。 - 该库会被打包到 APK 的 DEX 文件中,在运行时替代系统缺失的 API。
3.4 配置示例
android {defaultConfig {// 将minSdkVersion设置为20或更低时需要multiDexEnabled = true}compileOptions {// 标志以启用对新语言api的支持// 适用于 AGP 4.1+isCoreLibraryDesugaringEnabled = true// 适用于 AGP 4.0// coreLibraryDesugaringEnabled = true// 设置Java兼容性为Java 11sourceCompatibility = JavaVersion.VERSION_11targetCompatibility = JavaVersion.VERSION_11}
}dependencies {// 适用于 AGP 7.4+coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")// 适用于 AGP 7.3// coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.3")// 适用于 AGP 4.0 to 7.2// coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.9")
}
4. 脱糖的执行工具
两类脱糖均由 R8 编译器(替代早期的 D8 + ProGuard)统一处理(关于DX、D8、R8的另一篇文章):
-
R8 的角色:
-
整合了代码压缩(ProGuard)和字节码转换(D8)能力。
-
在编译流程中同时处理语言特性脱糖和 API 重定向。
-
-
与实时编辑(Live Edit)的协作:
- 在 Android Studio 的实时编辑功能中,脱糖需确保动态注入的字节码与构建系统的脱糖方式一致,避免运行时崩溃。
Android 脱糖功能发展时间线
1. 语言特性脱糖(语法糖支持)
- Android Gradle Plugin 3.0(2017年9月发布)
- 首次引入对 Java 8 语言特性的部分支持
- 支持特性:
- Lambda 表达式
- 方法引用
- 默认接口方法、静态接口方法
- 类型注解、重复注解
Try-with-recources
- Java 7语言特性在所有的Android API levels中都可用。
- 如果使用了第三方的retrolambda库,则要删除retrolambda库的依赖和配置才能开启Java8脱糖。
- 对应 Android Studio 3.0
2. API 脱糖(库脱糖)
- Android Gradle Plugin 4.0(2020年4月发布)
- 正式引入
coreLibraryDesugaring
功能 - 支持 Java 8+ API(如
java.time
,Stream API
) - 对应 Android Studio 4.0
- 正式引入
3. 重要演进节点
版本 | 发布时间 | 主要改进 |
---|---|---|
AGP 3.0 (D8 编译器) | 2017.09 | 基础语法糖脱糖 |
AGP 3.4 | 2019.03 | 改进 Lambda 脱糖性能 |
AGP 4.0 (R8 编译器) | 2020.04 | 完整 API 脱糖支持 |
AGP 7.0 | 2021.09 | 支持 Java 11 语言特性脱糖 |
从这个表格中可以了解到Android从Java8开始支持脱糖,可见当时的Java8版本是多么的重要。
- 2014年Oracle公司推出了Java 8
- 2017年,Android官方推出了Android 8.0,该版本原生支持Java 8。同时还推出了Android Studio 3.0,该版本支持Java 8脱糖。
从表格中还可以了解到目前Android官方主要实现了Java 8
和Java 11
的脱糖,这也是为什么当前(2025-06-13)最新Android Studio
创建项目时默认的配置是使用Java 11
,比如创建一个Kotlin语言的Android项目,默认配置如下:
android {compileOptions {sourceCompatibility = JavaVersion.VERSION_11targetCompatibility = JavaVersion.VERSION_11}kotlinOptions {jvmTarget = "11"}
}
sourceCompatibility = 11
限制了我们写代码的时候不能使用超过Java 11的语言特性,targetCompatibility = 11
和jvmTarget = 11
则设置了生成.class
字节码文件的版本为Java 11
版本,这正好是Android支持脱糖比较好的Java版本。
需要注意的是,即使我们把上面的3个参数都设置为JDK 21
版本,然后在Java代码中使用var、Lambda表达式,使用Java 8的time api等,脱糖工作一样是会进行的,但是设置为JDK 11,写代码的时候基本上所有 JDK 11的代码你都能写不会报错,如果你设置为JDK 21,在写代码时使用JDK 21的API,你会发现基本上你都用不了,因为脱糖还没有覆盖到JDK 21的API,就算有覆盖到那也只是覆盖到极少的一小部分,反正我尝试了调用多个JDK 21的API,全部是报错的,它们需求你把minSdk设置为比较高的版本才可以使用的,这样就没办法适配低版本的设备了。
突然我又有了个不一样的想法,如果我把版本设置为JDK 21,但是我写代码时不使用 JDK 21的那些API,只使用Java 11或以下版本的API,那编译运行也不会有兼容性问题啊,那这样的话,由于代码编译为JDK 21的字节会不会有编译优势,比如效率提高减少编译时间等。如果这样可行,那可以配置成这样:
java {sourceCompatibility = JavaVersion.VERSION_11 // 源码语法限制为Java 11targetCompatibility = JavaVersion.VERSION_21 // 字节码生成兼容Java 21
}
有时间可以试试看这样搞一搞。
4. 技术实现演进
- D8 编译器阶段(AGP 3.0+)
- 仅支持基础语法转换
- R8 编译器阶段(AGP 4.0+)
- 整合脱糖与代码优化
- 支持完整的 API 重定向
脱糖试验
1. 语言特性脱糖
Android 5
和Android 6
设备原生支持Java 7
,但不支持Java 8
,创建一个纯Java语言的Android项目,设置minSdk
为21
,build.gradle.kts
中的一些默认配置如下:
compileOptions {sourceCompatibility = JavaVersion.VERSION_11targetCompatibility = JavaVersion.VERSION_11
}
targetCompatibility
设置了编译的字节码为Java 11
版本。
在代码中使用Lambda表达式,如下:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);var button = findViewById(R.id.button);button.setOnClickListener(v -> showToast("Hello World"));}private void showToast(String msg) {Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();}}
如上是Java
代码:
button
变量的声明使用了var
,这是Java 10
推出的语言特性。button
点击事件使用了Lambda
,这是Java 8
推出的语言特性。
然后运行App到Android 6.0
手机上,一切正常,我当前使用的AGP
版本是8.10.1
,这说明AGP
默认就开启了语言特性脱糖
,因为你创建Android项目时默认就配置好了,示例如下:
android {...compileOptions {sourceCompatibility = JavaVersion.VERSION_11targetCompatibility = JavaVersion.VERSION_11}// For Kotlin projectskotlinOptions {jvmTarget = "11"}
}
如果是比较旧版本的Android Studio
,它的默认值是这样的:
android {...compileOptions {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8}// For Kotlin projectskotlinOptions {jvmTarget = "1.8"}
}
sourceCompatibility
控制了写代码时允许使用的最高版本的Java语言特性
, targetCompatibility
和 jvmTarget
则控制了生成.class
字节码的版本,如果设置为 1.8
,则AGP
会自动完成Java 8
的语言特性脱糖,如果设置为11
,则会AGP
会自动完成Java 11
的语言特性脱糖,Java 11
比Java8
要新,Java8
有的语言特性在Java11
中也会有,所以Java 11
的语言特性脱糖自然也包含了Java8
的语言特性脱糖。再比如我把targetCompatibility
设置为21
,虽然说Android
可能还没有实现对21
版本的相关脱糖,但是Java 8
和Java 11
版本中有的特性或API
在Java 21
版本上也会有,所以设置targetCompatibility
为21
也是会进行Java 8
和Java 11
脱糖的。
2. API 脱糖
在代码中使用Java 8
的时间API
,直接报错了,截图如下:
错误提示说需要 API 26
才能调用now()
函数,为什么需要API 26
才能调用?因为API 26
对应 Android 8.0
,从这个版本开始原生就支持 Java 8
了,即Android 8.0
这个版本不需要脱糖,原生就支持Java 8
了。
AGP
默认没有开启API脱糖
,如果想要使用API脱糖
,需要手动配置来开启,如下:
compileOptions {isCoreLibraryDesugaringEnabled = truesourceCompatibility = JavaVersion.VERSION_11targetCompatibility = JavaVersion.VERSION_11
}
设置后同步一下Gradle
,此时会报错,如下:
coreLibraryDesugaring configuration contains no dependencies. If you intend to enable core library desugaring, please add dependencies to coreLibraryDesugaring configuration.
错误提示说coreLibraryDesugaring
配置没有包含依赖,如果你想要开启核心库脱糖,请添加依赖到coreLibraryDesugaring
配置。
添加依赖如下:
dependencies {...coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
}
设置后同步Gradle,此时再看之前的Java代码就没有报错了,代码如下:
import java.time.LocalDate;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);var button = findViewById(R.id.button);button.setOnClickListener(v -> {var today = LocalDate.now();showToast(today.getClass().getName());});}private void showToast(String msg) {Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();}}
如上代码,这里的LocalDate
导包为:java.time.LocalDate
,运行到Android 6.0
手机上,显示的LocalDate
的类名为:j$.time.LocalDate
,这就是desugar_jdk_libs
库中的脱糖类,因为Android 6.0
系统中并不存在java.time.LocalDate
,所以使用脱糖库中的j$.time.LocalDate
代替来完成同样的功能。
从Android 8.0
开始原生就支持Java 8
了,那Android 11
肯定也原生支持Java 8
了,然后我把程序运行到Android 11
的设备上(因为我没有Android 8.0
的设备),发现显示的类名依旧是j$.time.LocalDate
,我问了DeepSeek说在旧版本会使用脱糖类,在新版本会使用原生类,我以为的逻辑是这样的:
if (Build.VERSION.SDK_INT >= 26) {// 使用java.time.LocalDate
} else {// 使用j$.time.LocalDate
}
然后又问了DeepSeek
为什么在新版本上运行类名也是j$.time.LocalDate
,回答如下:
当代码中使用LocalDate
时,全部都会替换成使用j$.time.LocalDate
,而当调用LocalDate
中的函数时,才会发生调用路径的判断处理,比如我们调用了LocalDate.now()
函数时:
// 脱糖库的伪代码逻辑
public class j$.time.LocalDate {static LocalDate now() {if (Build.VERSION.SDK_INT >= 26) {return java.time.LocalDate.now(); // 调用系统 java.time.LocalDate的实现} else {return DesugarImpl.now(); // 调用脱糖库的兼容实现}}
}
我想到一个办法,就是让代码抛出一个异常,然后看调用堆栈中有没有使用到原生的实现类,代码如下:
button.setOnClickListener(v -> {var localDate = LocalDate.parse("2024-02-30");showToast(localDate.toString());
});
我让LocalDate
解析一个2月30号的日期,这肯定会抛出异常,因为2月最多只有28号,但是另人失望,运行到Android11设备上,堆栈上全是脱糖库中的类,如下:
j$.time.format.DateTimeParseException: Text '2024-02-30' could not be parsed: Invalid date 'FEBRUARY 30'at j$.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1970)at j$.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1905)at j$.time.LocalDate.parse(LocalDate.java:428)at j$.time.LocalDate.parse(LocalDate.java:413)
实验证明,DeepSeek
是错误的,如果LocalDate.parse()
内部会委托给原生类,那我的异常堆栈中 “at j$.time.LocalDate.parse(LocalDate.java:413)
” 的上一行就会是原生类了,然而并不是,这说明只要使用了脱糖,不论是运行到什么版本的Android设备上,都永远只会使用脱糖类。
我感觉DeepSeek
的答案可能大部分来自国内,而ChatGPT的答案应该大多数来自国外,于是我在ChatGPT上又把问题问了一次,答案如下:
在 Android 开发中启用了 API 脱糖(API desugaring) 后,App 无论运行在低版本还是高版本设备上,都会使用脱糖实现,而不是在高版本上自动切换回原生实现。
✅ API 脱糖的工作机制:
- API 脱糖是在 编译期 由 D8 或 R8 工具将使用的新 API(例如 java.time, java.util.stream 等)转换为对兼容类库的调用。
- 这些脱糖类(backport classes)会被打包进你的 APK 或 dex 文件中。
- 无论设备运行的是 Android 5、8、11 还是 14,App 都是执行编译时生成的字节码,也就是使用脱糖替代实现。
❗不会在运行时判断系统版本选择原生实现:
- D8/R8 并不会在生成字节码时插入诸如 if (Build.VERSION.SDK_INT >= X) 的运行时判断逻辑。
- 脱糖类是为了兼容旧系统而设计的,它们在高版本系统上也照样运行。
- 脱糖后的代码不会自动利用新系统的原生 API 实现,除非你自己在代码中手动判断并调用原生 API。
所以,这里得到一个经验:技术上的问题,不要完全相信DeepSeek
,可以把DeepSeek
和ChatGPT
同时使用,看看答案是否有分歧,无分歧时采用,有分歧时就只能靠自己聪明的大脑去决定相信哪一个了。
当然了,ChatGPT也是不能完全相信的,比如它的答案中说到:
✅ 如果你想在高版本上用原生实现呢?
那你必须手动判断:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LocalDate date = LocalDate.now(); // 原生实现
} else {
// 使用脱糖或第三方兼容代码
}
这也是不行的,即使做了版本判断,它也是会被替换为脱糖的实现的。如果真的想调用原生的怎么办?有一个办法,那就是使用字符串的反射调用,脱糖应该还没这么聪明到连我们的字符串也要替换为脱糖吧,示例如下:
try {Object today;if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {var clazz = Class.forName("java.time.LocalDate");var method = clazz.getMethod("now");today = method.invoke(null);} else {today = LocalDate.now();}showToast(today.getClass().getName() + ": " + today);
} catch (Exception e) {e.printStackTrace();
}
- 运行到
Android 6
设备上显示:j$.time.LocalDate: 2025-06-13
- 运行到
Android 11
设备上显示:java.time.LocalDate: 2025-06-13
虽然这样实现了在高版本调用原生实现,在低版本调用脱糖实现,但是最好还是不要这样做,仔细想一想,如果调用原生实现有好处的话,那Android官方为什么不自动帮我们完成这个事情,脱糖库中的实现是怎么实现的?比如LocalDate
,搞不好就是直接从Java 8的JDK源码中拿过来,把包换成j$.time
而已,除非这个类里面有什么代码不兼容才需要去改一改,也就是说脱糖代码的实现和原生的实现可能几乎是相同的,所以这性能上肯定不会有什么差别的,这可能也是为什么Android官方不做版本判断去选择用原生还是用脱糖的原因。当然了,这也只是我自己的猜想。
3. 完整脱糖配置
android {defaultConfig {// 将minSdkVersion设置为20或更低时需要multiDexEnabled = true}compileOptions {// 适用于 AGP 4.1+,标志以启用对新语言api的支持isCoreLibraryDesugaringEnabled = true// 适用于 AGP 4.0// coreLibraryDesugaringEnabled = true// 设置Java兼容性为Java 11sourceCompatibility = JavaVersion.VERSION_11targetCompatibility = JavaVersion.VERSION_11}
}dependencies {// 适用于 AGP 7.4+coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")// 适用于 AGP 7.3// coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.3")// 适用于 AGP 4.0 到 7.2// coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.9")
}
这是从官方抄来的配置,比较完整,对于不同的版本,设置上稍微有点不同。
为什么minSdkVersion
设置为20或更低时需要multiDexEnabled=true
,因为启用脱糖后,AGP插件会为脱糖库单独生成一个dex
文件,再加上你自己的代码也会生成dex
文件,这样最少就会有两个dex文件了,所以要开启多dex支持(multiDexEnabled=true
)。那为什么是在版本20或更低的时候才需要进行设置开启?这是因为Android 5.0(API 21)
是一个关键分界线,在 Android 开发中,multiDexEnabled = true
的配置需求与 dex文件方法数限制 密切相关,从Android 5.0(API 21)
开始,系统默认使用 ART(Android Runtime)
替代了之前的 Dalvik
虚拟机。两者的核心差异在于:
-
Dalvik(API 20 及以下):
- 使用 JIT(Just-In-Time) 编译,运行时逐条解释字节码。
- 每个 DEX 文件的方法数限制为 65,536(64K),超出后必须拆分成多个 DEX 文件(即启用 multiDex)。
- 应用启动时需要加载所有 DEX 文件,导致启动延迟和内存压力。
-
ART(API 21 及以上):
- 使用 AOT(Ahead-Of-Time) 编译,安装时预编译为原生机器码。
- 原生支持从 APK 中直接加载多个 DEX 文件,无需开发者显式配置 multiDexEnabled。
- 方法数限制由系统动态管理,理论上无硬性上限(实际受设备存储空间限制)。
-
技术细节:DEX 文件处理的改进
- API 21+ 的隐式多 DEX 支持,即使 APK 中包含多个 DEX 文件(如主 classes.dex 和附加的 classes2.dex),ART 会自动合并它们的内存映射,无需在 build.gradle 中声明 multiDexEnabled。系统在安装时会将所有 DEX 文件编译为单个 .oat 文件(ART 的优化格式)。
- API 20 及以下的强制多 DEX 配置,Dalvik 无法自动处理多 DEX 文件,必须通过
MultiDex.install(Context)
在运行时动态加载额外的 DEX 文件。这就是为什么旧版本需要显式启用multiDexEnabled = true
并添加MultiDexApplication
或手动调用MultiDex.install()
。
版本对比总结
特性 | Dalvik (API ≤20) | ART (API ≥21) |
---|---|---|
DEX文件加载方式 | 需手动拆分并加载多DEX | 自动合并多DEX,无需配置 |
方法数限制 | 单个DEX文件≤65,536方法 | 无硬性限制(由设备存储空间决定) |
编译模式 | JIT(运行时解释) | AOT(安装时预编译) |
开发者配置 | 必须multiDexEnabled=true | 无需配置(系统隐式支持) |
4. 关于compileSdkVersion
DeepSeek有提到compileSdkVersion
尽量设置为最新版本,以便支持最新脱糖。
android 5-15 支持的最低 Java版本
根据 Android 开发的技术规范和实践,Android 系统本身并不直接运行在传统 JVM 上,而是通过 ART(Android Runtime) 或旧版的 Dalvik 虚拟机 执行 DEX 字节码。设备无需预装特定版本的 JVM,其兼容性主要依赖开发阶段的构建工具链处理。以下是各 Android 版本支持的最低 开发环境 JDK 版本 和 原生兼容的 Java 特性 的对照表:
从Android 5.0开始,系统正式采用ART(Android Runtime) 替代旧版Dalvik虚拟机。ART支持Java 7标准的字节码,因此,Android 5.0设备等效JVM版本为Java 7。
📱 Android 版本与 JDK/JVM 兼容性对照表
Android 版本 | API 级别 | 推荐开发 JDK | 原生支持的最高 Java 特性 | 关键说明 |
---|---|---|---|---|
Android 5.x (Lollipop) | 21–22 | JDK 7 | Java 7 | 需通过脱糖支持 Java 8+ 特性 |
Android 6.x (Marshmallow) | 23 | JDK 7 | Java 7 | 同上 |
Android 7.x (Nougat) | 24–25 | JDK 8 | Java 8(部分) | 原生支持 Lambda 等语法 |
Android 8.x (Oreo) | 26–27 | JDK 8 | Java 8 | 完整支持接口默认方法等 |
Android 9.x (Pie) | 28 | JDK 8 | Java 8 | 优化运行时性能 |
Android 10.x (Q) | 29 | JDK 8 | Java 8 | 无新增语言特性支持 |
Android 11.x ® | 30 | JDK 8 | Java 8 | 需 AGP 4.2+ 支持脱糖 |
Android 12.x (S) | 31–32 | JDK 11 | Java 11(需脱糖) | AGP 7.0+ 强制要求 JDK 11 |
Android 13.x (Tiramisu) | 33 | JDK 11 | Java 11(需脱糖) | 同 Android 12 |
Android 14.x (Upside Down Cake) | 34 | JDK 17 | Java 17(需脱糖) | AGP 8.2+ 推荐 JDK 17 |
Android 15.x (Vanilla Ice Cream) | 35 | JDK 17 | Java 17(需脱糖) | 需核心库脱糖支持 API |
💎 总结
- Android 5–7:开发推荐 JDK 8,设备原生支持 Java 7,Java 8+ 特性依赖脱糖。
- Android 8–11:开发推荐 JDK 8,设备原生支持 Java 8。
- **Android 12+**:开发强制要求 JDK 11+,设备通过脱糖支持 Java 11/17 特性。
- 核心原则:设备兼容性由 脱糖技术 和 minSdk 配置 共同保障,开发者无需关注设备原生支持的JAVA版本。建议始终使用最新稳定版 JDK 和 AGP 以最大化兼容性。
- **AGP 7.0+**:需 JDK 11 编译(如 Android 12+ 项目)
- AGP 8.0+:需 JDK 17 编译(如 Android 14+ 项目)
对于ART版本,可以通过如下代码获取:
String vmVersion = System.getProperty("java.vm.version"); // 如"2.1.0"(ART版本)
对于Java版本:
String specVersion = System.getProperty("java.specification.version"); // 如"1.7"
我在一台Android 11的手机上运行,它返回的是0.9,所以这个并不是代表Java的版本吧?
Android官方于关脱糖的链接
官方链接:
- Java 8语言支持:https://developer.android.google.cn/studio/write/java8-support?hl=zh-cn
- AGP 4.0插件中关于脱糖的介绍:https://developer.android.google.cn/build/releases/past-releases/agp-4-0-0-release-notes?hl=zh-cn#j8-library-desugaring
- AGP 4.0插件中关于
buildFeatures
属性的说明:https://developer.android.google.cn/build/releases/past-releases/agp-4-0-0-release-notes?hl=zh-cn#buildFeatures - Java 8语言支持表:https://developer.android.google.cn/studio/write/java8-support-table?hl=zh-cn
- Java 11语言支持表:https://developer.android.google.cn/studio/write/java11-default-support-table
- 脱糖库版本日志:https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md
- 使用Java8的时间Api时自定义功能时需要注意的:https://developer.android.google.cn/studio/write/java8-support-table?hl=zh-cn#java-time-customizations、https://developer.android.google.cn/studio/write/java11-default-support-table#java-time-customizations
下面转载部分官方原文,以预防官方链接失效。
Android Gradle 插件对使用某些 Java 8 语言功能以及利用这些功能的第三方库提供内置支持。如图 1 所示,默认工具链实现新语言功能的方法是在使用 D8/R8 将类文件编译成 DEX 代码的过程中执行字节码转换,这种转换称为 desugar。
图 1. 使用 desugar 字节码转换实现 Java 8 语言功能支持。
注意:您选择的字节码级别要在功能和构建速度之间达到平衡。字节码级别 6 的构建速度较快,功能较少;字节码级别 7 的功能和构建速度达到平衡;而字节码 8 功能更丰富,构建速度较慢。
为什么字节码级别高了构建速度会变慢,问了下DeepSeek:
根本原因:
字节码级别越高,编译器需要执行更复杂的转换和验证步骤,主要受以下因素影响:
-
语言特性复杂度
字节码版本 新增特性示例 转换复杂度 6 (Java 6) 基础特性 低 7 (Java 7) try-with-resources
、switch
字符串中 8 (Java 8) Lambda、接口默认方法 高 -
验证流程增加
- 类型系统验证:Java 8+ 需要验证更复杂的类型推断
- 桥接方法生成:Lambda 和方法引用需要生成额外桥接方法
- 语法树转换:将新语法降级为旧字节码需要多步转换
-
具体影响因素: 编译阶段耗时
Java 8 代码 → 语法分析 → 类型检查 → Lambda 转换 → 字节码生成 → 验证↑___________ 比 Java 6 多出这些步骤 __________↑
Java 8 语言功能支持(Android Gradle 插件 3.0.0 及更高版本)
如需开始使用受支持的 Java 8 语言功能,请执行以下操作:
-
将 Android Gradle 插件更新为 3.0.0 或更高版本。
-
针对在源代码中或通过依赖项使用 Java 8 语言功能的每个模块,更新模块的
build.gradle
或build.gradle.kts
文件,如下所示:android {...// 只对使用Java 8语言特性的每个模块进行配置(无论是在其源代码中还是通过依赖项)。compileOptions {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8}// 用于Kotlin项目kotlinOptions {jvmTarget = "1.8"} }
使用 Android Gradle 插件 3.0.0 及更高版本构建应用时,插件并非支持所有 Java 8 语言功能。现在,以下语言功能在任何 API 级别上均可用:
Java 8 语言功能 | 备注 |
---|---|
lambda 表达式 | Android 不支持 lambda 表达式的序列化 |
方法引用 | |
类型注解 | 类型注解信息仅在编译时可用,在运行时不可用。 在API级别24及更低级别中,平台支持 TYPE ,而不支持ElementType.TYPE_USE 或ElementType.TYPE_PARAMETER 。 |
默认和静态接口方法 | |
重复注解 |
除了上述 Java 8 语言功能之外,Android Gradle 插件版本 3.0.0 及更高版本还将对 try-with-resources
的支持扩展到所有 Android API 级别。
脱糖不支持 MethodHandle.invoke
和 MethodHandle.invokeExact
。如果您的源代码或某个模块依赖项使用其中某种方法,则您需要指定 minSdkVersion 26
或更高版本。否则,您会收到以下错误:
Dex: Error converting bytecode to dex:
Cause: signature-polymorphic method called without --min-sdk-version >= 26
在某些情况下,即使 invoke
或 invokeExact
方法包含在库依赖项中,您的模块也可能不使用这些方法。如需在指定了 minSdkVersion 25
或更低版本的情况下继续使用该库,请启用代码缩减来移除未使用的方法。如果这样做不起作用,可考虑使用一个替代库,让其不使用不受支持的方法。
可通过 Android Gradle
插件 3.0.0
及更高版本实现的 Java 8+
语言功能脱糖不会使任何额外的类和 API(如 java.util.stream.*
)可在较低的 Android 版本中使用。对部分 Java API 脱糖的支持可通过 Android Gradle
插件 4.0.0
或更高版本实现,下一部分对此进行了介绍。
Java 8 及更高版本 API 脱糖支持(Android Gradle 插件 4.0.0 及更高版本)
如果您使用 Android Gradle 插件 4.0.0 或更高版本构建应用,插件扩展了对使用多种 Java 8 语言 API 的支持,而无需为应用设置最低 API 级别。借助 Android Gradle 插件 7.4.0 或更高版本,脱糖库 2.0.0 或更高版本中也提供了一些 Java 11 语言 API。
之所以能够实现对较低平台版本的这种额外支持,是因为脱糖引擎经过插件 4.0.0 及更高版本扩展后,也能使 Java 语言 API 脱糖。您可以在支持旧版 Android 的应用中添加过去仅在最新 Android 版本中可用的标准语言 API(如 java.util.streams
)。
使用 Android Gradle 插件 4.0.0 或更高版本构建应用时,支持下面一组 API:
- 顺序流 (
java.util.stream
) java.time
的子集java.util.function
java.util.{Map,Collection,Comparator}
的最近新增内容- 可选内容(
java.util.Optional、java.util.OptionalInt 和 java.util.OptionalDouble
)以及一些新类 java.util.concurrent.atomic
的一些新增内容(AtomicInteger
、AtomicLong
和 -AtomicReference
的新方法)ConcurrentHashMap
(包含 Android 5.0 的 bug 修复)
Android Gradle 插件 7.4.0 或更高版本还支持其他 Java 11 API,例如 java.nio.file
软件包的子集。
如需查看受支持的 API 的完整列表,请参阅通过脱糖获得 Java 8 及更高版本 API 和通过脱糖提供的 Java 11 及更高版本 API。
为了支持这些语言 API,该插件会编译一个单独的 DEX 文件(其中包含缺失 API 的实现),并将其添加到您的应用中。脱糖过程会重新编写应用的代码,以便在运行时改用此库。编译为单独 DEX 文件的源代码可在 desugar_jdk_libs GitHub 代码库中找到。
如需在任意版本的 Android 平台上启用对这些语言 API 的支持,请执行以下操作:
-
将 Android Gradle 插件更新为 4.0.0 或更高版本。
-
在应用模块的 build.gradle 或 build.gradle.kts 文件中添加以下代码:
android {defaultConfig {// Required when setting minSdkVersion to 20 or lowermultiDexEnabled = true}compileOptions {// Flag to enable support for the new language APIs// For AGP 4.1+isCoreLibraryDesugaringEnabled = true// For AGP 4.0// coreLibraryDesugaringEnabled = true// Sets Java compatibility to Java 8sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8} }dependencies {// For AGP 7.4+coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")// For AGP 7.3// coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.3")// For AGP 4.0 to 7.2// coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.9") }
请注意,在以下情况下,您可能还需要在相应库模块的 build.gradle
或 build.gradle.kts
文件中添加前述代码段:
-
库模块的插桩测试会使用这些语言 API(直接使用,或者通过库模块或其依赖项使用)。这是为了向您的插桩测试 APK 提供缺失的 API。
-
您想单独在该库模块上运行 lint。这是为了帮助 lint 识别这些语言 API 的有效使用情况,并避免报告虚假警告。
另请注意,API 脱糖可以与缩减功能结合使用,但仅限在使用 R8 缩减器时。
下表显示了 Java 8+ API 库的版本以及支持每个版本的最低 Android Gradle 插件版本:
API 脱糖库版本 | 最低 Android Gradle 插件版本 |
---|---|
1.1.9 | 4.0.0 |
1.2.3 | 7.3.0 |
2.0.3 | 7.4.0-alpha10 |
比如想使用2.0.3
版本的高版本脱糖库,以实现更多我的API脱糖,则需要使用的AGP版本最低为7.4.0-alpha10
。