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,196 @@
package com.sw.inbound.utils
import android.content.Context
import timber.log.Timber
import java.io.File
/**
* 模型资源管理器
* 负责从接口下载模型资源包(Zip),并解压到应用缓存目录
* 替代原有的 assets 中写死的 data 文件夹和 .pt 文件
*/
object ModelResourceManager {
/**
* 资源下载和解压的回调接口
*/
interface ResourceCallback {
/**
* 下载进度回调
* @param progress 进度百分比 (0-100)
* @param message 进度信息
*/
fun onDownloadProgress(progress: Int, message: String)
/**
* 解压进度回调
* @param progress 进度百分比 (0-100)
* @param message 进度信息
*/
fun onExtractProgress(progress: Int, message: String)
/**
* 完成回调
* @param resourceDir 资源所在目录
*/
fun onSuccess(resourceDir: File)
/**
* 失败回调
* @param errorMessage 错误信息
*/
fun onFailure(errorMessage: String)
}
/**
* 下载并解压模型资源
* @param context 应用上下文
* @param downloadUrl 资源包下载链接
* @param callback 进度和结果回调
*/
fun downloadAndExtractResources(
context: Context,
downloadUrl: String,
callback: ResourceCallback
) {
try {
// 获取缓存目录
val cacheDir = context.cacheDir
val resourcesDir = File(cacheDir, "model_resources")
val zipFile = File(cacheDir, "model_resources.zip")
Timber.d("资源缓存目录: ${resourcesDir.absolutePath}")
Timber.d("Zip 文件路径: ${zipFile.absolutePath}")
// Step 1: 下载 Zip 文件
Timber.d("开始下载资源包: $downloadUrl")
ZipDownloadUtils.downloadZip(
downloadUrl = downloadUrl,
targetFile = zipFile,
listener = object : ZipDownloadUtils.DownloadProgressListener {
override fun onProgress(currentSize: Long, totalSize: Long, progress: Int) {
val message = "下载中... ${formatFileSize(currentSize)}/${formatFileSize(totalSize)}"
callback.onDownloadProgress(progress, message)
}
override fun onSuccess(file: File) {
Timber.d("资源包下载完成")
callback.onDownloadProgress(100, "下载完成")
// Step 2: 解压 Zip 文件
extractResources(resourcesDir, zipFile, callback)
}
override fun onFailure(exception: Exception) {
val errorMsg = "资源包下载失败: ${exception.message}"
Timber.e(exception, errorMsg)
callback.onFailure(errorMsg)
}
}
)
} catch (e: Exception) {
val errorMsg = "资源下载和解压过程异常: ${e.message}"
Timber.e(e, errorMsg)
callback.onFailure(errorMsg)
}
}
/**
* 解压资源包
*/
private fun extractResources(
resourcesDir: File,
zipFile: File,
callback: ResourceCallback
) {
try {
Timber.d("开始解压资源包")
ZipExtractUtils.extractZip(
zipFile = zipFile,
targetDir = resourcesDir,
listener = object : ZipExtractUtils.ExtractProgressListener {
override fun onProgress(
currentFile: String,
currentIndex: Int,
totalFiles: Int,
progress: Int
) {
val message = "解压中... ($currentIndex/$totalFiles) $currentFile"
callback.onExtractProgress(progress, message)
}
override fun onSuccess(targetDir: File) {
Timber.d("资源包解压完成: ${targetDir.absolutePath}")
callback.onExtractProgress(100, "解压完成")
// 清理 Zip 文件
zipFile.delete()
Timber.d("临时 Zip 文件已删除")
callback.onSuccess(targetDir)
}
override fun onFailure(exception: Exception) {
val errorMsg = "资源包解压失败: ${exception.message}"
Timber.e(exception, errorMsg)
callback.onFailure(errorMsg)
}
}
)
} catch (e: Exception) {
val errorMsg = "解压过程异常: ${e.message}"
Timber.e(e, errorMsg)
callback.onFailure(errorMsg)
}
}
/**
* 获取已缓存的资源目录
* @param context 应用上下文
* @return 资源目录,如果不存在则返回 null
*/
fun getCachedResourcesDir(context: Context): File? {
val resourcesDir = File(context.cacheDir, "model_resources")
return if (resourcesDir.exists() && resourcesDir.isDirectory) {
resourcesDir
} else {
null
}
}
/**
* 清理缓存的资源
* @param context 应用上下文
* @return 是否清理成功
*/
fun clearCachedResources(context: Context): Boolean {
return try {
val resourcesDir = File(context.cacheDir, "model_resources")
val zipFile = File(context.cacheDir, "model_resources.zip")
val dirDeleted = resourcesDir.deleteRecursively()
val zipDeleted = zipFile.delete()
Timber.d("清理缓存资源: 目录=$dirDeleted, Zip=$zipDeleted")
dirDeleted || zipDeleted
} catch (e: Exception) {
Timber.e(e, "清理缓存资源失败")
false
}
}
/**
* 格式化文件大小为可读字符串
*/
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))
}
}
}