android 如何从filepicker获取大文件的字节

jv4diomz  于 2023-04-28  发布在  Android
关注(0)|答案(2)|浏览(192)

在应用程序中,我使用filepicker,通过它我选择文件-〉转换为字节-〉上传到服务器:

private val filesReceiver =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
            if (result.resultCode == Activity.RESULT_OK) {
                val data = result.data
                data?.let {
                    val fileUri = data.data!!
                    var name = ""
                    var size: Long? = null

                    fileUri.let { returnUri ->
                        requireContext().contentResolver.query(returnUri, null, null, null, null)
                    }?.use { cursor ->
                        val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                        val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
                        cursor.moveToFirst()
                        name = cursor.getString(nameIndex)
                        size = cursor.getLong(sizeIndex)
                    }

                    val content = data.data?.let { uri ->
                        requireContext().contentResolver.openInputStream(uri)
                            .use { it!!.readBytes() }
                    }
                    val fileContent = String(content!!, Charsets.ISO_8859_1) 
                    if (size != null && size!!.toInt() > 0) {
                        sendingFile(fileContent, name, size!!)
                    }
                }
            }
        }

它可以很好地处理小文件(〈20 MB)。但是对于大文件(150+ MB),我收到OutOfMemoryException。我在清单中添加了这样的:

android:hardwareAccelerated="true"
android:largeHeap="true"

但它并没有帮助我。我想用途:

fun File.readBytes(): ByteArray

从URI构建文件,但是我得到了关于URI格式的异常。那么,我们有没有什么方法可以像我对小文件一样获得大文件的内容?

更新

此代码用于在聊天中发送文件。因此它可以通过块发送。下面是处理文件内容:

private fun sendingFile(fileContent: String, name: String, size: Long) {
        sendingTime = System.currentTimeMillis() / 1000
        if (size.toInt() < partSize) {
            sendFilePart(0, fileContent, name, size)
        } else {
            dialog.show()
            for (currentPart in 0..size.toInt().div(partSize)) {
                val slicedString = fileContent.slice(
                    currentPart * partSize
                            until if ((currentPart + 1) * partSize <= fileContent.length) {
                        (currentPart + 1) * partSize
                    } else {
                        fileContent.length
                    }
                )

                if (currentPart in 0..size.toInt().div(partSize)) {
                    dialog.hide()
                    sendingTime = null
                }

                sendFilePart(currentPart, slicedString, name, size)
            }
        }
    }

    private fun sendFilePart(part: Int?, fileContent: String, fileName: String, fileSize: Long) {
        chatVM.mWebSocket.value?.apply {
            if (sendingTime == null) {
                sendingTime = System.currentTimeMillis() / 1000
            }
            send(
                ChatRequestMessages.sendFilePart(
                    part!!,
                    fileContent,
                    fileName,
                    fileSize,
                    sendingTime!!,
                    chatVM.selectedContact.value?.id!!
                )
            )
        }
    }

这里块大小是private val partSize = 10000

polhcujo

polhcujo1#

如果你使用OkHttp上传文件,你可以简单地使用Uri创建请求体,以避免将整个文件内容读入内存:

class ContentUriRequestBody(
    private val contentResolver: ContentResolver,
    private val contentUri: Uri
) : RequestBody() {

    override fun contentType(): MediaType? {
        val contentType = contentResolver.getType(contentUri)
        return contentType?.toMediaTypeOrNull()
    }

    override fun writeTo(bufferedSink: BufferedSink) {
        val inputStream = contentResolver.openInputStream(contentUri)
            ?: throw IOException("Couldn't open content URI for reading")

        inputStream.source().use { source ->
            bufferedSink.writeAll(source)
        }
    }
}

来源:https://cketti.de/2020/05/23/content-uris-and-okhttp/

n1bvdmb6

n1bvdmb62#

因此,经过一些研究,我设法找到了解决方案。由于事实上,我发送的文件块,所以我这样做的方法:

fun InputStream.chunkedSequence(chunk: Int): Sequence<ByteArray> {
    val input = this.buffered()
    val buffer = ByteArray(chunk)
    return generateSequence {
        val red = input.read(buffer)
        if (red >= 0) buffer.copyOf(red)
        else {
            input.close()
            null
        }
    }
}

下面是选择后的文件处理:

fun sendFile(context: Context, fileUri: Uri, partSize: Int, callBack: (Any) -> Unit) {
        val sendingTime = System.currentTimeMillis() / 1000
        var name = ""
        var size: Long? = null

        fileUri.let { returnUri ->
            context.contentResolver.query(returnUri, null, null, null, null)
        }?.use { cursor ->
            val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
            val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
            cursor.moveToFirst()
            name = cursor.getString(nameIndex)
            size = cursor.getLong(sizeIndex)
        }

        val inputStream = context.contentResolver.openInputStream(fileUri)

        job = CoroutineScope(Dispatchers.IO).launch {
            withContext(Dispatchers.Main) {
                inputStream?.let { inStream ->
                    if (size!!.toInt() < partSize) {
                        sendFilePart(
                            0,
                            String(inStream.readBytes(), Charsets.ISO_8859_1),
                            name,
                            size!!,
                            sendingTime
                        )
                    } else {
                        var part = 0
                        inputStream.chunkedSequence(partSize).forEach {
                            sendFilePart(
                                part,
                                String(it, Charsets.ISO_8859_1),
                                name,
                                size!!,
                                sendingTime
                            )
                            if (part == (size!!.toInt().div(partSize))) {
                                callBack.invoke(true)
                                //inputStream.close()
                            }
                            part += 1
                        }
                    }
                }
            }
        }
    }

   fun sendFilePart(part: Int?, fileContent: String, fileName: String, fileSize: Long, sendingTime: Long) {
        mWebSocket.value?.apply {
            send(
                ChatRequestMessages.sendFilePart(
                    part!!,
                    fileContent,
                    fileName,
                    fileSize,
                    sendingTime,
                    selectedContact.value?.id!!
                )
            )
        }
    }

有时它可以冻结用户界面,但一般来说,它的工作。

相关问题