- 恢复 ToastUtils.kt 为原始注释状态(Compose 版本) - 修正 FunActivity 导入,使用 Common.kt 中已定义的 Context.toast 扩展方法 - 更新下载地址为真实的资源包地址
299 lines
11 KiB
Kotlin
299 lines
11 KiB
Kotlin
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<List<Float>> =
|
|
Gson().fromJson(embeddingsJson, object : TypeToken<List<List<Float>>>() {}.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<Food>()
|
|
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<Food>()
|
|
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))
|
|
}
|
|
}
|
|
}
|