android 下载多个文件怎么打开,Android OkHttp下载多个文件 暂停 继续下载

介绍

demo的主要逻辑是,利用okhttp 和 RxJava 在子线程中下载文件,通关观察者模式监听下载的进度,再回调到主线程中,然后利用EventBus 通知页面刷新,更新进度。

效果图

4bab2e9c577e

download.gif

step1 导入依赖库

// OKHttp RxJava

implementation 'com.squareup.okhttp3:okhttp:3.6.0'

implementation 'io.reactivex.rxjava2:rxjava:2.1.3'

implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'

// eventbus

implementation 'org.greenrobot:eventbus:3.0.0'

implementation 'com.android.support:recyclerview-v7:27.1.1'

step 2 定义下载bean

/**

* Created by zs

* Date:2018年 09月 12日

* Time:13:50

* —————————————————————————————————————

* About: 下载管理

* —————————————————————————————————————

*/

public class DownloadInfo {

/**

* 下载状态

*/

public static final String DOWNLOAD = "download";

public static final String DOWNLOAD_PAUSE = "pause";

public static final String DOWNLOAD_CANCEL = "cancel";

public static final String DOWNLOAD_OVER = "over";

public static final String DOWNLOAD_ERROR = "error";

public static final long TOTAL_ERROR = -1;//获取进度失败

private String url;

private String fileName;

private String downloadStatus;

private long total;

private long progress;

public DownloadInfo(String url) {

this.url = url;

}

public String getUrl() {

return url;

}

public String getFileName() {

return fileName;

}

public void setFileName(String fileName) {

this.fileName = fileName;

}

public long getTotal() {

return total;

}

public void setTotal(long total) {

this.total = total;

}

public long getProgress() {

return progress;

}

public void setProgress(long progress) {

this.progress = progress;

}

public String getDownloadStatus() {

return downloadStatus;

}

public void setDownloadStatus(String downloadStatus) {

this.downloadStatus = downloadStatus;

}

}

step 3 下载管理类 也是主要的内容 (DownloadManager)

/**

* Created by zs

* Date:2018年 09月 12日

* Time:13:56

* —————————————————————————————————————

* About: 下载管理

* —————————————————————————————————————

*/

