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

RxJava加Retrofit文件分段上傳實現(xiàn)詳解

 更新時間:2023年01月03日 09:17:05   作者:Chavin  
這篇文章主要為大家介紹了RxJava加Retrofit文件分段上傳實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

本文基于 RxJava 和 Retrofit 庫,設(shè)計并實現(xiàn)了一種用于大文件分塊上傳的工具,并對其進行了全面的拆解分析。拋磚引玉,對同樣有處理文件分塊上傳訴求的讀者,可能會起到一定的啟發(fā)作用。

文章主體由四部分構(gòu)成:

  • 首先分析問題,問題拆解為:多線程分段讀取文件、構(gòu)建和發(fā)出文件片段上傳請求
  • 基于 JDK 隨機讀取文件的類庫,設(shè)計本地多線程分段讀取文件的單元
  • 基于 Retrofit 設(shè)計由文件片段構(gòu)建上傳的網(wǎng)絡(luò)請求
  • 從上述設(shè)計演變而來的完整代碼實現(xiàn)

  另外,在文章提供的完整代碼中,還附了一段由 PHP 編寫,用來接收多線程分段數(shù)據(jù)的服務(wù)端接口實現(xiàn),其中處理了因客戶端都線程上傳片段,導致服務(wù)端接收的文件片段無序,故需在適當時機合并分塊構(gòu)成目標文件。

受限于筆者的開發(fā)經(jīng)驗與理論理解,文章的思路和代碼難免可能有偏頗,對于有改進和優(yōu)化的部分,歡迎大家討論區(qū)提出。

問題拆解

要完成文件分段上傳到服務(wù)端,第一步是分段讀取本地文件。通常分段是為了多線程同時執(zhí)行上傳,提高設(shè)備計算和網(wǎng)絡(luò)資源利用率,減少上傳時間優(yōu)化體驗,這樣即需要一個支持多線程的文件分段讀取工具。由于文件可能超過設(shè)備內(nèi)存大小,在讀取這類超大文件時需要控制最大讀取量防止內(nèi)存溢出。此時文件已從磁盤數(shù)據(jù)轉(zhuǎn)換為內(nèi)存中的字節(jié)數(shù)據(jù),只需要將這些內(nèi)存數(shù)據(jù)傳給服務(wù)端即可。這樣問題被分成 3 個子問題:

  • 分段讀取文件到內(nèi)存中
  • 控制多線程數(shù)量
  • 將文件片段傳給服務(wù)端

問題 1 很好解決,利用 Java 的 RandomAccessFile 可對文件的隨機讀取的特性,即可按需讀取文件片段到內(nèi)存中。

問題 2 相對復雜一點,但如果有閱讀過 JDK 中線程池源碼的讀者,就會發(fā)現(xiàn)這個問題的和控制線程池中線程數(shù)量其實是類似的。

問題 3 就不復雜了,Retrofit 基于 OKhttp ,OkHttp是很容易基于字節(jié)數(shù)組構(gòu)建 multipart/form-data 請求的。

分塊并發(fā)讀取文件

根據(jù)上述對問題 1、2 的拆解,可將讀取抽象為一個文件讀取器,構(gòu)建時傳入文件對象和分段大小以及最大并發(fā)數(shù),以及分段數(shù)據(jù)的回調(diào)。當外部啟動讀取時將根據(jù)文件大小和配置的分段大小構(gòu)建若干個 Task 用于讀取對應片段的數(shù)據(jù)。

public BlockReader(@NotNull File file, @NotNull BlockCallback callback, int poolSize, int blockSize) {
    mFile = file;
    mCallback = callback;
    mPoolSize = poolSize;
    mBlockSize = blockSize;
}
public void start(@Nullable BlockFilter filter) {
    Observable.empty().observeOn(Schedulers.computation()).doOnComplete(() -> {
        long length = mFile.length();
        for (long offset = 0; offset < length; offset += mBlockSize) {
            if (null != filter && filter.ignore(offset)) {
                continue;
            }
            mQueue.offer(new ReadTask(offset));
        }
        for (int i = 0; i < Math.min(mPoolSize, mQueue.size()); i++) {
            Observable.empty().observeOn(Schedulers.io()).doOnComplete(this::schedule).subscribe();
        }
    }).subscribe();
}

