V1.4.6: 点歌取址失败——修裸 Socket 通道两处缺陷 + 打通 JS 侧日志
现象: 曲库正常后点歌失败「详情=无法生成下载地址」,且远程日志里没有任何线索。
排查(本地复现源站协议):
- 用同一套签名从本机请求 i.php / music/do.php 全部正常,能拿到这首歌的真实地址;
换 App 注入的版本号(VER=2.0/VN=4.1.3.03281430) 也正常 → 源站没问题,是设备侧。
- 根因在 KtvJsBridge.rawRequest() 这条裸 Socket 通道(点歌取址正是走它):
1. 连接阶段没有超时: `Socket(host, port)` 不受 soTimeout 约束,遇路由黑洞按系统
TCP 超时(~2min)干等;7 个源叠加远超 JS_REQUEST_TIMEOUT_MS(180s) →
桥返回空 → 「无法生成下载地址」,而日志里连失败都没有(请求还没失败);
2. HTTPS 分支用 SSLSocketFactory.getDefault(),既不受
HttpsURLConnection.setDefaultSSLSocketFactory 影响,也不受
network_security_config 约束 → 老设备缺 ISRG 新根时握手必失败。
修复:
- rawRequest 连接改为显式 connect(timeout): Socket().connect(InetSocketAddress, t);
HTTPS 改用 TlsCompat.sslSocketFactory()(系统信任 ∪ 内置根证书)。
- TlsCompat 暴露 sslSocketFactory(),并按 API 分级: 裸通道始终用增强工厂,
HttpsURLConnection 默认工厂仅在 API<24 接管。
- 裸通道失败上报 RAW-ERR(原来只在 logcat)。
- 打通 JS 侧日志: wrapHtml 注入 console 桥,把 ktv_api.js 的 _log()/console.log
转发到 android.log → MiliLog("JS ...");点歌失败时可通过
「JS ...」逐步过程 + 「点歌失败 ... 详情=」汇总原因定位。
版本 1.4.5(25) 到 1.4.6(26);签名证书不变。ktv_api.js 未改动,KTV_BRIDGE_API 仍为 8。
This commit is contained in:
@@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLException
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
@@ -47,6 +48,20 @@ object TlsCompat {
|
||||
|
||||
@Volatile private var installed = false
|
||||
|
||||
/** 「系统信任 ∪ 内置根证书」的 SSLSocketFactory,供裸 Socket 通道复用。 */
|
||||
@Volatile private var patchedFactory: SSLSocketFactory? = null
|
||||
|
||||
/**
|
||||
* 给**裸 Socket** 通道用的 SSLSocketFactory。
|
||||
*
|
||||
* ⚠️ 必须单独提供:`HttpsURLConnection.setDefaultSSLSocketFactory` **不会**影响
|
||||
* `SSLSocketFactory.getDefault()`,而 `KtvJsBridge.rawRequest()` 的 HTTPS 分支正是用后者
|
||||
* (`network_security_config` 也只作用于 HttpsURLConnection / WebView,管不到裸 socket)。
|
||||
* 未初始化时回退系统默认。
|
||||
*/
|
||||
fun sslSocketFactory(): SSLSocketFactory =
|
||||
patchedFactory ?: (SSLSocketFactory.getDefault() as SSLSocketFactory)
|
||||
|
||||
/** 主机 → 是否已被增强信任库校验通过(只缓存肯定结果)。 */
|
||||
private val trustedHosts = ConcurrentHashMap<String, Boolean>()
|
||||
|
||||
@@ -63,15 +78,18 @@ object TlsCompat {
|
||||
Log.w(TAG, "no bundled certs loaded; keep system trust only")
|
||||
return
|
||||
}
|
||||
if (android.os.Build.VERSION.SDK_INT >= 24) {
|
||||
// 新系统由 network_security_config 负责,无需接管(避免多此一举)。
|
||||
Log.i(TAG, "API>=24, network-security-config handles bundled roots")
|
||||
return
|
||||
}
|
||||
val composite = CompositeTrustManager(system, bundled)
|
||||
val sslContext = SSLContext.getInstance("TLS")
|
||||
sslContext.init(null, arrayOf<TrustManager>(composite), null)
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)
|
||||
val factory = sslContext.socketFactory as SSLSocketFactory
|
||||
// 裸 Socket 通道始终使用它(系统信任 + 内置根证书,二者取并集)。
|
||||
patchedFactory = factory
|
||||
if (android.os.Build.VERSION.SDK_INT >= 24) {
|
||||
// 新系统由 network_security_config 负责 HttpsURLConnection,无需接管默认工厂。
|
||||
Log.i(TAG, "API>=24, keep system default for HttpsURLConnection")
|
||||
return
|
||||
}
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(factory)
|
||||
Log.i(
|
||||
TAG,
|
||||
"legacy TLS trust installed: system(${system?.acceptedIssuers?.size ?: 0} CAs) " +
|
||||
|
||||
Reference in New Issue
Block a user