最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android實現(xiàn)大文件分塊上傳的完整方案

 更新時間:2025年06月09日 10:33:34   作者:時小雨  
這篇文章主要為大家詳細介紹了如何使用Android實現(xiàn)大文件分塊上傳功能,從而突破表單數(shù)據(jù)限制,文中的示例代碼講解詳細,需要的可以了解下

一、問題背景與核心思路

1.1 場景痛點

當(dāng) Android 客戶端需要上傳 500MB 的大文件到服務(wù)器,而服務(wù)器表單限制為 2MB 時,傳統(tǒng)的直接上傳方案將完全失效。此時需要設(shè)計一套分塊上傳機制,將大文件拆分為多個小塊,突破服務(wù)器限制。

1.2 核心思路

分塊上傳 + 服務(wù)端合并

  • 將文件切割為 ≤2MB 的塊
  • 逐塊上傳至服務(wù)器
  • 服務(wù)端接收后按順序合并

二、Android 客戶端實現(xiàn)細節(jié)

2.1 分塊處理與上傳流程

完整代碼實現(xiàn)(Kotlin)

// FileUploader.kt
object FileUploader {
    // 分塊大?。?.9MB 預(yù)留安全空間)
    private const val CHUNK_SIZE = 1.9 * 1024 * 1024 

    suspend fun uploadLargeFile(context: Context, file: File) {
        val fileId = generateFileId(file) // 生成唯一文件標(biāo)識
        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
    }
}

關(guān)鍵技術(shù)點解析

1.唯一文件標(biāo)識生成:通過文件內(nèi)容哈希(如 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()
}

2.2 網(wǎng)絡(luò)請求實現(xiàn)(Retrofit + Kotlin Coroutine)

// 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()}")
    }
}

三、服務(wù)端實現(xiàn)(Spring Boot 示例)

3.1 接收分塊接口

@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) {
        
        // 創(chuàng)建臨時目錄
        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");
        }
        
        // 如果是最后一塊則觸發(fā)合并
        if (isLast) {
            asyncMergeFile(fileId, totalChunks);
        }
        
        return ResponseEntity.ok().build();
    }
    
    @Async
    public void asyncMergeFile(String fileId, int totalChunks) {
        // 實現(xiàn)合并邏輯
    }
}

3.2 合并文件實現(xiàn)

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());
}

四、技術(shù)對比與方案選擇

方案優(yōu)點缺點適用場景
傳統(tǒng)表單上傳實現(xiàn)簡單受限于服務(wù)器大小限制小文件上傳(<2MB)
分塊上傳突破大小限制,支持?jǐn)帱c續(xù)傳實現(xiàn)復(fù)雜度較高大文件上傳(>100MB)
第三方云存儲SDK無需自行實現(xiàn),功能完善依賴第三方服務(wù),可能有費用產(chǎn)生需要快速集成云存儲的場景

五、關(guān)鍵實現(xiàn)步驟總結(jié)

1.客戶端分塊切割

  • 確定分塊大?。ńㄗh略小于限制值)
  • 生成唯一文件ID(基于文件內(nèi)容哈希)
  • 實現(xiàn)可恢復(fù)的上傳進度記錄

2.分塊上傳

  • 使用多部分表單上傳每個分塊
  • 攜帶分塊元數(shù)據(jù)(序號/總數(shù)/文件ID)
  • 實現(xiàn)超時重試機制

3.服務(wù)端處理

  • 按文件ID創(chuàng)建臨時存儲目錄
  • 驗證分塊完整性(可選MD5校驗)
  • 原子性合并操作

4.可靠性增強

  • 斷點續(xù)傳支持
  • 網(wǎng)絡(luò)異常自動重試
  • 上傳完整性校驗

六、注意事項與優(yōu)化建議

1.分塊大小優(yōu)化

  • 建議設(shè)置為 服務(wù)器限制值 * 0.95(如 1.9MB)
  • 測試不同分塊大小對傳輸效率的影響

2.并發(fā)控制

  • 可并行上傳多個分塊(需服務(wù)端支持)
  • 合理控制并發(fā)數(shù)(建議 3-5 個并行)

3.安全防護

  • 添加身份驗證(JWT Token)
  • 限制單個文件的最大分塊數(shù)
  • 使用 HTTPS 加密傳輸

4.服務(wù)端優(yōu)化

  • 設(shè)置合理的臨時文件清理策略
  • 使用異步合并操作避免阻塞請求線程
  • 實現(xiàn)分塊哈希校驗(示例代碼見下方)

分塊校驗示例(服務(wù)端)

// 計算分塊MD5
String receivedHash = DigestUtils.md5Hex(chunk.getInputStream());
if (!receivedHash.equals(clientProvidedHash)) {
    throw new InvalidChunkException("Chunk hash mismatch");
}

七、擴展方案:第三方云存儲集成

對于不想自行實現(xiàn)分塊上傳的場景,可考慮以下方案:

阿里云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());

