Android 文件分段上传和下载实现方案

 更新时间:2024年11月22日 14:37:25   作者:粤M温同学  
文章介绍了Android大文件下载和上传的分段处理方法,在下载时,使用多线程并发请求文件的多个段,并将这些段写入同一个文件,在上传时,根据文件大小分段上传,每段文件的大小由预设阈值决定,感兴趣的朋友跟随小编一起看看吧

一、背景

Android 中的大文件下载需要使用分段下载,下载通常是在线程中进行的,假如有5段,那同时5个线程去执行下载,请求http返回文件流后,需要将多个文件流同时写进同一个文件,这里用到

RandomAccessFile

分段上传的话,只需要根据每段文件阀值,例如,50M为一段,将文件按照设置的阀值,分段上传即可

二、相关代码

2.1 分段上传关键代码

忽略网络请求和状态码,每个人接口定义的的请求参数和返回code不一样

       private val DEFAULT_BLOCK_SIZE: Long = 50 * 1024 * 1024 //50MB
        val blockSize=DEFAULT_BLOCK_SIZE
        val randomAccessFile = RandomAccessFile(filePath, "r")
        val fileLen = randomAccessFile.length()
        //超过设定的单个文件大小,需要分块上传
            val blockFileNum = Math.ceil((fileLen / blockSize.toDouble())).toInt()
            XLogUtil.d("${TAG}blockFileNum:$blockFileNum,,,,fileLen:$fileLen,,,blockSize:$blockSize,,,requestId:$requestId")
            var offSet = 0L
            var successNum = 0
            var isSendResult = true
            for (i in 0 until blockFileNum) {
                val startOffset = i * blockSize
                val blockFileLen = Math.min(blockSize, fileLen - startOffset)
                val fileData = getFileData(filePath, offSet, blockFileLen.toInt())
                // 创建文件名请求体
                val requestBody = RequestBody.create(null, fileData)
                val call = RetrofitClient.getUploadFileService(
                    token,
                    requestId,
                    offSet.toString(),
                    uploadType
                ).uploadFile(file.name, requestBody)
                XLogUtil.d("${TAG}upload 第${i + 1}块 block file,offSet:$offSet,,,blockFileLen:$blockFileLen,,,blockFileNum:$blockFileNum,,,fileLen:$fileLen,,,filePath:$filePath,,,fileData size:${fileData?.size},,,requestId:$requestId")
                offSet += blockFileLen
                call.enqueue(object : Callback<ResponseBody?> {
                    override fun onResponse(
                        call: Call<ResponseBody?>,
                        response: Response<ResponseBody?>
                    ) {
                        val code = response.code()
                        XLogUtil.d("${TAG}upload 第${i + 1}块 block file result code:$code,,,requestId:$requestId")
                        if (code == 201) {
                            //处理成功响应
                            successNum++
                            if (successNum == blockFileNum) {
                                XLogUtil.d("${TAG}upload all block file success,blockFileNum:$blockFileNum,,,requestId:$requestId")
                                listener?.apply {
                                    onSuccess(Constant.SUCCESS, requestId)
                                }
                                //上传完
                            }
                        } else {
                            //处理失败响应
                        }
                    }
                    override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
                        // 处理请求失败
                        XLogUtil.d("${TAG}upload 第${i + 1}块 block file onFailure message:${t.printStackTrace()}")
                    }
                })
            }
    /**
     * 根据偏移量获取分块文件数据
     */
    fun getFileData(filePath: String, offset: Long, length: Int): ByteArray? {
        // 使用RandomAccessFile随机访问文件
        var randomAccessFile: RandomAccessFile? = null
        try {
            randomAccessFile = RandomAccessFile(filePath, "r")
            val fileChannel = randomAccessFile.channel
            // 将文件的部分区域映射为内存区域
            val mappedByteBuffer =
                fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, length.toLong())
            val data = ByteArray(length)
            // 从映射区域中读取数据
            mappedByteBuffer[data]
            return data
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        } finally {
            if (randomAccessFile != null) {
                try {
                    randomAccessFile.close()
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
    }

2.2、分段下载关键代码

                val folder = File(DOWNLOAD_FOLDER_PATH)
                            if (!folder.exists()) {
                                folder.mkdirs()
                            }
                            val fileName = getFileNameWithPath(filePath)
                            val downFile = File(DOWNLOAD_FOLDER_PATH + File.separator + fileName)
                            if (!downFile.exists()) {
                                downFile.createNewFile()
                            }
                            // 使用输入流保存响应体到文件,这里通常是通过http请求,返回的文件流,替换即可
                            val inputStream = body.byteStream()
                            val rw = RandomAccessFile(downFile, "rw")
                            rw.seek(startPosition)//文件写入的初始位置
                            var hasReads = 0
                            var readLenght: Long = 0
                            val bytes = ByteArray(4096)
                            while ((inputStream.read(bytes).also { hasReads = it }) > 0) {
                                rw.write(bytes, 0, hasReads)
                                readLenght += hasReads
                                // val l = (readLenght * 100 / contentLength) as Int 单块文件写入进度
                            }
                            // 关闭文件输出流和输入流
                            inputStream.close()
                            rw.close()
    /**
     * 根据文件路径获取文件名
     */
    fun getFileNameWithPath(path: String): String {
        if (TextUtils.isEmpty(path)) {
            return ""
        }
        val start = path.lastIndexOf("/")
        return if (start != -1) {
            path.substring(start + 1)
        } else {
            "DEFAULT_NAME"
        }
    }

到此这篇关于Android 文件分段上传和下载实现方案的文章就介绍到这了,更多相关Android 文件分段上传和下载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论