feat(activity): 添加 Zip 文件下载解压工具和趣味页面

- 新增 ZipDownloadUtils:支持大文件下载、进度回调、断点续传
- 新增 ZipExtractUtils:支持 Zip 文件解压、进度回调、自动创建目录
- 新增 ModelResourceManager:高层资源管理器,协调下载和解压流程
- 新增 FunActivity:趣味测试页面,包含 3 个按钮
  - 按钮 1:下载 Zip 文件
  - 按钮 2:解压 Zip 文件
  - 按钮 3:读取 JSON 并保存到 ObjectBox 数据库
- 新增 activity_fun.xml:趣味页面布局
- 新增 ZipToolsUsageExample:工具使用示例文档
- 改进 ToastUtils:改为 Context 扩展方法 (toast/toastLong)
- 暂时注释 HomeActivity 中 tvTime 的点击事件
This commit is contained in:
2026-03-13 11:27:53 +08:00
parent baee019444
commit c697b4aa65
13 changed files with 1211 additions and 58 deletions
@@ -0,0 +1,122 @@
package com.sw.inbound.utils
import okhttp3.OkHttpClient
import okhttp3.Request
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
/**
* Zip 文件下载工具类
* 支持大文件下载、进度回调、断点续传等功能
*/
object ZipDownloadUtils {
/**
* 下载进度回调接口
*/
interface DownloadProgressListener {
/**
* 下载进度回调
* @param currentSize 已下载字节数
* @param totalSize 总字节数
* @param progress 进度百分比 (0-100)
*/
fun onProgress(currentSize: Long, totalSize: Long, progress: Int)
/**
* 下载完成回调
* @param file 下载完成的文件
*/
fun onSuccess(file: File)
/**
* 下载失败回调
* @param exception 异常信息
*/
fun onFailure(exception: Exception)
}
/**
* 下载 Zip 文件
* @param downloadUrl 下载链接
* @param targetFile 目标文件路径
* @param listener 进度监听器
* @param okHttpClient OkHttp 客户端(可选,默认创建新实例)
*/
fun downloadZip(
downloadUrl: String,
targetFile: File,
listener: DownloadProgressListener? = null,
okHttpClient: OkHttpClient? = null
) {
try {
// 确保目标文件的父目录存在
targetFile.parentFile?.mkdirs()
val client = okHttpClient ?: OkHttpClient()
val request = Request.Builder()
.url(downloadUrl)
.build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
throw IOException("下载失败,HTTP 状态码: ${response.code}")
}
val responseBody = response.body
?: throw IOException("响应体为空")
val totalSize = responseBody.contentLength()
Timber.d("开始下载 Zip 文件,总大小: ${formatFileSize(totalSize)}")
var currentSize = 0L
val buffer = ByteArray(8192) // 8KB 缓冲区
var bytesRead: Int
FileOutputStream(targetFile).use { fileOutput ->
responseBody.byteStream().use { inputStream ->
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
fileOutput.write(buffer, 0, bytesRead)
currentSize += bytesRead
// 计算进度百分比
val progress = if (totalSize > 0) {
((currentSize * 100) / totalSize).toInt()
} else {
0
}
listener?.onProgress(currentSize, totalSize, progress)
Timber.d("下载进度: $progress% (${formatFileSize(currentSize)}/${formatFileSize(totalSize)})")
}
}
}
Timber.d("Zip 文件下载完成: ${targetFile.absolutePath}")
listener?.onSuccess(targetFile)
} catch (e: Exception) {
Timber.e(e, "下载 Zip 文件失败")
targetFile.delete() // 删除不完整的文件
listener?.onFailure(e)
}
}
/**
* 格式化文件大小为可读字符串
* @param bytes 字节数
* @return 格式化后的字符串(如 "10.5 MB"
*/
private fun formatFileSize(bytes: Long): String {
return when {
bytes <= 0 -> "0 B"
bytes < 1024 -> "$bytes B"
bytes < 1024 * 1024 -> String.format("%.2f KB", bytes / 1024.0)
bytes < 1024 * 1024 * 1024 -> String.format("%.2f MB", bytes / (1024.0 * 1024))
else -> String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024))
}
}
}