Retrofit2-如何從服務器下載文件

在這篇博客中,將會講述使用Retrofit十分需要的一個功能:怎么去下載文件,下面會展示一些下載文件需要寫的代碼片段,從小的 png 圖片到大的 zip文件。

原文地址

Retrofit 2 — How to Download Files from Server

怎么指定一個Retrofit請求

如果你剛剛開始閱讀這篇文章并且以前沒有寫過任何關于Retrofit請求的代碼,可以先看一下前面翻譯的Retrofit系列的文章。對于已經用過的人來說,下載文件的請求和其他的請求看起來是差不多的。

// option 1: a resource relative to your base URL
@GET("/resource/example.zip")
Call<ResponseBody> downloadFileWithFixedUrl();

// option 2: using a dynamic URL
@GET
Call<ResponseBody> downloadFileWithDynamicUrlSync(@Url String fileUrl);  

如果你想下載的文件資源是靜態的(資源一直在同一個地址)并且和你的base URL相關聯,可以使用第一種方式。正如你所看到的,這看起來就像是Retrofit 2的一般的請求,值得注意的是,我們指定了 ResponseBody 來作為返回的類型。你不應該使用任何其他的類型,否則Retrofit會嘗試去解析和映射轉換,在下載任務的時候這些操作都是沒有意義的。
第二種方式是Retrofit 2新加的,你現在可以輕松的通過一個動態的URL作為請求的參數,這個特性在下載文件的時候是格外有用的,因為一般url都是根據參數,使用者,和時間所決定的,你可以動態的去構建完整的請求URL。如果你還沒有使用過動態URL,可以回頭去看我已經翻譯的另一篇文章:Retrofit2-如何在請求時使用動態URL。
你可以自己選擇使用哪一種方法,然后我們接著看下一步。

怎么調用這個請求方法

當我們聲明了一個方法后,我們需要去調用它,代碼如下:

FileDownloadService downloadService = ServiceGenerator.create(FileDownloadService.class);

Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);

call.enqueue(new Callback<ResponseBody>() {  
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        if (response.isSuccess()) {
            Log.d(TAG, "server contacted and has file");

            boolean writtenToDisk = writeResponseBodyToDisk(response.body());

            Log.d(TAG, "file download was a success? " + writtenToDisk);
        } else {
            Log.d(TAG, "server contact failed");
        }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        Log.e(TAG, "error");
    }
});

如果你對 ServiceGenerator.create() 感到迷惑,可以回頭看我翻譯的系列第一篇文章 Retrofit-開始并創建一個Android客戶端,一旦我們創建了這個service,就可以像其他請求一樣來調用這個請求,當然這只是第一步,真正重要的功能是 writeResponseBodyToDisk() 將返回的 ResponseBoby寫到磁盤中。

怎么保存文件

這個 writeResponseBodyToDisk() 方法拿到了 ResponseBody 對象,然后從里面讀流并寫到磁盤中,這個代碼只是看起來比較復雜。

private boolean writeResponseBodyToDisk(ResponseBody body) {  
    try {
        // todo change the file location/name according to your needs
        File futureStudioIconFile = new File(getExternalFilesDir(null) + File.separator + "Future Studio Icon.png");

        InputStream inputStream = null;
        OutputStream outputStream = null;

        try {
            byte[] fileReader = new byte[4096];

            long fileSize = body.contentLength();
            long fileSizeDownloaded = 0;

            inputStream = body.byteStream();
            outputStream = new FileOutputStream(futureStudioIconFile);

            while (true) {
                int read = inputStream.read(fileReader);

                if (read == -1) {
                    break;
                }

                outputStream.write(fileReader, 0, read);

                fileSizeDownloaded += read;

                Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
            }

            outputStream.flush();

            return true;
        } catch (IOException e) {
            return false;
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }

            if (outputStream != null) {
                outputStream.close();
            }
        }
    } catch (IOException e) {
        return false;
    }
}

大部分的代碼只是 Java I/O的模板代碼,你可能需要去自己改變第一行文件保存的地址和文件保存的名字。當你做完這些后,已經算是準備好了使用Retrofit來下載文件,但是,仍然還有一些事情沒有解決,比如一個主要的問題:默認情況下,Retrofit會將服務器的返回全部放到內存之中,這個操作在返回JSON或者XML時是OK的,因為這些都比較小,但是大文件就非常容易導致 Out-of-Memory-Errors。
如果你的應用需要下載一些大的文件,我們強烈建議閱讀下面這一段。

謹防大文件:使用 @Streaming

如果你在下載一個大的文件,Retrofit默認會將整個文件移到內存中,為了避免這種情況,我們需要為這種請求加一個特殊的注解:

@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);  

加入這個 @Streaming 聲明后并不是將整個文件全部放入內存中,而是實時的返回字節碼。值得注意的是,如果你在添加了 @Streaming聲明的情況下依然使用上面的方式來進行下載,Android就會拋出一個溢出 android.os.NetworkOnMainThreadException。
所以,最后一步就是把請求包裝到一個另外的線程中,比如使用 AsyncTask

final FileDownloadService downloadService =  
                ServiceGenerator.create(FileDownloadService.class);

new AsyncTask<Void, Long, Void>() {  
   @Override
   protected Void doInBackground(Void... voids) {
       Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);
       call.enqueue(new Callback<ResponseBody>() {
           @Override
           public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
               if (response.isSuccess()) {
                   Log.d(TAG, "server contacted and has file");

                   boolean writtenToDisk = writeResponseBodyToDisk(response.body());

                   Log.d(TAG, "file download was a success? " + writtenToDisk);
               }
               else {
                   Log.d(TAG, "server contact failed");
               }
           }
       return null;
   }
}.execute(); 

如果你記住了一個 @Streaming 聲明和上面這一段,你就能用Retrofit來有效的下載大文件而不是出現內存溢出的問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,728評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • 原文鏈接: Retrofit 2 — How to Download Files from Server原文出自:...
    小鄧子閱讀 48,621評論 19 87
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,733評論 18 399
  • 沿墻布置,任意布置,平面布線自由連接,弧形連接
    當陽英雄閱讀 169評論 2 0