1. 引言
😶 最近组里准备用Flutter重构的模块中包含了 "视频播放 ",寻思着用 官方 的 video_player 2.7.2 插件 (🐶鸿蒙也有适配),添加完依赖,Gradle构建直接GG,不过也正常,9年多的项目,构建工具都比较老。
😐 之前用 mmkv 的时候就报过错了,但是当时比较急,只能把库拉下来,删掉android部分,然后封装一层,判断平台,iOS走 mmkv 存储 键值对 ,Android 走 MethodChannel,原生端实现sp读写相关的方法。
🤷♀️ 但这次好像逃不过了,自己整起来肯定很么烦,长痛不如短痛,唉,升级一波吧😑,折腾了 快四天,项目终于能跑起来,基本功能验证没问题。本文记录下踩坑过程,希望对同样有升级需求的童鞋有帮助,下面是旧项目构建相关的版本信息:
bash
com.android.tools.build:gradle:4.2.2
org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21
buildToolsVersion = "30.0.2"
compileSdkVersion = 31
minSdkVersion = 22
targetSdkVersion = 28
gradle-7.5
java 11
2. minCompileSdk(34) specified xxx

用到的 androidx.media3:media3-extractor:1.4.0 库要求 最低CompileSdk 为 34,旧项目为31,两种解法:
- ① 将 compileSdk 提升到 34 ,需要更新 AGP 、Gradle 等的版本。
- ② 将 Media3 固定到较低版本,强行降级可能存在API不匹配,导致编译问题或运行时错误
🤔 从 长期 考虑 (新版本修复与特性,其它库也需要升级),选了前者~
3. AAPT2 process xxx Unknow chunk type '200'

搜了下错误,《安卓14适配编译问题和坑总结》提到需要升级 Gradle 版本,最低7.4.2了,而我原本已经7.5了,问了下 Cursor,建议我走 GP 8.1+ + Gradle 8.0+ + JDK 17 的组合,更新下相关版本:
bash
com.android.tools.build:gradle:8.1.4
org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22
targetSdkVersion = 34
gradle-8.2.1
java 17
4. Gradle JVM version incompatible

修改 Gradle JDK 路径,选个17的:

5. com.huawei.agconnect 版本过低 (1.6.0.300)

原因:AGP 8 上调用了已移除的 Transform API (android.registerTransform)。
解法 :升级到支持 AGP 8 的新版本,这里改为 1.9.1.300。
6. Multiple build operations failed
this and base files have different roots:

原因 :Windows盘符不一致导致的路径相对化报错,Flutter把插件复制到E盘路径,但同时又从C盘读取源码,AGP 在生成 generateDebugUnitTestConfig 时对路径做相对化,因根不同直接抛错。
解法 :要么迁移项目路径,要么修改pub的默认缓存目录 ,这里选后者,设置下 PUB_CACHE 环境变量,位置随意,只要跟 Flutter 项目同一个磁盘就行:

设置完依次执行:flutter clean → flutter pub get,看到设置的目录有缓存文件生成就可以了`

7. Build Type 'debug' contains custom BuildConfig fields
原因 :在 debug 构建类型里用了 buildConfigField ,但 buildConfig 生成功能被关闭,导致不能生成 BuildConfig 类而直接报错。从 AGP 8 起 (尤其是库模块 ),buildConfig 默认可能是关闭的,一旦你声明了 buildConfigField ,就必须显式开启 android.buildFeatures.buildConfig true。
解法 :在对应模块的 android { buildFeatures { buildConfig true } } 打开它:

8. kaptGenerateStubsDebugKotlin jvm target compatibility

原因 :KAPT/JVM 版本不一致,在 AGP 8 + Gradle 8 + JDK 17 环境下,Kotlin 的 KAPT 任务默认跑在 JDK 17 上,导致 kaptGenerateStubsDebugKotlin 的 jvmTarget=17,而 Java 编译任务仍是 1.8,于是出现 "两个任务的 JVM 目标不一致" 的报错。
解法 :Java 和 Kotlin 的 JVM 目标统一到同一个版本,官方更推荐用 JVM Toolchain。

也可以这样写:
bash
kotlin {
jvmToolchain(17)
}
🤔 一个个module改太麻烦了,直接项目根目录的 build.gradle 中写代码统一项目中所有 Android/Kotlin 子模块的 Java 和 Kotlin 编译目标版本:
bash
// 统一所有 Android/Kotlin 子模块的 Java/Kotlin 目标版本,避免 kapt 与 javac 不一致
subprojects { sub ->
// 仅对白名单中的本仓库模块生效,避免干扰 Flutter/第三方插件模块(如 :flutter、:shared_preferences_android 等)
def targetModules = [
'app', 'module_xxx', 'module_yyy', 'module_zzz', 'xxx_yyy',
]
if (!targetModules.contains(sub.name)) {
return
}
// 统一 Kotlin Toolchain 与 jvmTarget 为 17
sub.plugins.withId('org.jetbrains.kotlin.android') {
sub.extensions.findByName('kotlin')?.jvmToolchain(17)
sub.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { task ->
task.kotlinOptions { jvmTarget = "17" }
}
}
// 兼容旧插件ID:kotlin-android
sub.plugins.withId('kotlin-android') {
sub.extensions.findByName('kotlin')?.jvmToolchain(17)
sub.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { task ->
task.kotlinOptions { jvmTarget = "17" }
}
}
// 统一 Java 源/目标兼容性为 17
sub.plugins.withId('com.android.application') {
sub.android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
}
sub.plugins.withId('com.android.library') {
sub.android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
}
// 兜底:即使模块没暴露 android{} 或被其他脚本覆盖,强制所有 JavaCompile 使用 17
sub.tasks.withType(JavaCompile).configureEach { jc ->
jc.sourceCompatibility = JavaVersion.VERSION_17
jc.targetCompatibility = JavaVersion.VERSION_17
// 不再强制设置 --release,避免 AGP 在 Java9+ 下的告警与潜在兼容性问题
}
}
9. Manifest 合并失败
原因:Android 12 开始,凡是 activity/service/receiver 里声明了 intent-filter 的组件,必须显式标注 android:exported。
解法 :报错模块的 AndroidManifest.xml 的四大组件添加 android:exported="true"或"false"。

10. SupportLibraryBlurImpl 爆红
原因 :AGP 7+ 开始弃用 RenderScript 、AGP 8 后不再支持 RenderScript Support Mode,androidx.renderscript 工件也已下线,导致 import androidx.renderscript.* 直接编译报"找不到类"。
解法 :SupportLibraryBlurImpl.java 的 androidx.renderscript. * 改为 android.renderscript.* ,与系统自带 RS API 对齐。移除 renderscriptTargetApi 与 renderscriptSupportModeEnabled。
11. Unresolved reference: synthetic
原因 :项目中用了大量旧的 Kotlin Android Extensions (kotlinx.android.synthetic.*) 来直接访问布局控件。该特性已在 Kotlin 1.4.20 起废弃,并在 Kotlin 1.8 起彻底移除。上面更新了Kotlin版本,所以出现这个错误。
解法 :用 ViewBinding/DataBinding 替代 synthetic ,或者手写 findViewById() 。这里直接后者,ViewBinding 弄起来太麻烦了,每次改完还得重新构建,看布局生成对应的 Binding 导入有没有爆红,远没findViewById() 验证得快。

🤮 要改动的地方太多了 (上百个文件...),让AI代劳,先写个py脚本,把要修改的文件都筛出来(做下去重):

生成的脚本:
python
import re
def extract_file_paths(text):
"""
提取文本中的文件路径
"""
# 匹配 file:///盘符:/路径 格式的文件路径
pattern = r'file:///([A-Za-z]:/[^:\s]+.(kt|java|xml|py|js|ts|cpp|h|c))'
matches = re.findall(pattern, text)
# 只返回文件路径部分
file_paths = [match[0] for match in matches]
return file_paths
def save_paths_to_file(file_paths, output_file):
"""
将文件路径保存到txt文件
"""
# 去重并排序
unique_paths = sorted(set(file_paths))
with open(output_file, 'w', encoding='utf-8') as f:
for path in unique_paths:
f.write(path + '\n')
print(f"已提取 {len(unique_paths)} 个唯一文件路径,保存到 {output_file}")
def main():
print("请粘贴包含文件路径的文本(两个连续回车结束):")
lines = []
empty_line_count = 0
while True:
line = input()
if line.strip() == '':
empty_line_count += 1
if empty_line_count >= 2:
break
else:
empty_line_count = 0
lines.append(line)
if not lines:
print("未输入任何文本")
return
text = '\n'.join(lines)
# 提取文件路径
file_paths = extract_file_paths(text)
if not file_paths:
print("未找到任何文件路径")
return
unique_paths = sorted(set(file_paths))
for path in unique_paths:
print(path)
if __name__ == "__main__":
main()
运行下,可以看到成功把路径提取出来了 (还做了去重):

接着写Prompt,让 Cursor 来改:
markdown
## 背景
我之前使用了kotlinx.android.synthetic.*来直接访问布局控件,升级了kotlin版本后,构建报错Unresolved reference: synthetic。
## 指令
1.使用 **findViewById** 来替换,我会给出一个待修改的文件列表。
2.需要你删掉文件的import kotlinx.android.synthetic,改为findViewById()的方式访问组件。
3.需要同步组件调用处变量名的修改,组件的类型需要跟对应xml里的组件id对得上,并添加improt导包 (注意避免重复导入)。
## 约束
1.不要进行其它额外的操作,如生成文档,python脚本。
2.老老实实给我一个个文件修改
这些是需要修改的文件列表:[]
😳 等等,为啥我要编译失败,copy?编译失败,再copy,我直接遍历包含 import kotlinx.android.synthetic 的文件不就好了... 直接让 Cursor 代劳:

🤡 经过AI漫长的改动 (总共改了90+个kt文件),总算解决,继续`
12. BuildConfig.DEBUG not found
AGP 8.0 及以后的版本中,默认禁用了 buildConfig ,在以前的版本中,这个功能默认是启用的,允许你在代码里通过 BuildConfig 类访问一些自动生成的构建配置常量。在 AGP 8.0+ 版本中想继续使用,需要在模块级别的 build.gradle 中明确启用:
groovy
android {
// 加上这个
buildFeatures {
buildConfig = true
}
}
13. Constant expression required