八、關(guān)鍵點總結(jié)

  • 分塊策略:合理設(shè)置分塊大小,生成唯一文件標(biāo)識
  • 斷點續(xù)傳:本地持久化上傳進度,支持網(wǎng)絡(luò)恢復(fù)
  • 完整性校驗:客戶端與服務(wù)端雙端校驗分塊數(shù)據(jù)
  • 并發(fā)控制:平衡并行上傳數(shù)量與服務(wù)器壓力
  • 錯誤處理:實現(xiàn)自動重試與異常上報機制
  • 安全防護:身份驗證 + 傳輸加密 + 大小限制

到此這篇關(guān)于Android實現(xiàn)大文件分塊上傳的完整方案的文章就介紹到這了,更多相關(guān)Android大文件分塊上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • android原生JSON解析實例

    android原生JSON解析實例

    通過實例給大家詳細分析一下關(guān)于android開發(fā)原生JSON解析的問題。
    2017-11-11
  • Retrofit網(wǎng)絡(luò)請求和響應(yīng)處理重點分析講解

    Retrofit網(wǎng)絡(luò)請求和響應(yīng)處理重點分析講解

    這篇文章主要介紹了Retrofit網(wǎng)絡(luò)請求和響應(yīng)處理重點分析,在使用?Retrofit發(fā)起網(wǎng)絡(luò)請求時,我們可以通過定義一個接口并使用Retrofit的注解來描述這個接口中的請求,Retrofit會自動生成一個實現(xiàn)該接口的代理對象
    2023-03-03
  • Android?ContentObserver?監(jiān)聽短信思路詳解

    Android?ContentObserver?監(jiān)聽短信思路詳解

    ContentObserver允許在Android中監(jiān)控特定數(shù)據(jù)的變化,可用于短信等應(yīng)用的數(shù)據(jù)監(jiān)聽,開發(fā)者可通過繼承ContentObserver并實現(xiàn)onChange方法來定義當(dāng)目標(biāo)內(nèi)容變化時的響應(yīng)行為,感興趣的朋友一起看看吧
    2024-09-09
  • Android實現(xiàn)簡易的音樂播放器

    Android實現(xiàn)簡易的音樂播放器

    這篇文章主要為大家詳細介紹了Android實現(xiàn)簡易的音樂播放器,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • 獲取Android界面性能數(shù)據(jù)的快捷方法

    獲取Android界面性能數(shù)據(jù)的快捷方法

    這篇文章主要介紹了獲取Android界面性能數(shù)據(jù)的快捷方法,幫助大家更好的理解和學(xué)習(xí)使用Android開發(fā),感興趣的朋友可以了解下
    2021-04-04
  • Android使用ContentProvider實現(xiàn)跨進程通訊示例詳解

    Android使用ContentProvider實現(xiàn)跨進程通訊示例詳解

    這篇文章主要為大家介紹了Android使用ContentProvider實現(xiàn)跨進程通訊示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • Android編程實現(xiàn)獲得內(nèi)存剩余大小與總大小的方法

    Android編程實現(xiàn)獲得內(nèi)存剩余大小與總大小的方法

    這篇文章主要介紹了Android編程實現(xiàn)獲得內(nèi)存剩余大小與總大小的方法,涉及Android基于ActivityManager實現(xiàn)內(nèi)存信息的操作技巧,需要的朋友可以參考下
    2015-12-12
  • RxJava2.x實現(xiàn)定時器的實例代碼

    RxJava2.x實現(xiàn)定時器的實例代碼

    本篇文章主要介紹了RxJava2.x實現(xiàn)定時器,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Android啟動頁設(shè)置及動態(tài)權(quán)限跳轉(zhuǎn)問題解決

    Android啟動頁設(shè)置及動態(tài)權(quán)限跳轉(zhuǎn)問題解決

    在我遇到這個實際問題之前,我一直認(rèn)為啟動頁的作用是美化產(chǎn)品,提升軟件逼格。但實際上,它更重要的是起到了一個攔截器的作用,這篇文章主要介紹了Android啟動頁設(shè)置以及動態(tài)權(quán)限跳轉(zhuǎn),需要的朋友可以參考下
    2022-04-04
  • 詳解Flutter掃碼識別二維碼內(nèi)容

    詳解Flutter掃碼識別二維碼內(nèi)容

    這篇文章主要介紹了Flutter掃碼識別二維碼內(nèi)容的相關(guān)知識,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11

最新評論

庄浪县| 三门县| 太湖县| 新宁县| 沙洋县| 东光县| 中牟县| 眉山市| 枣强县| 宜春市| 宝兴县| 西吉县| 马边| 普兰店市| 潼南县| 阿瓦提县| 博兴县| 那坡县| 遵化市| 巴东县| 余庆县| 贵港市| 红河县| 江川县| 那坡县| 寿阳县| 靖州| 卢龙县| 祁东县| 英山县| 沽源县| 乌恰县| 太白县| 大新县| 北川| 佛坪县| 东辽县| 科尔| 贡嘎县| 永吉县| 郯城县|