介绍
demo的主要逻辑是,利用okhttp 和 RxJava 在子线程中下载文件,通关观察者模式监听下载的进度,再回调到主线程中,然后利用EventBus 通知页面刷新,更新进度。
效果图
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
这里要多分析一下,下载管理类是单例模式这是必须的,里面定义一个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
这里用到的是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地址
https://github.com/QQzs/DownloadFile
参考地址:
https://blog.csdn.net/cfy137000/article/details/54838608
希望给大家有所帮助。