使用较新版本的Android Gradle插件或者在库模块中,R.id.xxx 资源ID 不再是 编译时常量 ,无法在 switch 语句的case中使用,改为 if-else 即可解决。
14. okhttp3 Expected Android API level 21+ but was 32
😳 兼容性冲突,项目用到的 OkHttp 3.6.0 版本过旧 (2017年发布),不支持 targetSdkVersion 32,更新下相关依赖:
groovy
// 旧版本 (有问题)
api 'com.squareup.retrofit2:retrofit:2.0.0'
api 'com.squareup.retrofit2:converter-gson:2.0.0'
api 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
api 'com.squareup.okhttp3:logging-interceptor:3.6.0'
// 新版本 (已修复)
api 'com.squareup.retrofit2:retrofit:2.9.0'
api 'com.squareup.retrofit2:converter-gson:2.9.0'
api 'com.squareup.retrofit2:adapter-rxjava:2.9.0'
api 'com.squareup.okhttp3:logging-interceptor:4.12.0'
api 'com.squareup.okhttp3:okhttp:4.12.0'
OkHttp 4.x API 变更导致报错:
- 属性化:方法调用改为属性访问 (url() → url)
- 扩展函数:静态方法改为扩展函数 (parse() → toMediaType())
- SSL 参数:sslSocketFactory 需要额外的 X509TrustManager 参数
部分修改代码:
kotlin
// 属性化
val originUrl = request.url
val originUrlStr = originUrl.toUrl().toString()
val hostUrl = originUrl.host
return chain.proceed(request.newBuilder().url(hookUrlStr.toHttpUrl()).build())
// SSL
// 创建信任管理器
private fun createTrustManager() = object : X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) { }
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) { }
override fun getAcceptedIssuers(): Array<X509Certificate?>? = arrayOfNulls(0)
}
// 设置信任全部证书
private fun createSSLSocketFactory() = try {
val mMyTrustManager = createTrustManager()
val sc: SSLContext = SSLContext.getInstance("TLS")
sc.init(null, arrayOf(mMyTrustManager), SecureRandom())
sc.socketFactory
} catch (ignored: Exception) {
ignored.printStackTrace()
null
}
// MediaType
val requestFile = RequestBody.create("multipart/form-data".toMediaType(), file)
val requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), compressVideoFile!!)
15. SecurityException: getDeviceId: not meet xxx
从Android 10 (API 29)开始,Google严格限制了对设备唯一标识符的访问,普通应用无法再获取设备的IMEI/MEID等敏感信息,TelephonyManager.getDeviceId()在Android 10+上即使有权限也无法正常调用。
kotlin
public String getMyUUID() {
Context mContext = HttpConfig.getInstance().getAppContext();
final String tmDevice, tmSerial, androidId;
// 获取Android ID(所有版本都可用)
androidId = android.provider.Settings.Secure.getString(
HttpConfig.getInstance().getAppContext().getContentResolver(),
android.provider.Settings.Secure.ANDROID_ID
);
// 尝试获取设备ID和SIM序列号,如果失败则使用备用方案
String deviceId = "";
String simSerial = "";
try {
// 检查权限
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE) ==
PackageManager.PERMISSION_GRANTED) {
final TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
// Android 10+需要特殊处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+ 无法获取设备ID,使用备用方案
deviceId = getAlternativeDeviceId(mContext);
try {
simSerial = tm.getSimSerialNumber();
} catch (SecurityException e) {
simSerial = "unavailable_q";
Log.w("AndroidUtils", "无法获取SIM序列号: " + e.getMessage());
}
} else {
// Android 9及以下版本
try {
deviceId = tm.getDeviceId();
simSerial = tm.getSimSerialNumber();
} catch (SecurityException e) {
Log.w("AndroidUtils", "获取设备ID失败: " + e.getMessage());
deviceId = getAlternativeDeviceId(mContext);
simSerial = "unavailable";
}
}
} else {
// 没有权限时使用备用方案
deviceId = getAlternativeDeviceId(mContext);
simSerial = "no_permission";
}
} catch (Exception e) {
Log.e("AndroidUtils", "获取设备信息异常: " + e.getMessage());
deviceId = getAlternativeDeviceId(mContext);
simSerial = "exception";
}
tmDevice = deviceId;
tmSerial = simSerial;
// 生成UUID
UUID deviceUuid = new UUID(
androidId.hashCode(),
((long) tmDevice.hashCode() << 32) | tmSerial.hashCode()
);
String uniqueId = deviceUuid.toString();
Log.d("debug", "uuid=" + uniqueId);
return uniqueId;
}
/**
* 获取备用设备标识符
* 当无法获取设备ID时使用的备用方案:使用Android ID + 制造商 + 型号 + 其他信息
*/
private String getAlternativeDeviceId(Context context) {
try {
// 组合多个可获取的标识符
String androidId = android.provider.Settings.Secure.getString(
context.getContentResolver(),
android.provider.Settings.Secure.ANDROID_ID
);
String manufacturer = Build.MANUFACTURER;
String model = Build.MODEL;
String serial = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
Build.getRadioVersion() : "unknown";
return androidId + "_" + manufacturer + "_" + model + "_" + serial;
} catch (Exception e) {
Log.e("AndroidUtils", "获取备用设备ID失败: " + e.getMessage());
return "fallback_" + System.currentTimeMillis();
}
}
16. Targeting S+ (version 31 and above) requires xxx
完整错误:
argeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
错误原因:
Android 12 开始,创建 PendingIntent 时必须明确指定 FLAG_IMMUTABLE (更安全) 或 FLAG_MUTABLE 标志位,以提高安全性。
解决方法:
用到 PendingIntent 的地方添加下 FLAG_IMMUTABLE 标识。
17. insertFileIfNecessary failed
完整错误:
Primary directory com.xxx.xxx.debug not allowed for content://media/external_primary/file; allowed directories are [Download, Documents]
错误原因
从 Android 10 (API 29) 开始,Google 引入了 Scoped Storage(分区存储) 机制,限制了应用对外部存储的访问范围。应用不能再随意在外部存储的根目录或任意位置创建文件夹。我的代码中使用了 MediaStore.Images.Media.insertImage() ,试图将图片保存到系统相册。
解决方法:
Android 10+ 使用新的 ContentValues + MediaStore.Images.Media.EXTERNAL_CONTENT_URI 方式
kotlin
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+ 使用 MediaStore API
val values = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
val uri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
uri?.let {
context.contentResolver.openOutputStream(it)?.use { outputStream ->
bmp.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
success = true
}
}
18. Toast不显示
用的 com.blankj:utilcodex 库来弹 Toast ,升级完发现调原来的 ToastUtils.showShort() 不显示了,搜了下issues,找到这个《Toastutils - Android target 31 不允许通过getView 自定义 Toast》,把版本从原来的 1.29.0 改为 1.30.0 解决。然后一些 静态 设置Toast样式的方法,如:setMsgColor()、setBgColor() 等都被干掉了,需要通过 链式调用 的方式来设置属性:
kotlin
ToastUtils.make()
.setTextColor(ContextCompat.getColor(context, R.color.white))
.setBgColor(ContextCompat.getColor(context, R.color.transparent_30))
.show("你的消息内容");
// 可以包一层,定义一个工具方法便于调用
public static void showStyledToast(String message) {
ToastUtils.make()
.setTextColor(ContextCompat.getColor(context, R.color.white))
.setBgColor(ContextCompat.getColor(context, R.color.transparent_30))
.show(message);
}
19. flutter页面白屏
🤡 这个bug搞了我快两天,莫名其妙就 FlutterFragment-白屏、FlutterActivity-黑屏,啥报错没有,就一句:
bash
Tried to send viewport metrics from Android to Flutter
but this FlutterView was not attached to a FlutterEngine.
// 翻译下就是:
试图在 FlutterView 还没有正确附加到 FlutterEngine 时发送视口度量信息
// 报错的可能原因
1. FlutterView 和 FlutterEngine 的生命周期没有正确同步
2. 可能过早地调用了需要 FlutterEngine 的操作
3. FlutterEngine 可能已经被销毁,但 FlutterView 还在尝试使用它
问题是我在 Application 类中已经做了 Flutter引擎预热 ,添加发现有走,但是 engine 是 null ...

😐 感觉是 flutter_boost 的问题,issues 搜了一圈没找到解决方案,让 Cursor 给我在 FlutterFragment 和 FlutterActivity 相关回调打日志,帮助我排查,甚至做了引擎判断,为null自己创建一个,白屏依旧 ... 不知道尝试了多少次 gradle clean 、invalidate Caches.. ,一样白屏,然后代码推到打包机上打包又正常,🙃 人麻了,就差没重装电脑了... 后面又再仔细看了下issues,在《[Bug]: flutterboost4.4.0版本:原生页面打开Flutter页面时空白页》看到了这个:

😳 em... 跟我之前搞 "主项目打Debug,flutter子项目打Release,Flutter引擎二进制文件不匹配导致的白屏" 有关吗?flutter子项目没变化,so文件没重新生成?执行下述命令重新构建子项目:
bash
flutter clean
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
运行无误后,运行主项目,再打开flutter页面,好了 !!!卧槽,有毒...
