前言
上篇中我們介紹了基于MVP的Retrofit2+RXjava封裝,還沒有看的點擊這里劝贸,這一篇我們來說說文件下載的實現(xiàn)。
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝(一)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之文件下載(二)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之文件上傳(三)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之常見問題(四)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之斷點下載(五)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之數(shù)據(jù)預處理(六)
- 【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之多Url(七)
我們先在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下載文件暫時告一段落块饺。