前言:最近有個斷點下載的需求,搗鼓了下,而後分享下java
關於文件下載,在第2篇中已經詳細說過,這裏就不在詳細說了,先對以前的下載作個封裝 首先是ApiServer
git
@Streaming
@GET
/** * 大文件官方建議用 @Streaming 來進行註解,否則會出現IO異常,小文件能夠忽略不注入 */
Observable<ResponseBody> downloadFile(@Url String fileUrl);
複製代碼
定義下載回調github
public interface DownFileCallback {
void onSuccess(String path);
void onFail(String msg);
void onProgress(long totalSize, long downSize);
}
複製代碼
DownLoadManager
api
public class DownLoadManager {
private static DownLoadManager loadManager;
private HashMap<String, FileObserver> hashMap;
private OkHttpClient client;
private Retrofit retrofit;
private ApiServer apiServer;
private DownFileCallback fileCallback;
public DownLoadManager() {
hashMap = new HashMap<>();
client = new OkHttpClient.Builder()
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response.newBuilder().body(new ProgressResponseBody(response.body(),
new ProgressResponseBody.ProgressListener() {
@Override
public void onProgress(long totalSize, long downSize) {
if (fileCallback != null) {
fileCallback.onProgress(totalSize, downSize);
}
}
})).build();
}
}).build();
retrofit = new Retrofit.Builder().client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("https://wawa-api.vchangyi.com/").build();
apiServer = retrofit.create(ApiServer.class);
}
public static DownLoadManager getInstance() {
synchronized (Object.class) {
if (loadManager == null) {
loadManager = new DownLoadManager();
}
}
return loadManager;
}
/** * 下載單個文件 * * @param url * @param fileCallback */
public void downFile(final String url, final DownFileCallback fileCallback) {
//若是正在下載,則暫停
if (isDownLoad(url)) {
pause(url);
return;
}
this.fileCallback = fileCallback;
//存儲的文件路徑
final String path = getTemporaryName(url);
FileObserver observer = 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<String>() {
@Override
public void onSuccess(String path) {
fileCallback.onSuccess(path);
hashMap.remove(url);
}
@Override
public void onError(String msg) {
fileCallback.onFail(msg);
hashMap.remove(url);
}
});
//保存
hashMap.put(url, observer);
}
/** * 暫停/取消任務 * * @param url 完整url */
public void pause(String url) {
if (hashMap.containsKey(url)) {
FileObserver observer = hashMap.get(url);
if (observer != null) {
observer.dispose();
hashMap.remove(url);
}
}
}
/** * 獲取臨時文件名 * * @param url * @return */
public static String getTemporaryName(String url) {
String type = "";
if (url.contains(".")) {
type = url.substring(url.lastIndexOf("."));
}
String dirName = Environment.getExternalStorageDirectory() + "/mvp/";
File f = new File(dirName);
//不存在建立
if (!f.exists()) {
f.mkdirs();
}
return dirName + System.currentTimeMillis() + type;
}
/** * 是否在下載 * * @param url * @return */
public boolean isDownLoad(String url) {
return hashMap.containsKey(url);
}
public abstract class FileObserver<T> extends DisposableObserver<T> {
@Override
public void onNext(T t) {
onSuccess(t);
}
@Override
public void onError(Throwable e) {
onError(e.getMessage());
}
@Override
public void onComplete() {
}
public abstract void onSuccess(T o);
public abstract void onError(String msg);
}
}
複製代碼
斷點下載的話,有2點要注意架構
RANGE
請求頭if (downSize != 0 && totalSize != 0) {
request = request.newBuilder()
.addHeader("RANGE", "bytes=" + downSize + "-" + totalSize).build();
}
複製代碼
downSize
和totalSize
,前者是下載的開始長度,後者是整個文件的長度,這些都須要咱們暫停時,本身作保存。app
RandomAccessFile
/** * @param filePath * @param start 起始位置 * @param body */
public static File saveFile(String filePath, long start, ResponseBody body) {
InputStream inputStream = null;
RandomAccessFile raf = null;
File file = null;
try {
file = new File(filePath);
raf = new RandomAccessFile(filePath, "rw");
inputStream = body.byteStream();
byte[] fileReader = new byte[4096];
//移動到該位置
raf.seek(start);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
raf.write(fileReader, 0, read);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return file;
}
複製代碼
建個實體類,來保存文件相關屬性,get和set就不貼了dom
public class DownModel {
private String url;
private String path;
private String title;
private String cover;
private long totalSize;
private long currentTotalSize;
private long downSize;
private boolean isExists;
private boolean isFinish;
private boolean isPause;
}
複製代碼
/** * 下載單個文件 * * @param downModel * @param */
public void downFile(final DownModel downModel, final DownFileCallback fileCallback) {
if (downModel == null) {
return;
}
//若是正在下載,則暫停
final String url = downModel.getUrl();
if (isDownLoad(url)) {
pause(url);
Log.e("cheng", "pause url=" + url);
return;
}
//當前連接
currentUrl = url;
//是不是斷點下載
if (downModel.getDownSize() != 0 && downModel.getTotalSize() != 0) {
totalSize = downModel.getTotalSize();
downSize = downModel.getDownSize();
currentPath = downModel.getPath();
} else {
totalSize = 0;
downSize = 0;
currentPath = getTemporaryName(url);
downModel.setPath(currentPath);
}
this.fileCallback = fileCallback;
Log.e("cheng", "currentUrl=" + currentUrl);
Log.e("cheng", "downSize=" + downSize + ",totalSize=" + totalSize + ",currentPath=" + currentPath);
FileObserver observer = apiServer.downloadFile(url).map(new Function<ResponseBody, String>() {
@Override
public String apply(ResponseBody body) throws Exception {
if (downModel.getDownSize() != 0 && downModel.getTotalSize() != 0) {
return FileUtil.saveFile(currentPath, downModel.getDownSize(), body).getPath();
}
File file = FileUtil.saveFile(currentPath, body);
return file.getPath();
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new FileObserver<String>() {
@Override
public void onSuccess(String path) {
downModel.setFinish(true);
downModel.setPath(path);
downModel.setExists(true);
if (fileCallback != null) {
fileCallback.onSuccess(path);
}
hashMap.remove(url);
currentUrl = null;
}
@Override
public void onError(String msg) {
if (fileCallback != null) {
fileCallback.onFail(msg);
}
hashMap.remove(url);
currentUrl = null;
}
});
//保存
hashMap.put(url, observer);
}
複製代碼
須要注意的是,若是文件總大小爲50M
,已下載的大小爲10M
,再次下載時onProgress
返回的totalSize
是文件總長度 減去 已下載大小 10M
, 即40M
,downSize
爲本次下載的已下載量ide
private void down2() {
String url = "http://download.sdk.mob.com/apkbus.apk";
if (downModel == null) {
downModel = new DownModel();
downModel.setUrl(url);
}
DownLoadManager.getInstance().downFile(downModel, new DownFileCallback() {
@Override
public void onSuccess(String path) {
showtoast("下載成功,path=" + path);
}
@Override
public void onFail(String msg) {
}
@Override
public void onProgress(long totalSize, long downSize) {
Log.e("cheng", "totalSize =" + totalSize + ",downSize=" + downSize);
if (downModel.getTotalSize() == 0) {
downModel.setTotalSize(totalSize);
}
downModel.setCurrentTotalSize(totalSize);
downModel.setDownSize(downSize + downModel.getTotalSize() - downModel.getCurrentTotalSize());
runOnUiThread(new Runnable() {
@Override
public void run() {
int progress = (int) (downModel.getDownSize() * 100 / downModel.getTotalSize());
tvDown.setText(progress + "%");
sbDown.setProgress(progress);
}
});
}
});
}
複製代碼
最後,獻上源碼 Githubpost