多線程調(diào)度部分,可通過加鎖和記錄狀態(tài)變量統(tǒng)計當前正運行的線程數(shù),則可控制字節(jié)數(shù)組數(shù),這樣就相當于控制住了最大內(nèi)存占用。

private void schedule() {
    if (mRunning.get() >= mPoolSize) {
        return;
    }
    ReadTask task;
    synchronized (mQueue) {
        if (mRunning.get() >= mPoolSize) {
            return;
        }
        task = mQueue.poll();
        if (null != task) {
            mRunning.incrementAndGet();
        }
    }
    if (null != task) {
        task.run();
    }
}

最后是文件隨機讀取,直接調(diào)用 RandomAccessFile 的 API 即可:

private class ReadTask implements Action {
    @Override
    public void run() {
        try (RandomAccessFile raf = new RandomAccessFile(mFile, RAF_MODE);
                ByteArrayOutputStream out = new ByteArrayOutputStream(mBlockSize)) {
            raf.seek(mOffset);
            byte[] buf = new byte[DEF_BLOCK_SIZE];
            long cnt = 0;
            for (int bytes = raf.read(buf); bytes != -1 && cnt < mBlockSize; bytes = raf.read(buf)) {
                out.write(buf, 0, bytes);
                cnt += bytes;
            }
            out.flush();
            mCallback.onFinished(mOffset, out.toByteArray());
        } catch (IOException e) {
            mCallback.onFinished(mOffset, null);
        } finally {
            mRunning.decrementAndGet();
            schedule();
        }
    }
}

文件片段上傳

上傳部分則使用 Retrofit 提供的注解和 OKHttp 的類庫構(gòu)建請求。但值得一提的是需要在磁盤IO線程同步完成網(wǎng)絡(luò)IO,這樣可以避免網(wǎng)絡(luò)IO速度落后磁盤IO太多而導致任務(wù)堆積造成內(nèi)存溢出。

public interface BlockUploader {
    @POST("test/upload.php")
    @Multipart
    Single<Response<ResponseBody>> upload(@Header("filename") String filename,
                                          @Header("total") long total,
                                          @Header("offset") long offset,
                                          @Part List<MultipartBody.Part> body);
}
private static void syncUpload(String fileName, long fileLength, long offset, byte[] bytes) {
    RequestBody data = RequestBody.create(MediaType.parse("application/octet-stream"), bytes);
    MultipartBody body = new MultipartBody.Builder()
            .addFormDataPart("file", fileName, data)
            .setType(MultipartBody.FORM)
            .build();
    retrofit.create(BlockUploader.class).upload(fileName, fileLength, offset, body.parts()).subscribe(resp -> {
        if (resp.isSuccessful()) {
            System.out.println("? offset: " + offset + " upload succeed " + resp.code());
        } else {
            System.out.println("? offset: " + offset + " upload failed " + resp.code());
        }
    }, throwable -> {
        System.out.println("! offset: " + offset + " upload failed");
    });
}

完整代碼

為控制篇幅,完整代碼請移步 Github,服務(wù)端部分處理形如:

