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();
}