public class DownloadManager {

private static final AtomicReference INSTANCE = new AtomicReference<>();

private OkHttpClient mClient;

private HashMap downCalls; //用来存放各个下载的请求

public static DownloadManager getInstance() {

for (; ; ) {

DownloadManager current = INSTANCE.get();

if (current != null) {

return current;

}

current = new DownloadManager();

if (INSTANCE.compareAndSet(null, current)) {

return current;

}

}

}

private DownloadManager() {

downCalls = new HashMap<>();

mClient = new OkHttpClient.Builder().build();

}

/**

* 查看是否在下载任务中

* @param url

* @return

*/

public boolean getDownloadUrl(String url){

return downCalls.containsKey(url);

}

/**

* 开始下载

*

* @param url 下载请求的网址

* @param downLoadObserver 用来回调的接口

*/

public void download(String url, DownloadObserver downLoadObserver) {

Observable.just(url)

.filter(new Predicate() {

@Override

public boolean test(String s) {

return !downCalls.containsKey(s);

}

}) // 过滤 call的map中已经有了,就证明正在下载,则这次不下载

.flatMap(new Function>() {

@Override

public ObservableSource> apply(String s) {

return Observable.just(createDownInfo(s));

}

}) // 生成 DownloadInfo

.map(new Function() {

@Override

public DownloadInfo apply(Object o) {

return getRealFileName((DownloadInfo)o);

}

}) // 如果已经下载,重新命名

.flatMap(new Function>() {

@Override

public ObservableSource apply(DownloadInfo downloadInfo) {

return Observable.create(new DownloadSubscribe(downloadInfo));

}

}) // 下载

.observeOn(AndroidSchedulers.mainThread()) // 在主线程中回调

.subscribeOn(Schedulers.io()) // 在子线程中执行

.subscribe(downLoadObserver); // 添加观察者,监听下载进度

}

/**

* 下载取消或者暂停

* @param url

*/

public void pauseDownload(String url) {

Call call = downCalls.get(url);

if (call != null) {

call.cancel();//取消

}

downCalls.remove(url);

}

/**

* 取消下载 删除本地文件

* @param info

*/

public void cancelDownload(DownloadInfo info){

pauseDownload(info.getUrl());

info.setProgress(0);

info.setDownloadStatus(DownloadInfo.DOWNLOAD_CANCEL);

EventBus.getDefault().post(info);

Constant.deleteFile(info.getFileName());

}

/**

* 创建DownInfo

*

* @param url 请求网址

* @return DownInfo

*/

private DownloadInfo createDownInfo(String url) {

DownloadInfo downloadInfo = new DownloadInfo(url);

long contentLength = getContentLength(url);//获得文件大小

downloadInfo.setTotal(contentLength);

String fileName = url.substring(url.lastIndexOf("/"));

downloadInfo.setFileName(fileName);

return downloadInfo;

}

/**

* 如果文件已下载重新命名新文件名

* @param downloadInfo

* @return

*/

private DownloadInfo getRealFileName(DownloadInfo downloadInfo) {

String fileName = downloadInfo.getFileName();

long downloadLength = 0, contentLength = downloadInfo.getTotal();

File path = new File(Constant.FILE_PATH);

if (!path.exists()) {

path.mkdir();

}

File file = new File(Constant.FILE_PATH, fileName);

if (file.exists()) {

//找到了文件,代表已经下载过,则获取其长度

downloadLength = file.length();

}

//之前下载过,需要重新来一个文件

int i = 1;

while (downloadLength >= contentLength) {

int dotIndex = fileName.lastIndexOf(".");

String fileNameOther;

if (dotIndex == -1) {

fileNameOther = fileName + "(" + i + ")";

} else {

fileNameOther = fileName.substring(0, dotIndex)

+ "(" + i + ")" + fileName.substring(dotIndex);

}

File newFile = new File(Constant.FILE_PATH, fileNameOther);

file = newFile;

downloadLength = newFile.length();

i++;

}

//设置改变过的文件名/大小

downloadInfo.setProgress(downloadLength);

downloadInfo.setFileName(file.getName());

return downloadInfo;

}

private class DownloadSubscribe implements ObservableOnSubscribe {

private DownloadInfo downloadInfo;

public DownloadSubscribe(DownloadInfo downloadInfo) {

this.downloadInfo = downloadInfo;

}

@Override

public void subscribe(ObservableEmitter e) throws Exception {

String url = downloadInfo.getUrl();

long downloadLength = downloadInfo.getProgress();//已经下载好的长度

long contentLength = downloadInfo.getTotal();//文件的总长度

//初始进度信息

e.onNext(downloadInfo);

Request request = new Request.Builder()

//确定下载的范围,添加此头,则服务器就可以跳过已经下载好的部分

.addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength)

.url(url)

.build();

Call call = mClient.newCall(request);

downCalls.put(url, call);//把这个添加到call里,方便取消

Response response = call.execute();

File file = new File(Constant.FILE_PATH, downloadInfo.getFileName());

InputStream is = null;

FileOutputStream fileOutputStream = null;

try {

is = response.body().byteStream();

fileOutputStream = new FileOutputStream(file, true);

byte[] buffer = new byte[2048];//缓冲数组2kB

int len;

while ((len = is.read(buffer)) != -1) {

fileOutputStream.write(buffer, 0, len);

downloadLength += len;

downloadInfo.setProgress(downloadLength);

e.onNext(downloadInfo);

}

fileOutputStream.flush();

downCalls.remove(url);

} finally {

//关闭IO流

DownloadIO.closeAll(is, fileOutputStream);

}

e.onComplete();//完成

}

}

/**

* 获取下载长度

*

* @param downloadUrl

* @return

*/

private long getContentLength(String downloadUrl) {

Request request = new Request.Builder()

.url(downloadUrl)

.build();

try {

Response response = mClient.newCall(request).execute();

if (response != null && response.isSuccessful()) {

long contentLength = response.body().contentLength();

response.close();

return contentLength == 0 ? DownloadInfo.TOTAL_ERROR : contentLength;

}

} catch (IOException e) {

e.printStackTrace();

}

return DownloadInfo.TOTAL_ERROR;

}

}

这里要多分析一下,下载管理类是单例模式这是必须的,里面定义一个HashMap用来存放所有的下载任务,里面的download()方法,是主要过程

public void download(String url, DownloadObserver downLoadObserver) {

Observable.just(url)

.filter(new Predicate() {

@Override

public boolean test(String s) {

return !downCalls.containsKey(s);

}

}) // 过滤 call的map中已经有了,就证明正在下载,则这次不下载

.flatMap(new Function>() {

@Override

public ObservableSource> apply(String s) {

return Observable.just(createDownInfo(s));

}

}) // 生成 DownloadInfo

.map(new Function() {

@Override

public DownloadInfo apply(Object o) {

return getRealFileName((DownloadInfo)o);

}

}) // 如果已经下载,重新命名

.flatMap(new Function>() {

@Override

public ObservableSource apply(DownloadInfo downloadInfo) {

return Observable.create(new DownloadSubscribe(downloadInfo));

}

}) // 下载

.observeOn(AndroidSchedulers.mainThread()) // 在主线程中回调

.subscribeOn(Schedulers.io()) // 在子线程中执行

.subscribe(downLoadObserver); // 添加观察者,监听下载进度

}

