You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin/Java读取多文件字节时添加进度对话框解决UI冻结问题

问题描述

我从Firebase Storage创建了多文件下载目录,需要读取已下载文件的所有字节并写入第三方指纹设备,但遇到两个问题:

  • ProgressDialog在文件完成写入前就提前关闭
  • Android UI出现冻结情况
原代码示例
val storageReference =
    FirebaseStorage.getInstance().reference.child("finger-blok/fingertemplate${FileBin.name!!.replace(" ", "")}.bin")
val rootPath = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    File(
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
            .toString() + "/" + "finger-blok"
    )
} else {
    File(
        Environment.getExternalStorageDirectory()
            .toString() + "/${Environment.DIRECTORY_DOWNLOADS}/" + "finger-blok"
    )
}

if (!rootPath.exists()) {
    rootPath.mkdirs()
}
val localFile = File(rootPath, "fingertemplate${FileBin.name!!.replace(" ", "")}.bin")
val task = storageReference.getFile(localFile)

task.addOnCompleteListener {
    Log.e("localfile is create", "${it.result.bytesTransferred}, fingerid : ${FileBin.fingerid}, ${localFile.path}")
    progessdialog2 = ProgressDialog(context, R.style.AppCompatAlertDialogStyle).apply {
        this.setCancelable(false)
        this.setMessage("Write Data ...")
        progessdialog.dismiss()
        this.show()
    }
    when{
        !progessdialog.isShowing->{
            if (progessdialog2.isShowing){
                val filebyteserver: Task<ByteArray> = Files.readAllBytes(localFile.toPath()) as Task<ByteArray>
                filebyteserver.addOnCompleteListener { byte->
                    statuswr = writeTemplate(FileBin.fingerid!!.toInt(), byte.result)
                    Log.e("status_write_template", "Status Write template ${FileBin.name} : $statuswr")
                    if (statuswr){
                        localFile.delete()
                    }
                    Log.e("BYTEARRAY", byte.result.contentToString()+", ${FileBin.name!!.count()}")
                    val toast = Toast.makeText(
                        context,
                        "Tulis Fingerprint $statuswr",
                        Toast.LENGTH_SHORT
                    )
                    toast.show()
                }
            }
            progessdialog2.dismiss() 
        }
    }

}.addOnProgressListener {
    var persen = it.bytesTransferred/it.totalByteCount * 100
    progessdialog.setMessage("Update Data $persen")
}.addOnFailureListener {
    Log.e("ErrorDownload File", "${it.message}")
}
问题根源分析
  1. ProgressDialog提前关闭:在addOnCompleteListener中,显示progessdialog2后,不管文件写入的异步任务是否完成,直接在when块末尾调用了progessdialog2.dismiss(),导致对话框提前关闭。
  2. UI冻结:Files.readAllBytes是同步IO操作,直接在主线程执行会阻塞UI;另外writeTemplate是和硬件设备交互的耗时操作,同样不应该放在主线程。
  3. 类型转换错误:Files.readAllBytes()返回的是ByteArray,不是Task<ByteArray>,强制转换会抛出异常,这部分逻辑完全错误。
修正后的代码方案

使用Kotlin协程将文件读取和硬件写入操作放到后台线程,确保主线程不被阻塞,并且只在所有操作完成后才关闭ProgressDialog:

val storageReference =
    FirebaseStorage.getInstance().reference.child("finger-blok/fingertemplate${FileBin.name!!.replace(" ", "")}.bin")
val rootPath = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    File(
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
        "finger-blok"
    )
} else {
    File(
        Environment.getExternalStorageDirectory(),
        "${Environment.DIRECTORY_DOWNLOADS}/finger-blok"
    )
}

if (!rootPath.exists()) {
    rootPath.mkdirs()
}
val localFile = File(rootPath, "fingertemplate${FileBin.name!!.replace(" ", "")}.bin")
val task = storageReference.getFile(localFile)

task.addOnCompleteListener { taskResult ->
    Log.e("localfile is create", "${taskResult.result.bytesTransferred}, fingerid : ${FileBin.fingerid}, ${localFile.path}")
    
    // 显示写入进度对话框
    progessdialog2 = ProgressDialog(context, R.style.AppCompatAlertDialogStyle).apply {
        setCancelable(false)
        setMessage("Write Data ...")
        progessdialog.dismiss()
        show()
    }

    // 使用协程在后台执行文件读取和写入操作
    CoroutineScope(Dispatchers.IO).launch {
        try {
            // 同步读取文件字节(在IO线程执行,不阻塞主线程)
            val fileBytes = Files.readAllBytes(localFile.toPath())
            
            // 执行指纹设备写入操作
            statuswr = writeTemplate(FileBin.fingerid!!.toInt(), fileBytes)
            
            // 回到主线程更新UI和关闭对话框
            withContext(Dispatchers.Main) {
                Log.e("status_write_template", "Status Write template ${FileBin.name} : $statuswr")
                if (statuswr) {
                    localFile.delete()
                }
                Log.e("BYTEARRAY", fileBytes.contentToString() + ", ${FileBin.name!!.count()}")
                Toast.makeText(context, "Tulis Fingerprint $statuswr", Toast.LENGTH_SHORT).show()
                
                // 所有操作完成后关闭对话框
                progessdialog2.dismiss()
            }
        } catch (e: Exception) {
            // 异常处理,回到主线程关闭对话框并提示错误
            withContext(Dispatchers.Main) {
                Log.e("WriteError", "Failed to write fingerprint: ${e.message}")
                Toast.makeText(context, "写入失败: ${e.message}", Toast.LENGTH_SHORT).show()
                progessdialog2.dismiss()
            }
        }
    }
}.addOnProgressListener {
    val persen = (it.bytesTransferred.toFloat() / it.totalByteCount) * 100
    progessdialog.setMessage("Update Data ${persen.toInt()}%")
}.addOnFailureListener {
    Log.e("ErrorDownload File", "${it.message}")
    Toast.makeText(context, "下载失败: ${it.message}", Toast.LENGTH_SHORT).show()
    progessdialog.dismiss()
}
关键优化点
  • 用CoroutineScope(Dispatchers.IO)将文件读取和硬件写入放到IO线程,避免阻塞主线程导致UI冻结
  • 将progessdialog2.dismiss()移到写入操作完成后的主线程代码块中,确保对话框只在所有操作完成后关闭
  • 修正了文件读取的错误逻辑,去掉了错误的Task<ByteArray>强制转换
  • 添加了异常捕获,处理文件读取或写入失败的情况,避免崩溃
  • 进度计算修正为浮点运算,避免整数除法导致进度始终为0的问题

内容的提问来源于stack exchange,提问作者faisal Cyber Dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 03:15:36