16人参与 • 2025-06-09 • Android
当 android 客户端需要上传 500mb 的大文件到服务器,而服务器表单限制为 2mb 时,传统的直接上传方案将完全失效。此时需要设计一套分块上传机制,将大文件拆分为多个小块,突破服务器限制。
分块上传 + 服务端合并:
完整代码实现(kotlin)
// fileuploader.kt object fileuploader { // 分块大小(1.9mb 预留安全空间) private const val chunk_size = 1.9 * 1024 * 1024 suspend fun uploadlargefile(context: context, file: file) { val fileid = generatefileid(file) // 生成唯一文件标识 val totalchunks = calculatetotalchunks(file) val uploadedchunks = loadprogress(context, fileid) // 加载已上传分块记录 fileinputstream(file).use { fis -> for (chunknumber in 0 until totalchunks) { if (uploadedchunks.contains(chunknumber)) continue val chunkdata = readchunk(fis, chunknumber) val islastchunk = chunknumber == totalchunks - 1 try { uploadchunk(fileid, chunknumber, totalchunks, chunkdata, islastchunk) saveprogress(context, fileid, chunknumber) // 记录成功上传的分块 } catch (e: exception) { handleretry(fileid, chunknumber) // 重试逻辑 } } } } private fun readchunk(fis: fileinputstream, chunknumber: int): bytearray { val skipbytes = chunknumber * chunk_size fis.channel().position(skipbytes.tolong()) val buffer = bytearray(chunk_size) val bytesread = fis.read(buffer) return if (bytesread < buffer.size) buffer.copyof(bytesread) else buffer } }
关键技术点解析
1.唯一文件标识生成:通过文件内容哈希(如 sha-256)确保唯一性
fun generatefileid(file: file): string { val digest = messagedigest.getinstance("sha-256") file.inputstream().use { is -> val buffer = bytearray(8192) var read: int while (is.read(buffer).also { read = it } > 0) { digest.update(buffer, 0, read) } } return digest.digest().tohex() }
2.进度持久化存储:使用 sharedpreferences 记录上传进度
private fun saveprogress(context: context, fileid: string, chunk: int) { val prefs = context.getsharedpreferences("upload_progress", mode_private) val key = "${fileid}_chunks" val existing = prefs.getstringset(key, mutablesetof()) ?: mutablesetof() prefs.edit().putstringset(key, existing + chunk.tostring()).apply() }
// uploadservice.kt interface uploadservice { @multipart @post("api/upload/chunk") suspend fun uploadchunk( @part("fileid") fileid: requestbody, @part("chunknumber") chunknumber: requestbody, @part("totalchunks") totalchunks: requestbody, @part("islast") islast: requestbody, @part chunk: multipartbody.part ): response<uploadresponse> } // 上传请求封装 private suspend fun uploadchunk( fileid: string, chunknumber: int, totalchunks: int, chunkdata: bytearray, islast: boolean ) { val service = retrofitclient.create(uploadservice::class.java) val requestfile = chunkdata.torequestbody("application/octet-stream".tomediatype()) val chunkpart = multipartbody.part.createformdata( "chunk", "chunk_${chunknumber}", requestfile ) val response = service.uploadchunk( fileid = fileid.torequestbody(), chunknumber = chunknumber.tostring().torequestbody(), totalchunks = totalchunks.tostring().torequestbody(), islast = islast.tostring().torequestbody(), chunk = chunkpart ) if (!response.issuccessful) { throw ioexception("upload failed: ${response.errorbody()?.string()}") } }
@restcontroller @requestmapping("/api/upload") public class uploadcontroller { @value("${upload.temp-dir:/tmp/uploads}") private string tempdir; @postmapping("/chunk") public responseentity<?> uploadchunk( @requestparam string fileid, @requestparam int chunknumber, @requestparam int totalchunks, @requestparam boolean islast, @requestpart("chunk") multipartfile chunk) { // 创建临时目录 path tempdirpath = paths.get(tempdir, fileid); if (!files.exists(tempdirpath)) { try { files.createdirectories(tempdirpath); } catch (ioexception e) { return responseentity.status(500).body("create dir failed"); } } // 保存分块 path chunkfile = tempdirpath.resolve("chunk_" + chunknumber); try { chunk.transferto(chunkfile); } catch (ioexception e) { return responseentity.status(500).body("save chunk failed"); } // 如果是最后一块则触发合并 if (islast) { asyncmergefile(fileid, totalchunks); } return responseentity.ok().build(); } @async public void asyncmergefile(string fileid, int totalchunks) { // 实现合并逻辑 } }
private void mergefile(string fileid, int totalchunks) throws ioexception { path tempdir = paths.get(this.tempdir, fileid); path outputfile = paths.get("/data/final", fileid + ".dat"); try (outputstream out = new bufferedoutputstream(files.newoutputstream(outputfile))) { for (int i = 0; i < totalchunks; i++) { path chunk = tempdir.resolve("chunk_" + i); files.copy(chunk, out); } out.flush(); } // 清理临时文件 fileutils.deletedirectory(tempdir.tofile()); }
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
传统表单上传 | 实现简单 | 受限于服务器大小限制 | 小文件上传(<2mb) |
分块上传 | 突破大小限制,支持断点续传 | 实现复杂度较高 | 大文件上传(>100mb) |
第三方云存储sdk | 无需自行实现,功能完善 | 依赖第三方服务,可能有费用产生 | 需要快速集成云存储的场景 |
1.客户端分块切割
2.分块上传
3.服务端处理
4.可靠性增强
1.分块大小优化
服务器限制值 * 0.95
(如 1.9mb)2.并发控制
3.安全防护
4.服务端优化
分块校验示例(服务端):
// 计算分块md5 string receivedhash = digestutils.md5hex(chunk.getinputstream()); if (!receivedhash.equals(clientprovidedhash)) { throw new invalidchunkexception("chunk hash mismatch"); }
对于不想自行实现分块上传的场景,可考虑以下方案:
阿里云oss分片上传
val oss = ossclient(context, endpoint, credentialprovider) val request = initiatemultipartuploadrequest(bucketname, objectkey) val uploadid = oss.initmultipartupload(request).uploadid // 上传分片 val partetags = mutablelistof<partetag>() for (i in chunks.indices) { val uploadpartrequest = uploadpartrequest( bucketname, objectkey, uploadid, i+1).apply { partcontent = chunks[i] } partetags.add(oss.uploadpart(uploadpartrequest).partetag) } // 完成上传 val completerequest = completemultipartuploadrequest( bucketname, objectkey, uploadid, partetags) oss.completemultipartupload(completerequest)
aws s3 transferutility
transferutility transferutility = transferutility.builder() .s3client(s3client) .context(context) .build(); multiplefileupload upload = transferutility.uploaddirectory( bucketname, remotedir, localdir, new objectmetadataprovider() { @override public void provideobjectmetadata(file file, objectmetadata metadata) { metadata.setcontenttype("application/octet-stream"); } }); upload.settransferlistener(new uploadlistener());
到此这篇关于android实现大文件分块上传的完整方案的文章就介绍到这了,更多相关android大文件分块上传内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论