【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之文件下載(二)

前言

上篇中我們介紹了基于MVP的Retrofit2+RXjava封裝,還沒有看的點擊這里劝贸,這一篇我們來說說文件下載的實現(xiàn)。

我們先在ApiServer定義好調(diào)用的接口

 @GET
    Observable<ResponseBody> downloadFile(@Url String fileUrl);

接著定義一個接口,下載成功后用來回調(diào)

public interface FileView extends BaseView {

    void onSuccess(File file);
}

接著是Observer找都,建議與處理普通接口的Observer區(qū)分處理

public abstract class FileObsever extends BaseObserver<ResponseBody> {
    private String path;

    public FileObsever(BaseView view, String path) {
        super(view);
        this.path = path;
    }

    @Override
    protected void onStart() {
    }

    @Override
    public void onComplete() {
    }

    @Override
    public void onSuccess(ResponseBody o) {

    }

    @Override
    public void onError(String msg) {

    }

    @Override
    public void onNext(ResponseBody o) {
        File file = FileUtil.saveFile(path, o);
        if (file != null && file.exists()) {
            onSuccess(file);
        } else {
            onErrorMsg("file is null or file not exists");
        }
    }

    @Override
    public void onError(Throwable e) {
        onErrorMsg(e.toString());
    }


    public abstract void onSuccess(File file);

    public abstract void onErrorMsg(String msg);
}

FileUtil
注:如果需要寫入文件的進度,可以在將這段方法放在onNext中,在FileObsever這個類寫個方法彤枢,然后回調(diào)荐操。

public static File saveFile(String filePath, ResponseBody body) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        File file = null;
        try {
            if (filePath == null) {
                return null;
            }
            file = new File(filePath);
            if (file == null || !file.exists()) {
                file.createNewFile();
            }


            long fileSize = body.contentLength();
            long fileSizeDownloaded = 0;
            byte[] fileReader = new byte[4096];

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

            while (true) {
                int read = inputStream.read(fileReader);
                if (read == -1) {
                    break;
                }
                outputStream.write(fileReader, 0, read);
                fileSizeDownloaded += read;

            }

            outputStream.flush();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return file;
    }

下來是FilePresenter

public class FilePresenter extends BasePresenter<FileView> {
    public FilePresenter(FileView baseView) {
        super(baseView);
    }

    public void downFile(String url, final String path) {

        addDisposable(apiServer.downloadFile(url), new FileObsever(baseView, path) {


            @Override
            public void onSuccess(File file) {
                if (file != null && file.exists()) {
                    baseView.onSuccess(file);
                } else {
                    baseView.showError("file is null");
                }
            }

            @Override
            public void onErrorMsg(String msg) {
                baseView.showError(msg);

            }
        });

    }
    }

最后在Activity中調(diào)用

private void downFile() {
        String url = "http://download.sdk.mob.com/apkbus.apk";

        String state = Environment.getExternalStorageState();
        if (state.equals(Environment.MEDIA_MOUNTED)) {// 檢查是否有存儲卡
            dir = Environment.getExternalStorageDirectory() + "/ceshi/";
            File dirFile = new File(dir);
            if (!dirFile.exists()) {
                dirFile.mkdirs();
            }
        }
        presenter.downFile(url, dir + "app-debug.apk");
    }

就在我以為萬事大吉的時候,APP崩潰了委可,錯誤信息如下:
[圖片上傳失敗...(image-39e2a7-1532052138950)]

原來是加入日志監(jiān)聽器,會導致每次都把整個文件加載到內(nèi)存,那我們就去掉這個

修改FilePresenter#downFile如下:

 public void downFile(String url, final String path) {

        OkHttpClient client = new OkHttpClient.Builder().build();
        Retrofit retrofit = new Retrofit.Builder().client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl("https://wawa-api.vchangyi.com/").build();

        apiServer = retrofit.create(ApiServer.class);

        addDisposable(apiServer.downloadFile(url), new FileObsever(baseView, path) {
            @Override
            public void onSuccess(File file) {
                if (file != null && file.exists()) {
                    baseView.onSuccess(file);
                } else {
                    baseView.showError("file is null");
                }
            }

            @Override
            public void onErrorMsg(String msg) {
                baseView.showError(msg);

            }
        });
    }

這次倒是下載成功了险领,不過官方建議10M以上的文件用Streaming標簽,我們加上Streaming標簽試試
修改ApiServer

 @Streaming
    @GET
    /**
     * 大文件官方建議用 @Streaming 來進行注解秒紧,不然會出現(xiàn)IO異常绢陌,小文件可以忽略不注入
     */
    Observable<ResponseBody> downloadFile(@Url String fileUrl);

這次又崩潰了,錯誤信息如下:
[圖片上傳失敗...(image-fc8c28-1532052138951)]

這是怎么回事熔恢,我們網(wǎng)絡(luò)請求是在子線程啊脐湾。無奈之下只得翻翻官方文檔,原來使用該注解表示響應用字節(jié)流的形式返回.如果沒使用該注解,默認會把數(shù)據(jù)全部載入到內(nèi)存中叙淌。我們可以在主線程中處理寫入文件(不建議)秤掌,但不能在主線程中處理字節(jié)流。所以鹰霍,我們需要將處理字節(jié)流机杜、寫入文件都放在子線程中。

