package com.sw.inbound.activity import android.os.Bundle import androidx.lifecycle.lifecycleScope import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.sw.inbound.base.BaseActivity import com.sw.inbound.databinding.ActivityFunBinding import com.sw.inbound.objbox.Food import com.sw.inbound.objbox.FoodClassInfo import com.sw.inbound.objbox.ObjectBox import com.sw.inbound.utils.ext.toast import com.sw.inbound.utils.ZipDownloadUtils import com.sw.inbound.utils.ZipExtractUtils import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber import java.io.File /** * 趣味页面 Activity * 用于测试 Zip 文件下载、解压和数据库操作 */ @AndroidEntryPoint class FunActivity : BaseActivity() { private lateinit var binding: ActivityFunBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityFunBinding.inflate(layoutInflater) setContentView(binding.root) initViews() } /** * 初始化各控件的点击事件 */ private fun initViews() { // 返回按钮点击事件 binding.ivBack.setOnClickListener { finish() } // 下载按钮 binding.btnDownload.setOnClickListener { downloadZipFile() } // 解压按钮 binding.btnExtract.setOnClickListener { extractZipFile() } // 读取并保存按钮 binding.btnLoadAndSave.setOnClickListener { loadAndSaveToDatabase() } } /** * 下载 Zip 文件 */ private fun downloadZipFile() { lifecycleScope.launch { try { updateProgress("开始下载...") binding.btnDownload.isEnabled = false val zipFile = File(cacheDir, "model_resources.zip") // 假的下载地址 val downloadUrl = "https://dev.yixiong-tech.com:8081/common/files/20260313/embedding_model_resources.zip" withContext(Dispatchers.IO) { ZipDownloadUtils.downloadZip( downloadUrl = downloadUrl, targetFile = zipFile, listener = object : ZipDownloadUtils.DownloadProgressListener { override fun onProgress(currentSize: Long, totalSize: Long, progress: Int) { updateProgress("下载中... $progress%") } override fun onSuccess(file: File) { updateProgress("下载完成!文件大小: ${formatFileSize(file.length())}") Timber.d("Zip 文件下载完成: ${file.absolutePath}") } override fun onFailure(exception: Exception) { updateProgress("下载失败: ${exception.message}") Timber.e(exception, "下载失败") this@FunActivity.toast("下载失败: ${exception.message}") } } ) } } catch (e: Exception) { Timber.e(e, "下载异常") updateProgress("异常: ${e.message}") this@FunActivity.toast("异常: ${e.message}") } finally { binding.btnDownload.isEnabled = true } } } /** * 解压 Zip 文件 */ private fun extractZipFile() { lifecycleScope.launch { try { updateProgress("开始解压...") binding.btnExtract.isEnabled = false val zipFile = File(cacheDir, "model_resources.zip") val targetDir = File(cacheDir, "model_resources") if (!zipFile.exists()) { updateProgress("Zip 文件不存在,请先下载") this@FunActivity.toast("Zip 文件不存在,请先下载") return@launch } withContext(Dispatchers.IO) { ZipExtractUtils.extractZip( zipFile = zipFile, targetDir = targetDir, listener = object : ZipExtractUtils.ExtractProgressListener { override fun onProgress( currentFile: String, currentIndex: Int, totalFiles: Int, progress: Int ) { updateProgress("解压中... $progress% ($currentIndex/$totalFiles)") } override fun onSuccess(targetDir: File) { updateProgress("解压完成!目录: ${targetDir.absolutePath}") Timber.d("Zip 文件解压完成: ${targetDir.absolutePath}") } override fun onFailure(exception: Exception) { updateProgress("解压失败: ${exception.message}") Timber.e(exception, "解压失败") this@FunActivity.toast("解压失败: ${exception.message}") } } ) } } catch (e: Exception) { Timber.e(e, "解压异常") updateProgress("异常: ${e.message}") this@FunActivity.toast("异常: ${e.message}") } finally { binding.btnExtract.isEnabled = true } } } /** * 读取解压后的文件并保存到 ObjectBox 数据库 */ private fun loadAndSaveToDatabase() { lifecycleScope.launch { try { updateProgress("开始读取数据...") binding.btnLoadAndSave.isEnabled = false val resourcesDir = File(cacheDir, "model_resources") if (!resourcesDir.exists()) { updateProgress("资源目录不存在,请先解压") this@FunActivity.toast("资源目录不存在,请先解压") return@launch } withContext(Dispatchers.IO) { // 清除已有数据 updateProgress("清除已有数据...") clearFoodData() // 读取 JSON 文件 updateProgress("读取 JSON 文件...") val embeddingsJson = readFileContent(File(resourcesDir, "data/embeddings.json")) val labelsJson = readFileContent(File(resourcesDir, "data/labels.json")) val classInfoJson = readFileContent(File(resourcesDir, "data/class_info.json")) if (embeddingsJson.isEmpty() || labelsJson.isEmpty() || classInfoJson.isEmpty()) { updateProgress("JSON 文件读取失败") this@FunActivity.toast("JSON 文件读取失败") return@withContext } // 解析 JSON 数据 updateProgress("解析 JSON 数据...") val embeddingsList: List> = Gson().fromJson(embeddingsJson, object : TypeToken>>() {}.type) val labelsList: IntArray = Gson().fromJson(labelsJson, IntArray::class.java) val classInfo: FoodClassInfo = Gson().fromJson(classInfoJson, FoodClassInfo::class.java) // 构建 Food 对象列表 updateProgress("构建数据对象...") val foodMap = classInfo.idx_to_class val foodList = mutableListOf() val size = embeddingsList.size Timber.d("开始构建 Food 对象,总数: $size") embeddingsList.forEachIndexed { index, floatList -> val classIdx = labelsList[index] val foodName = foodMap["$classIdx"] val array = floatList.toFloatArray() val food = Food(name = foodName, foodVector = array, foodIdx = -1) foodList.add(food) // 每 100 条更新一次进度 if ((index + 1) % 100 == 0) { val progress = ((index + 1) * 100) / size updateProgress("构建数据中... $progress% (${index + 1}/$size)") } } // 保存到数据库 updateProgress("保存到数据库...") ObjectBox.putAll(foodList) updateProgress("完成!已保存 ${foodList.size} 条数据到数据库") Timber.d("数据保存完成,总数: ${foodList.size}") } } catch (e: Exception) { Timber.e(e, "读取和保存异常") updateProgress("异常: ${e.message}") this@FunActivity.toast("异常: ${e.message}") } finally { binding.btnLoadAndSave.isEnabled = true } } } /** * 清除 Food 表中的所有数据 */ private suspend fun clearFoodData() { try { ObjectBox.safeDbOp { val box = ObjectBox.getBox() box?.removeAll() Timber.d("Food 表数据已清除") } } catch (e: Exception) { Timber.e(e, "清除数据失败") } } /** * 读取文件内容 */ private fun readFileContent(file: File): String { return try { if (!file.exists()) { Timber.w("文件不存在: ${file.absolutePath}") "" } else { file.readText() } } catch (e: Exception) { Timber.e(e, "读取文件失败: ${file.absolutePath}") "" } } /** * 更新进度显示 */ private fun updateProgress(message: String) { runOnUiThread { binding.tvProgress.text = message Timber.d("进度: $message") } } /** * 格式化文件大小 */ 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)) } } }