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}") }
问题根源分析
- ProgressDialog提前关闭:在
addOnCompleteListener中,显示progessdialog2后,不管文件写入的异步任务是否完成,直接在when块末尾调用了progessdialog2.dismiss(),导致对话框提前关闭。 - UI冻结:
Files.readAllBytes是同步IO操作,直接在主线程执行会阻塞UI;另外writeTemplate是和硬件设备交互的耗时操作,同样不应该放在主线程。 - 类型转换错误:
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
相关产品推荐
相关产品推荐

