摘要:最近组里准备用 Flutter 重构的模块中包含了 "视频播放",寻思着用官方的 video_player 2.7.2 插件 (鸿蒙也有适配),添加完依赖,gradle 构建直接 GG,不过也正常,9年多的项目,构建工具都比较老。
最近组里准备用 Flutter 重构的模块中包含了 " 视频播放 ",寻思着用 官方 的 video_player 2.7.2 插件 (鸿蒙也有适配),添加完依赖,gradle 构建直接 GG,不过也正常,9年多的项目,构建工具都比较老。
之前用 mmkv 的时候就报过错了,但是当时比较急,只能把库拉下来,删掉 android 部分,然后封装一层,判断平台,iOS 走 mmkv 存储 键值对 ,Android 走 MethodChannel ,原生端实现 sp 读写相关的方法。
♀️ 但这次好像逃不过了,自己整起来肯定很么烦,长痛不如短痛,唉,升级一波吧,折腾了 快四天 ,项目终于能跑起来,基本功能验证没问题。本文记录下踩坑过程,希望对同样有升级需求的童鞋有帮助,下面是旧项目构建相关的版本信息:
com.android.tools.Build:gradle:4.2.2org.jetbrains.kotlin:Kotlin-gradle-plug #技术分享in:1.6.21buildToolsVersion = "30.0.2"compileSdkVersion = 31minSdkVersion = 22targetSdkVersion = 28gradle-7.5java 11用到的 androidx.media3:media3-extractor:1.4.0 库要求 最低 CompileSdk 为 34,旧项目为31,两种解法:
① 将 compileSdk 提升到 34 ,需要更新 AGP 、 Gradle 等的版本。② 将 Media3 固定到较低版本 ,强行降级可能存在API不匹配,导致编译问题或运行时错误从 长期 考虑 (新版本修复与特性,其它库也需要升级),选了前者~
搜了下错误,《安卓14适配编译问题和坑总结》 提到需要升级 Gradle 版本,最低7.4.2了,而我原本已经7.5了,问了下 Cursor,建议我走 GP 8.1+ + Gradle 8.0+ + JDK 17 的组合,更新下相关版本:
com.android.tools.build:gradle:8.1.4org.jetbrAIns.kotlin:kotlin-gradle-plugin:1.8.22targetSdkVersion = 34gradle-8.2.1java 17修改 Gradle JDK 路径,选个17的:
原因:AGP 8 上调用了已移除的 Transform API (android.registerTransform)。
解法 :升级到支持 AGP 8 的新版本,这里改为 1.9.1.300 。
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 } } 打开它:
原因 :KAPT/jvm 版本不一致,在 AGP 8 + Gradle 8 + JDK 17 环境下,Kotlin 的 KAPT 任务默认跑在 JDK 17 上,导致 kaptGenerateStubsDebugKotlin 的 jvmTarget=17,而 Java 编译任务仍是 1.8,于是出现 "两个任务的 JVM 目标不一致 " 的报错。
解法 :Java 和 Kotlin 的 JVM 目标统一到同一个版本,官方更推荐用 JVM Toolchain 。
也可以这样写:
kotlin {jvmToolchain(17)}一个个 module 改太麻烦了,直接项目根目录的 build.gradle 中写代码统一项目中所有 Android/Kotlin 子模块的 Java 和 Kotlin 编译目标版本:
// 统一所有 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+ 下的告警与潜在兼容性问题 } }原因 :Android 12 开始,凡是 activity/service/receiver 里声明了 intent-filter 的组件,必须显式标注 android:exported。
解法 :报错模块的 AndroidManifest.xml 的四大组件添加 android:exported="true"或"false"。
原因 :AGP 7+ 开始弃用 RenderScript 、AGP 8 后不再支持 RenderScript Support Mode,androidx.renderscript 工件也已下线,导致 import androidx.renderscript. * 直接编译报“找不到类”。
解法 :SupportLibraryBlurImpl.java 的 androidx.renderscript. * 改为 android.renderscript. * ,与系统自带 RS API 对齐。移除 renderscriptTargetApi 与 renderscriptSupportModeEnabled。
原因 :项目中用了大量旧的 Kotlin Android Extensions (kotlinx.android.synthetic.*) 来直接访问布局控件。该特性已在 Kotlin 1.4.20 起废弃,并在 Kotlin 1.8 起彻底移除。上面更新了 Kotlin 版本,所以出现这个错误。
解法 :用 ViewBinding/DataBinding 替代 synthetic ,或者手写 findViewById 。这里直接后者,ViewBinding 弄起来太麻烦了,每次改完还得重新构建,看布局生成对应的 Binding 导入有没有爆红,远没 findViewById 验证得快。
要改动的地方太多了 (上百个文件...),让 AI 代劳,先写个 py 脚本,把要修改的文件都筛出来(做下去重):
生成的脚本:
import redef extract_file_paths(text): """ 提取文本中的文件路径 """ 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_pathsdef 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 = 0while 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("未输入任何文本") returntext = '\n'.join(lines)file_paths = extract_file_paths(text)if not file_paths: print("未找到任何文件路径") returnunique_paths = sorted(set(file_paths)) for path in unique_paths: print(path)if __name__ == "__main__": main运行下,可以看到成功把路径提取出来了 (还做了去重):
接着写 Prompt,让 Cursor 来改:
## 背景我之前使用了 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 文件),总算解决,继续`
AGP 8.0 及以后的版本中,默认禁用了 buildConfig ,在以前的版本中,这个功能默认是启用的,允许你在代码里通过 BuildConfig 类访问一些自动生成的构建配置常量。在 AGP 8.0+ 版本中想继续使用,需要在模块级别的 build.gradle 中明确启用:
android {// 加上这个buildFeatures {buildConfig = true}}使用较新版本的 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,更新下相关依赖:
// 旧版本 (有问题)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 参数部分修改代码:
val originUrl = request.urlval originUrlStr = originUrl.toUrl.toStringval hostUrl = originUrl.hostreturn chain.proceed(request.newBuilder.url(hookUrlStr.toHttpUrl).build)private fun createTrustManager = object : X509TrustManager { override fun checkClientTrusted(chain: Array?, authType: String?) { }override fun checkServerTrusted(chain: Array?, authType: String?) { }override fun getAcceptedIssuers: Array? = 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 }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+上即使有权限也无法正常调用。
public String getMyUUID {Context mContext = HttpConfig.getInstance.getAppContext;final String tmDevice, tmSerial, androidId;androidId = android.provider.Settings.Secure.getString(HttpConfig.getInstance.getAppContext.getContentResolver,android.provider.Settings.Secure.ANDROID_ID);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);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {deviceId = getAlternativeDeviceId(mContext);try {simSerial = tm.getSimSerialNumber;} catch (SecurityException e) {simSerial = "unavailable_q";Log.w("AndroidUtils", "无法获取SIM序列号: " + e.getMessage);}} else {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 deviceUuid = new UUID(androidId.hashCode,((long) tmDevice.hashCode = 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 标识。
完整错误:
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 方式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {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 } }用的 com.blankj:utilcodex 库来弹 Toast ,升级完发现调原来的 ToastUtils.showShort 不显示了,搜了下 issues,找到这个 《Toastutils - Android target 31 不允许通过 getView 自定义 Toast》 ,把版本从原来的 1.29.0 改为 1.30.0 解决。然后一些 静态 设置 Toast 样式的方法,如:setMsgColor、setBgColor 等都被干掉了,需要通过 链式调用 的方式来设置属性:
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); }这个 bug 搞了我快两天,莫名其妙就 FlutterFragment-白屏、FlutterActivity-黑屏,啥报错没有,就一句:
Tried to send viewport metrics from Android to Flutterbut 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 文件没重新生成?执行下述命令重新构建子项目:
flutter cleanflutter pub getflutter pub run build_runner build --delete-conflicting-outputs运行无误后,运行主项目,再打开 flutter 页面,好了 !!!卧槽,有毒...
Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
用了好几个 AI (Claude 4、GPT 5、Gemini 2.5 Pro、Grok、DeepSeek) 改了一天都没解决这个问题,全是在重复折腾这四个:
♀️ 没解决问题之余,还在那里输出无用的所谓 "情绪价值", "噢,我发现问题了,xxx,已经完美修复了",还 TM 原地拉屎写起了修复文档,我真的会谢... 最后花不到半小时 " 断点调试 " 就定位到问题了,是依赖的一个 弹窗库 通过 "反射" 的方式访问一个被限制的内部 API 导致的 (Android 9,API 28开始对非 SDK 接口的使用进行了限制),找了下库的 issues,发现升级下库版本就好了...
来源:墨码行者一点号