这里用到的是Rxjava,第一步filter,过滤下载,已经下载的url再点击下载是不会另起下载任务的;第二步flatMap,通过url生成Bean类,这个按需求来设计,也可以直接传一个Bean进来也是可以的;第三步map,如果这个文件已经下载了,再次下载重新命名文件,这也是根据需求改变,如果下载过的文件不需要下载,这就可以省了;第四步flatMap,去下载具体去看下载方法;剩下的就是切换线程和添加观察者了。

step4 观察者

/**

* Created by zs

* Date:2018年 09月 12日

* Time:13:50

* —————————————————————————————————————

* About: 观察者

* —————————————————————————————————————

*/

public class DownloadObserver implements Observer {

public Disposable d;//可以用于取消注册的监听者

public DownloadInfo downloadInfo;

@Override

public void onSubscribe(Disposable d) {

this.d = d;

}

@Override

public void onNext(DownloadInfo value) {

this.downloadInfo = value;

downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD);

EventBus.getDefault().post(downloadInfo);

}

@Override

public void onError(Throwable e) {

Log.d("My_Log","onError");

if (DownloadManager.getInstance().getDownloadUrl(downloadInfo.getUrl())){

DownloadManager.getInstance().pauseDownload(downloadInfo.getUrl());

downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_ERROR);

EventBus.getDefault().post(downloadInfo);

}else{

downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_PAUSE);

EventBus.getDefault().post(downloadInfo);

}

}

@Override

public void onComplete() {

Log.d("My_Log","onComplete");

if (downloadInfo != null){

downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_OVER);

EventBus.getDefault().post(downloadInfo);

}

}

}

这里是用到EventBus来通知页面刷新的,当然也可以不用,把订阅者直接写在Activity,但是那样不利于代码的复用,如果多个页面需要进度更新就麻烦了。

step5 Activity 和 Adapter

页面中是一个RecyclerView,交互逻辑不是很多,说一点,Adapter条目的更新,用的notifyItemChanged(i) 每次更新某一条的进度,而不是notifyDataSetChanged()全部刷新,因为调用全部刷新,刷新的频率很高会导致条目中控件的点击事件不好用没有响应,被拦截了,而更新某一条也会有一个问题是条目刷新时会闪动,解决方案是把RecyclerView的刷新动画去掉,这样就解决了。

// 取消item刷新的动画

((SimpleItemAnimator)recycler_view.getItemAnimator()).setSupportsChangeAnimations(false);

/**

* Created by zs

* Date:2018年 09月 11日

* Time:18:06

* —————————————————————————————————————

* About:

* —————————————————————————————————————

*/

public class DownloadAdapter extends RecyclerView.Adapter {

private List mdata;

public DownloadAdapter(List mdata) {

this.mdata = mdata;

}

/**

* 更新下载进度

* @param info

*/

public void updateProgress(DownloadInfo info){

for (int i = 0; i < mdata.size(); i++){

if (mdata.get(i).getUrl().equals(info.getUrl())){

mdata.set(i,info);

notifyItemChanged(i);

break;

}

}

}

@Override

public UploadHolder onCreateViewHolder(ViewGroup parent, int viewType) {

View view = View.inflate(parent.getContext(), R.layout.item_download_layout,null);

return new UploadHolder(view);

}

@Override

public void onBindViewHolder(UploadHolder holder, int position) {

final DownloadInfo info = mdata.get(position);

if (DownloadInfo.DOWNLOAD_CANCEL.equals(info.getDownloadStatus())){

holder.main_progress.setProgress(0);

}else if (DownloadInfo.DOWNLOAD_OVER.equals(info.getDownloadStatus())){

holder.main_progress.setProgress(holder.main_progress.getMax());

}else {

if (info.getTotal() == 0){

holder.main_progress.setProgress(0);

}else {

float d = info.getProgress() * holder.main_progress.getMax() / info.getTotal();

holder.main_progress.setProgress((int) d);

}

}

holder.main_btn_down.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

DownloadManager.getInstance().download(info.getUrl(), new DownloadObserver());

}

});

holder.main_btn_pause.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

DownloadManager.getInstance().pauseDownload(info.getUrl());

}

});

holder.main_btn_cancel.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

DownloadManager.getInstance().cancelDownload(info);

}

});

}

@Override

public int getItemCount() {

return mdata.size();

}

public class UploadHolder extends RecyclerView.ViewHolder{

private ProgressBar main_progress;

private Button main_btn_down;

private Button main_btn_pause;

private Button main_btn_cancel;

public UploadHolder(View itemView) {

super(itemView);

main_progress = itemView.findViewById(R.id.main_progress);

main_btn_down = itemView.findViewById(R.id.main_btn_down);

main_btn_pause = itemView.findViewById(R.id.main_btn_pause);

main_btn_cancel = itemView.findViewById(R.id.main_btn_cancel);

}

}

}

github地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值