diff --git a/app/build.gradle b/app/build.gradle index 06e0871..90c422e 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -264,7 +264,8 @@ dependencies { // implementation 'com.sensorsdata.analytics.android:SensorsAnalyticsSDK:6.8.3' implementation 'commons-codec:commons-codec:1.15' - implementation 'com.squareup.okhttp3:okhttp:3.12.13' + // 3.12.0:3.12.1+(含 3.12.13)引入 Android10Platform 触发 Dalvik VerifyError,必须精确锁 3.12.0 + implementation 'com.squareup.okhttp3:okhttp:3.12.0' implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.github.gzu-liyujiang:Android_CN_OAID:4.1.3' implementation('io.github.volcengine-tls:tls-android-producer:2.0.2-api16') { @@ -278,22 +279,28 @@ dependencies { implementation 'com.google.guava:guava:31.1-android' // ===== 网络层(脚手架新引入,与既有 HttpUtils 双栈共存) ===== - // 为什么显式指定版本:app 模块此前未声明 retrofit/okhttp/moshi/coroutines,避免被根 config.gradle 隐式拉入不同版本。 - // Kotlin 1.9.22 -> KSP 1.9.22-1.0.17 已对齐。 - // 为什么用 `platform(enforcedPlatform)` 而非 `implementation`:okhttp-bom 是 Bill-of-Materials,本身仅提供版本对齐, - // 不能被当作运行时产物拉到 runtime classpath(这正是上一个编译报错的根因)。 - implementation platform('com.squareup.okhttp3:okhttp-bom:4.12.0') - implementation 'com.squareup.retrofit2:retrofit:2.11.0' - implementation 'com.squareup.retrofit2:converter-moshi:2.11.0' - implementation 'com.squareup.okhttp3:okhttp' - implementation 'com.squareup.okhttp3:logging-interceptor' + // ===== Android 4.2 兼容(对齐 mediaapp 已验证组合,见 Android4.2兼容性改动文档)===== + // - OkHttp 4.x 官方要求 API 21+;3.13+ 含 Android10Platform(API 10 SSL API)触发 Dalvik VerifyError。 + // 3.12.0 是支持 API 9-20 的长期分支(与 libcore/libdata 老栈版本一致),配合下方 force 全局锁定。 + // - Retrofit 2.6.0:suspend 函数 2.6.0 起支持;2.9+ 传递依赖 OkHttp 3.14.9(含 Android10Platform)。 + // - Coroutines 1.6.4:1.7+ 要求 minSdk 21,1.6.4 兼容 API 16+。 + implementation 'com.squareup.retrofit2:retrofit:2.6.0' + implementation 'com.squareup.retrofit2:converter-moshi:2.6.0' + implementation 'com.squareup.okhttp3:okhttp:3.12.0' + implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0' implementation 'com.squareup.moshi:moshi:1.15.1' implementation 'com.squareup.moshi:moshi-kotlin:1.15.1' ksp 'com.squareup.moshi:moshi-kotlin-codegen:1.15.1' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2' + // 防止其他依赖(tls-producer/libdata 等)传递引入更高版本 OkHttp/Retrofit 破坏 4.2 兼容 + configurations.all { + resolutionStrategy.force 'com.squareup.okhttp3:okhttp:3.12.0' + resolutionStrategy.force 'com.squareup.retrofit2:retrofit:2.6.0' + } + implementation 'net.jpountz.lz4:lz4:1.3.0' implementation 'org.slf4j:slf4j-android:1.7.36' //腾讯buggly diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4adbd41..5b584c8 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,10 @@ package="com.pukka.jiecheng" tools:ignore="MissingLeanbackLauncher"> + + + diff --git a/app/src/main/java/com/pukka/jiecheng/network/auth/HeaderInterceptor.kt b/app/src/main/java/com/pukka/jiecheng/network/auth/HeaderInterceptor.kt index 5b3ad32..a20ed2a 100755 --- a/app/src/main/java/com/pukka/jiecheng/network/auth/HeaderInterceptor.kt +++ b/app/src/main/java/com/pukka/jiecheng/network/auth/HeaderInterceptor.kt @@ -26,7 +26,8 @@ class HeaderInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val original = chain.request() - val path = SignRequestPolicy.relativePath(original.url.toString()) + // OkHttp 3.12 兼容:Java 版字段访问需用方法调用(url()),4.x 的 Kotlin val 语法不可用 + val path = SignRequestPolicy.relativePath(original.url().toString()) val builder = original.newBuilder() diff --git a/app/src/main/java/com/pukka/jiecheng/network/auth/SignAuthenticator.kt b/app/src/main/java/com/pukka/jiecheng/network/auth/SignAuthenticator.kt index 69d20b1..9465172 100755 --- a/app/src/main/java/com/pukka/jiecheng/network/auth/SignAuthenticator.kt +++ b/app/src/main/java/com/pukka/jiecheng/network/auth/SignAuthenticator.kt @@ -55,7 +55,8 @@ class SignAuthenticator : Authenticator { private var credentialBroken: Boolean = false override fun authenticate(route: Route?, response: Response): Request? { - val path = SignRequestPolicy.relativePath(response.request.url.toString()) + // OkHttp 3.12 兼容:Java 版 request()/priorResponse() 方法调用(4.x Kotlin val 语法不可用) + val path = SignRequestPolicy.relativePath(response.request().url().toString()) // 保护 1:首签/刷新接口 401 直接放行 if (!SignRequestPolicy.shouldTriggerRefresh(path)) { @@ -63,12 +64,12 @@ class SignAuthenticator : Authenticator { } // 保护 2:刷新调用本身带 X-Refresh-Call=1 标记,Authenticator 见到放行 - if (response.request.header(HEADER_REFRESH_CALL) == "1") { + if (response.request().header(HEADER_REFRESH_CALL) == "1") { return null } // 保护 3:priorResponse 链上已有 X-Refresh-Marker,说明已刷新过一次仍 401,放弃 - val prior = response.priorResponse + val prior = response.priorResponse() if (prior != null && prior.header(HEADER_REFRESH_MARKER) != null) { return null } @@ -104,7 +105,7 @@ class SignAuthenticator : Authenticator { Network.tokenStore.refreshAndPersist(newSign) // 重建原 Request:保留所有原有 headers,注入新 sign 头 + X-Refresh-Marker 标记 - response.request.newBuilder() + response.request().newBuilder() .header("sign", newSign) .header(HEADER_REFRESH_MARKER, "1") .build() diff --git a/app/src/main/java/com/pukka/jiecheng/network/auth/SignRequestPolicy.kt b/app/src/main/java/com/pukka/jiecheng/network/auth/SignRequestPolicy.kt index 290b46d..750466e 100755 --- a/app/src/main/java/com/pukka/jiecheng/network/auth/SignRequestPolicy.kt +++ b/app/src/main/java/com/pukka/jiecheng/network/auth/SignRequestPolicy.kt @@ -1,6 +1,6 @@ package com.pukka.jiecheng.network.auth -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.HttpUrl /** * sign 头注入 / 401 刷新触发策略。 @@ -36,11 +36,14 @@ object SignRequestPolicy { /** * 提取相对 baseUrl 的 path(去掉 query/fragment),用于上述匹配。 + * + * 注意:HttpUrl.parse 是 OkHttp 3.12 的 API(4.x 才有 toHttpUrlOrNull 扩展), + * 项目已为 Android 4.2 兼容全局锁定 OkHttp 3.12.0,勿改回扩展函数。 */ fun relativePath(absoluteUrl: String): String { - val url = absoluteUrl.toHttpUrlOrNull() ?: return absoluteUrl + val url = HttpUrl.parse(absoluteUrl) ?: return absoluteUrl // pathSegments.joinToString("/") 自动处理首尾斜杠 - return url.encodedPath.trimStart('/').let { + return url.encodedPath().trimStart('/').let { // 去掉 /api/ 前缀,匹配 Set 中的相对路径 if (it.startsWith("api/")) it.removePrefix("api/") else it } diff --git a/config.gradle b/config.gradle index a5ffaa8..db06180 100755 --- a/config.gradle +++ b/config.gradle @@ -1,7 +1,7 @@ ext { app_android = [ compileSdkVersion: 34, - minSdkVersion : 19, + minSdkVersion : 17, // Android 4.2 兼容(探测编译,待验证) targetSdkVersion : 29, multiDexEnabled : true ] diff --git a/mediaplayertv/src/main/java/com/pukka/videoplayertv/VideoViewTv.java b/mediaplayertv/src/main/java/com/pukka/videoplayertv/VideoViewTv.java index a7ccec1..7970046 100755 --- a/mediaplayertv/src/main/java/com/pukka/videoplayertv/VideoViewTv.java +++ b/mediaplayertv/src/main/java/com/pukka/videoplayertv/VideoViewTv.java @@ -328,6 +328,11 @@ public class VideoViewTv extends FrameLayout implements MediaPlayerControl { } private void initVideoView() { + // Android 4.2 兼容:ExoPlayer 2.19 官方支持 API 19+,低版本强制切系统 MediaPlayer 内核, + // 避免 PkExoMediaPlayer/ExoPlayer 类在 Dalvik 上触发 VerifyError + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + terminalType = 0; + } if (!checkAudioCapability()){ terminalType = 0; } @@ -562,7 +567,9 @@ public class VideoViewTv extends FrameLayout implements MediaPlayerControl { reset(); mMediaPlayer.release(); if(mMediaPlayer != null){ - if(mMediaPlayer instanceof PkExoMediaPlayer){ + // terminalType 短路:非 Exo 内核时跳过 instanceof,避免 Dalvik 解析 + // PkExoMediaPlayer 引发的 ExoPlayer 类加载 VerifyError(Android 4.2) + if(terminalType == 2 && mMediaPlayer instanceof PkExoMediaPlayer){ PkExoMediaPlayer pkExoMediaPlayer = (PkExoMediaPlayer)mMediaPlayer; pkExoMediaPlayer.resetListeners(); } @@ -1491,7 +1498,8 @@ public class VideoViewTv extends FrameLayout implements MediaPlayerControl { onPauseListener = null; onResumeListener = null; if(mMediaPlayer != null){ - if(mMediaPlayer instanceof PkExoMediaPlayer){ + // terminalType 短路:同上,避免非 Exo 内核时 Dalvik 触发 PkExoMediaPlayer 类解析 + if(terminalType == 2 && mMediaPlayer instanceof PkExoMediaPlayer){ PkExoMediaPlayer pkExoMediaPlayer = (PkExoMediaPlayer)mMediaPlayer; pkExoMediaPlayer.resetListeners(); }