一、背景
Android 中的大文件下载需要使用分段下载,下载通常是在线程中进行的,假如有5段,那同时5个线程去执行下载,请求http返回文件流后,需要将多个文件流同时写进同一个文件,这里用到
RandomAccessFile。
分段上传的话,只需要根据每段文件阀值,例如,50M为一段,将文件按照设置的阀值,分段上传即可
二、相关代码
2.1 分段上传关键代码
忽略网络请求和状态码,每个人接口定义的的请求参数和返回code不一样
private val DEFAULT_BLOCK_SIZE: Long = 50 * 1024 * 1024 //50MBval blockSize=DEFAULT_BLOCK_SIZEval 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 = 0Lvar successNum = 0var isSendResult = truefor (i in 0 until blockFileNum) {val startOffset = i * blockSizeval 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 += blockFileLencall.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? = nulltry {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 = 0var readLenght: Long = 0val 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"}}