以上就是RxJava加Retrofit文件分段上傳示例的詳細內(nèi)容,更多關(guān)于RxJava Retrofit文件上傳的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 淺談Android設(shè)計模式學習之觀察者模式

    淺談Android設(shè)計模式學習之觀察者模式

    觀察者模式在實際項目中使用的也是非常頻繁的,它最常用的地方是GUI系統(tǒng)、訂閱——發(fā)布系統(tǒng)等。這篇文章主要介紹了淺談Android設(shè)計模式學習之觀察者模式,感興趣的小伙伴們可以參考一下
    2018-05-05
  • android中DownloadManager實現(xiàn)版本更新,監(jiān)聽下載進度實例

    android中DownloadManager實現(xiàn)版本更新,監(jiān)聽下載進度實例

    本篇文章主要介紹了android中DownloadManager實現(xiàn)版本更新,監(jiān)聽下載進度實例。具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-03-03
  • kotlin android extensions 插件實現(xiàn)示例詳解

    kotlin android extensions 插件實現(xiàn)示例詳解

    這篇文章主要為大家介紹了kotlin android extensions 插件實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • Android自定義實現(xiàn)BaseAdapter的優(yōu)化布局

    Android自定義實現(xiàn)BaseAdapter的優(yōu)化布局

    這篇文章主要為大家詳細介紹了Android自定義實現(xiàn)BaseAdapter的優(yōu)化布局,感興趣的小伙伴們可以參考一下
    2016-08-08
  • Android Glide常見使用方式講解

    Android Glide常見使用方式講解

    對于Glide這個加載圖片的框架,很多人都在用,我之前使用的是ImageLoader,最近查資料時,發(fā)現(xiàn)Glide才是Google推薦的加載圖片框架,功能非常強大,而且還有Google專人維護,要知道,ImageLoader已經(jīng)沒人維護了,除了問題可沒人解答。所以有必要整理一下Glide的使用
    2023-01-01
  • Linux命令行下編譯Android NDK的示例代碼

    Linux命令行下編譯Android NDK的示例代碼

    今天小編就為大家分享一篇關(guān)于Linux命令行下編譯Android NDK的示例代碼,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • Android中Webview打開網(wǎng)頁的同時發(fā)送HTTP頭信息方法

    Android中Webview打開網(wǎng)頁的同時發(fā)送HTTP頭信息方法

    這篇文章主要介紹了Android中Webview打開網(wǎng)頁的同時發(fā)送HTTP頭信息方法,本文是講解的是一種通過修改Referer來控制盜鏈的方法,需要的朋友可以參考下
    2015-01-01
  • Android使用Gridview單行橫向滾動顯示

    Android使用Gridview單行橫向滾動顯示

    這篇文章主要為大家詳細介紹了Android使用Gridview單行橫向滾動顯示,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-07-07
  • android編程實現(xiàn)添加文本內(nèi)容到sqlite表中的方法

    android編程實現(xiàn)添加文本內(nèi)容到sqlite表中的方法

    這篇文章主要介紹了android編程實現(xiàn)添加文本內(nèi)容到sqlite表中的方法,結(jié)合實例較為詳細的分析了Android針對txt文本文件的讀取及SQL數(shù)據(jù)庫操作的相關(guān)技巧,需要的朋友可以參考下
    2015-11-11
  • Android手機信號強度檢測詳細介紹

    Android手機信號強度檢測詳細介紹

    這篇文章主要介紹了Android手機信號強度檢測的相關(guān)資料,android定義了2種信號單位:dBm和asu。具體兩種的關(guān)系本文給大家介紹非常詳細,需要的朋友可以參考下
    2016-11-11

最新評論

长宁县| 安远县| 丹棱县| 中卫市| 盈江县| 定远县| 香港 | 丁青县| 文安县| 南雄市| 江山市| 尼木县| 公主岭市| 遵化市| 阳山县| 洛隆县| 大兴区| 涿鹿县| 凤凰县| 祥云县| 志丹县| 双江| 休宁县| 彰武县| 博客| 阿克苏市| 泽州县| 山丹县| 乐至县| 平远县| 赣州市| 永宁县| 卢氏县| 长兴县| 民乐县| 阳新县| 贺兰县| 济阳县| 阿合奇县| 陆丰市| 沽源县|