于是衅谷,修改FilePresenter#downFile如下:

 OkHttpClient client = new OkHttpClient.Builder().build();
        Retrofit retrofit = new Retrofit.Builder().client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl("https://wawa-api.vchangyi.com/").build();

        apiServer = retrofit.create(ApiServer.class);
          apiServer
                .downloadFile(url)
                .map(new Function<ResponseBody, String>() {
                    @Override
                    public String apply(ResponseBody body) throws Exception {
                        File file = FileUtil.saveFile(path, body);
                        return file.getPath();
                    }
                }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(new FileObserver(baseView) {
                    @Override
                    public void onSuccess(File file) {
                        baseView.onSuccess(file);
                    }

                    @Override
                    public void onError(String msg) {
                        baseView.showError(msg);
                    }
                });

這樣椒拗,下載文件算是完成了,好像還缺點什么?對蚀苛,缺個下載進度在验,還記得攔截器嗎,我們可以從這里入手:

public class ProgressResponseBody extends ResponseBody {
    private ResponseBody responseBody;
    private BufferedSource bufferedSource;
    private ProgressListener progressListener;

    public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
        this.responseBody = responseBody;
        this.progressListener = progressListener;
    }


    @Nullable
    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }

    private Source source(Source source) {
        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                totalBytesRead += bytesRead;
                progressListener.onProgress(responseBody.contentLength(), totalBytesRead);
                return bytesRead;
            }
        };
    }

    public interface ProgressListener {
        void onProgress(long totalSize, long downSize);
    }
}

在BaseView 中定義接口堵未,個人建議放在BaseView 中腋舌,在BaseActivity中實現(xiàn)BaseView,方便復用

 /**
     * 下載進度
     *
     * @param totalSize
     * @param downSize
     */

    void onProgress(long totalSize, long downSize);

再次修改FilePresenter#downFile如下:

   public void downFile(final String url, final String path) {


        OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Response response = chain.proceed(chain.request());
                        return response.newBuilder().body(new ProgressResponseBody(response.body(),
                                new ProgressResponseBody.ProgressListener() {
                                    @Override
                                    public void onProgress(long totalSize, long downSize) {
                                        baseView.onProgress(totalSize, downSize);
                                    }
                                })).build();
                    }
                }).build();

        Retrofit retrofit = new Retrofit.Builder().client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl("https://wawa-api.vchangyi.com/").build();

        apiServer = retrofit.create(ApiServer.class);

        apiServer
                .downloadFile(url)
                .map(new Function<ResponseBody, String>() {
                    @Override
                    public String apply(ResponseBody body) throws Exception {
                        File file = FileUtil.saveFile(path, body);
                        return file.getPath();
                    }
                }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(new FileObserver(baseView) {
                    @Override
                    public void onSuccess(File file) {
                        baseView.onSuccess(file);
                    }

                    @Override
                    public void onError(String msg) {
                        baseView.showError(msg);
                    }
                });


    }

至此渗蟹,使用Retrofit下載文件暫時告一段落块饺。

項目源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市雌芽,隨后出現(xiàn)的幾起案子授艰,更是在濱河造成了極大的恐慌,老刑警劉巖世落,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淮腾,死亡現(xiàn)場離奇詭異,居然都是意外死亡屉佳,警方通過查閱死者的電腦和手機谷朝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來武花,“玉大人圆凰,你說我怎么就攤上這事√寤” “怎么了专钉?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長干旁。 經(jīng)常有香客問我驶沼,道長炮沐,這世上最難降的妖魔是什么争群? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮大年,結(jié)果婚禮上换薄,老公的妹妹穿的比我還像新娘。我一直安慰自己翔试,他們只是感情好轻要,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垦缅,像睡著了一般冲泥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天凡恍,我揣著相機與錄音志秃,去河邊找鬼。 笑死嚼酝,一個胖子當著我的面吹牛浮还,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闽巩,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼钧舌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涎跨?” 一聲冷哼從身側(cè)響起洼冻,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎六敬,沒想到半個月后碘赖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡外构,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年普泡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片审编。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡撼班,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出垒酬,到底是詐尸還是另有隱情砰嘁,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布勘究,位于F島的核電站矮湘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏口糕。R本人自食惡果不足惜缅阳,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望景描。 院中可真熱鬧十办,春花似錦、人聲如沸超棺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棠绘。三九已至件相,卻和暖如春再扭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背夜矗。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工霍衫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侯养。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓敦跌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逛揩。 傳聞我的和親對象是個殘疾皇子柠傍,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,947評論 2 89
  • JAVA面試題 1、作用域public,private,protected,以及不寫時的區(qū)別答:區(qū)別如下:作用域 ...
    JA尐白閱讀 1,160評論 1 0
  • 前言 最近有個新項目要做辩稽,搭建框架的時候惧笛,順便梳理了下MVP模式,特此記錄逞泄,歡迎大家指正患整。 【Android架構(gòu)】...
    歡子3824閱讀 6,573評論 17 44
  • 我叫X敦煌,這個名字伴隨我走過了25個年頭喷众。誠然各谚,如大家所見,它和敦煌莫高窟有同樣的兩個字到千,這恍惚間給了我...
    敦哉煌也閱讀 866評論 1 4
  • 早春昌渤,就好比新生的嬰兒,春分也就好比那第一聲啼哭憔四,向全世界宣布誕生膀息。 像往常一樣,走著去上學了赵,一撇頭潜支,看到...
    養(yǎng)樂多忒愛閱讀 259評論 1 1