fix: 图片和语音改用 HttpURLConnection 下载

OkHttp 在该设备连 OSS 的 HTTPS/HTTP 都被 reset。
改用系统 HttpURLConnection(走系统 TLS 栈,可能支持不同 cipher suite)。
图片和语音统一使用原始 HTTPS URL。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dongliang
2026-05-07 12:23:54 +09:30
parent 77619a5300
commit cacbdb6aaa

View File

@@ -19,8 +19,6 @@ import com.xiaoqu.watch.databinding.FragmentTaskListBinding
import com.xiaoqu.watch.network.ApiResult import com.xiaoqu.watch.network.ApiResult
import com.xiaoqu.watch.network.api.TaskApi import com.xiaoqu.watch.network.api.TaskApi
import com.xiaoqu.watch.network.safeApiCall import com.xiaoqu.watch.network.safeApiCall
import okhttp3.OkHttpClient
import okhttp3.Request
import com.xiaoqu.watch.service.manager.NfcTaskManager import com.xiaoqu.watch.service.manager.NfcTaskManager
import com.xiaoqu.watch.ui.common.BaseFragment import com.xiaoqu.watch.ui.common.BaseFragment
import com.xiaoqu.watch.ui.widget.QuTipDialog import com.xiaoqu.watch.ui.widget.QuTipDialog
@@ -45,8 +43,6 @@ class TaskListFragment : BaseFragment<FragmentTaskListBinding>() {
@Inject lateinit var taskApi: TaskApi @Inject lateinit var taskApi: TaskApi
@Inject lateinit var nfcTaskManager: NfcTaskManager @Inject lateinit var nfcTaskManager: NfcTaskManager
/** 下载用的 OkHttp不带签名拦截器走 HTTP 避免 TLS 问题) */
private val downloadClient by lazy { OkHttpClient.Builder().build() }
/** 任务 ID 列表queryTaskIds 返回) */ /** 任务 ID 列表queryTaskIds 返回) */
private var taskList: List<TaskItem> = emptyList() private var taskList: List<TaskItem> = emptyList()
@@ -438,10 +434,9 @@ class TaskListFragment : BaseFragment<FragmentTaskListBinding>() {
scaleType = android.widget.ImageView.ScaleType.CENTER_CROP scaleType = android.widget.ImageView.ScaleType.CENTER_CROP
setBackgroundColor(requireContext().getColor(R.color.card_background)) setBackgroundColor(requireContext().getColor(R.color.card_background))
} }
val httpUrl = pic.url.replace("https://", "http://") loadImage(imageView, pic.url)
loadImage(imageView, httpUrl)
// 点击放大全屏查看 // 点击放大全屏查看
imageView.setOnClickListener { showFullImage(httpUrl) } imageView.setOnClickListener { showFullImage(pic.url) }
binding.picContainer.addView(imageView) binding.picContainer.addView(imageView)
} }
} }
@@ -471,18 +466,21 @@ class TaskListFragment : BaseFragment<FragmentTaskListBinding>() {
loadImage(imageView, url) loadImage(imageView, url)
} }
/** 异步加载图片到 ImageView */ /** 异步加载图片到 ImageView(使用系统 HttpURLConnection */
private fun loadImage(imageView: android.widget.ImageView, url: String) { private fun loadImage(imageView: android.widget.ImageView, url: String) {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
try { try {
val bitmap = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) { val bitmap = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
val request = Request.Builder().url(url).build() val conn = java.net.URL(url).openConnection() as java.net.HttpURLConnection
val response = downloadClient.newCall(request).execute() conn.connectTimeout = 10000
if (response.isSuccessful) { conn.readTimeout = 10000
response.body?.byteStream()?.let { conn.connect()
android.graphics.BitmapFactory.decodeStream(it) if (conn.responseCode == 200) {
conn.inputStream.use { android.graphics.BitmapFactory.decodeStream(it) }
} else {
Timber.w("图片HTTP错误: ${conn.responseCode} $url")
null
} }
} else null
} }
bitmap?.let { imageView.setImageBitmap(it) } bitmap?.let { imageView.setImageBitmap(it) }
} catch (e: Exception) { } catch (e: Exception) {
@@ -549,21 +547,21 @@ class TaskListFragment : BaseFragment<FragmentTaskListBinding>() {
} }
} }
/** 用 OkHttp 下载音频文件到缓存目录 */ /** 下载音频文件到缓存目录(使用系统 HttpURLConnection */
private fun downloadToCache(url: String): java.io.File? { private fun downloadToCache(url: String): java.io.File? {
return try { return try {
// OSS 的 HTTPS 在 Android 8.1 设备 TLS 握手失败,降级为 HTTP val conn = java.net.URL(url).openConnection() as java.net.HttpURLConnection
val httpUrl = url.replace("https://", "http://") conn.connectTimeout = 15000
val request = Request.Builder().url(httpUrl).build() conn.readTimeout = 15000
val response = downloadClient.newCall(request).execute() conn.connect()
if (!response.isSuccessful) { if (conn.responseCode != 200) {
Timber.w("语音下载HTTP错误: ${response.code}") Timber.w("语音下载HTTP错误: ${conn.responseCode}")
return null return null
} }
// 写入缓存文件 // 写入缓存文件
val fileName = "voice_${url.hashCode()}.mp3" val fileName = "voice_${url.hashCode()}.mp3"
val cacheFile = java.io.File(requireContext().cacheDir, fileName) val cacheFile = java.io.File(requireContext().cacheDir, fileName)
response.body?.byteStream()?.use { input -> conn.inputStream.use { input ->
cacheFile.outputStream().use { output -> cacheFile.outputStream().use { output ->
input.copyTo(output) input.copyTo(output)
} }