OkDownload项目实战
本文介绍项目中引入okhttp-okgo开源框架里的OkDownload部分,实现了RecyclerView列表的下载功能.
- 引入OKDownload
需求不仅是要支持断点续传,而且还要支持队列下载和任务恢复,这样单单导入okgo的包是不够的,还需要额外导入下面这个okserver的包.然后在APPlication里初始化OkGo
//初始化OkGo
OkGo.getInstance().init(this);
//设置下载路径
OkDownload.getInstance().setFolder(Constants.DOWNLOAD_APK_PATH);
- 单个任务的下载
上篇博文已经介绍了三个任务下载的基本使用,现在这里直接粘贴代码
GetRequest<File> request = OkGo.get(itemsBean.getUrl());
DownloadTask task = OkDownload.request(itemsBean.getUrl(), request) .save().register(new ListDownloadListener(itemsBean.getUrl(), mHolder));
task.start();
根据下载url得到一个GetRequest对象,然后调用OkDownload的静态方法,把一个保证下载任务唯一性的key(String类型)和这个GetRequest对象传递进去就可以创建一个下载任务了.
- 列表下载
okdownload库中维护了一个由线程池控制的下载队列,可以通过下面这个方式来设定允许同时下载的任务数目(默认是3个)
OkDownload.getInstance().getThreadPool().setCorePoolSize(2);
然后就是状态控制了,我们都知道一个文件下载伴随有这几种状态
public static final int NONE = 0; //无状态 public static final int WAITING = 1; //等待 public static final int LOADING = 2; //下载中 public static final int PAUSE = 3; //暂停 public static final int ERROR = 4; //错误 public static final int FINISH = 5; //完成
可以根据它提供的一个Progress的status,表示当前状态的字段,来更新UI,给用户反馈.
于是,我们可以获取到当前item的任务之后,让下载的点击事件这样响应:
Progress progress = task.progress; if (progress.status == Progress.LOADING || progress.status == Progress.PAUSE) { flikerProgressBar.toggle(); } switch (progress.status) { case Progress.PAUSE: case Progress.NONE: case Progress.ERROR: task.start(); break; case Progress.LOADING: task.pause(); break; case Progress.FINISH: if (ApkUtils.isAvailable(context, new File(progress.filePath))) { ApkUtils.startAPP(context, ApkUtils.getPackageName(context, progress.filePath)); } else { ApkUtils.install(context, new File(progress.filePath)); } break; } refresh(progress);
- 判断和获取指定tag的下载任务
DownloadTask task;
if (!OkDownload.getInstance().hasTask(itemsBean.getUrl())) {
GetRequest<File> request = OkGo.get(itemsBean.getUrl());
task = OkDownload.request(itemsBean.getUrl(), request)
.save().register(new ListDownloadListener(itemsBean.getUrl(), mHolder));
} else {
task = OkDownload.getInstance().getTask(itemsBean.getUrl());
}
如果下载队列里不包含当前tag的任务,通过OkDownload.getInstance().hasTask(String tag)判断,那么就对它新建一个下载任务,因为我们要对他进行下载操作.
如果下载队列里已经包含当前任务,那么我们通过OkDownload.getInstance().getTask(String tag)方法来获取这个task;
然后对这个task进行上面的步骤.
如此,一个下载队列的功能就完成了.
下面贴出完整代码
public class BaseAppsDownloadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private List<AppBean.ResultBean.ItemsBean> mList; private LayoutInflater inflater; private Context context; private List<DownloadTask> values; private boolean comeFromRank; public BaseAppsDownloadAdapter(Context context, List<AppBean.ResultBean.ItemsBean> mList, boolean comeFromRank) { this.comeFromRank = comeFromRank; this.mList = mList; this.context = context; inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public void updateData() { //这里是将数据库的数据恢复 values = OkDownload.restore(DownloadManager.getInstance().getAll()); notifyDataSetChanged(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = inflater.inflate(R.layout.home_innovate_item, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (mList == null || mList.size() == 0) { return; } AppBean.ResultBean.ItemsBean itemsBean = mList.get(position); if (itemsBean == null) { return; } ViewHolder mHolder = (ViewHolder) holder; // 判断布局中是否需要显示排行 if (comeFromRank) { mHolder.tv_rank.setVisibility(View.VISIBLE); mHolder.tv_rank.setText("" + (position + 1)); } else { mHolder.tv_rank.setVisibility(View.GONE); } mHolder.bind(mHolder, itemsBean); mHolder.flikerProgressBar.setVisibility(View.INVISIBLE); mHolder.ib_download.setVisibility(View.VISIBLE); if (TextUtils.isEmpty(itemsBean.getUrl())) { return; } if (OkDownload.getInstance().hasTask(itemsBean.getUrl())) { DownloadTask task = OkDownload.getInstance().getTask(itemsBean.getUrl()).register(new ListDownloadListener(itemsBean.getUrl(), mHolder)); mHolder.setTask(task); mHolder.setTag(itemsBean.getUrl()); mHolder.flikerProgressBar.setVisibility(View.VISIBLE); mHolder.ib_download.setVisibility(View.INVISIBLE); mHolder.refresh(task.progress); } else { mHolder.flikerProgressBar.setVisibility(View.INVISIBLE); mHolder.ib_download.setVisibility(View.VISIBLE); mHolder.setTask(null); mHolder.setTag(null); } } public void notifySetDataListChanged(List<AppBean.ResultBean.ItemsBean> appsData) { mList = appsData; notifyDataSetChanged(); } public void unRegister() { Map<String, DownloadTask> taskMap = OkDownload.getInstance().getTaskMap(); for (DownloadTask task : taskMap.values()) { task.unRegister(task.progress.tag); } } @Override public int getItemCount() { return mList == null ? 0 : mList.size(); } public class ViewHolder extends RecyclerView.ViewHolder { private final ImageView iv_icon; private final TextView tv_name; private final TextView tv_size; private final TextView tv_rank; private final TextView tv_des; private final TextView ib_download; private FlickerProgressBar flikerProgressBar; private DownloadTask task; private String tag; public ViewHolder(View itemView) { super(itemView); iv_icon = ((ImageView) itemView.findViewById(R.id.app_icon)); tv_name = ((TextView) itemView.findViewById(R.id.tv_app_name)); tv_size = ((TextView) itemView.findViewById(R.id.tv_app_size)); tv_rank = ((TextView) itemView.findViewById(R.id.tv_app_rank)); tv_des = ((TextView) itemView.findViewById(R.id.tv_app_des)); ib_download = ((TextView) itemView.findViewById(R.id.ib_download)); flikerProgressBar = ((FlickerProgressBar) itemView.findViewById(R.id.btn_progress)); itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { context.startActivity(new Intent(context, AppDetailActivity.class) .putExtra("appid", mList.get(getAdapterPosition()).getId()) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } }); } public void setTask(DownloadTask task) { this.task = task; } public void bind(final ViewHolder mHolder, final AppBean.ResultBean.ItemsBean itemsBean) { mHolder.tv_name.setText(itemsBean.getName()); if (TextUtils.isEmpty(itemsBean.getImageUrl())) { mHolder.iv_icon.setImageResource(R.mipmap.icon_default); } else { Glide.with(context).load(itemsBean.getImageUrl()).placeholder(R.mipmap.icon_default).into(mHolder.iv_icon); } mHolder.tv_des.setText("" + itemsBean.getAbstractString()); mHolder.tv_size.setText(FileUtil.bytes2kb(itemsBean.getSize())); mHolder.flikerProgressBar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (TextUtils.isEmpty(itemsBean.getUrl())) { Toast.makeText(v.getContext(), "下载链接不存在", Toast.LENGTH_SHORT).show(); return; } DownloadTask task; if (!OkDownload.getInstance().hasTask(itemsBean.getUrl())) { GetRequest<File> request = OkGo.get(itemsBean.getUrl()); task = OkDownload.request(itemsBean.getUrl(), request) .save().register(new ListDownloadListener(itemsBean.getUrl(), mHolder)); } else { task = OkDownload.getInstance().getTask(itemsBean.getUrl()); } mHolder.setTask(task); mHolder.setTag(itemsBean.getUrl()); mHolder.start(); } }); } public void refresh(Progress progress) { flikerProgressBar.setProgress((int) (progress.fraction * 100)); switch (progress.status) { case Progress.NONE: flikerProgressBar.setText("下载"); break; case Progress.PAUSE: flikerProgressBar.setText("继续"); break; case Progress.ERROR: flikerProgressBar.setText("出错"); break; case Progress.WAITING: flikerProgressBar.setText("等待"); break; case Progress.FINISH: if (ApkUtils.isAppInstalled(context, ApkUtils.getPackageName(context, progress.filePath))) { flikerProgressBar.setText("打开"); } else { flikerProgressBar.setText("安装"); } break; case Progress.LOADING: flikerProgressBar.setText((int) (progress.fraction * 100 + 0.5f) + "%"); break; } } public void start() { Progress progress = task.progress; if (progress.status == Progress.LOADING || progress.status == Progress.PAUSE) { flikerProgressBar.toggle(); } switch (progress.status) { case Progress.PAUSE: case Progress.NONE: case Progress.ERROR: task.start(); break; case Progress.LOADING: task.pause(); break; case Progress.FINISH: if (ApkUtils.isAvailable(context, new File(progress.filePath))) { ApkUtils.startAPP(context, ApkUtils.getPackageName(context, progress.filePath)); } else { ApkUtils.install(context, new File(progress.filePath)); } break; } refresh(progress); } public void setTag(String tag) { this.tag = tag; } public String getTag() { return tag; } } private class ListDownloadListener extends DownloadListener { private ViewHolder holder; ListDownloadListener(Object tag, ViewHolder holder) { super(tag); this.holder = holder; } @Override public void onStart(Progress progress) { } @Override public void onProgress(Progress progress) { if (tag == holder.getTag()) { holder.refresh(progress); } } @Override public void onError(Progress progress) { Throwable throwable = progress.exception; if (throwable != null) throwable.printStackTrace(); } @Override public void onFinish(File file, Progress progress) { Toast.makeText(context, "下载完成:" + progress.filePath, Toast.LENGTH_SHORT).show(); } @Override public void onRemove(Progress progress) { } }
上面的代码,之所以可以有效的避免RecyclerView滚动item布局混乱的问题.是因为我们对每一个item的各项数据都做了赋值操作.
更新下载进度的ListDownloadListener 只与该任务的tag有关,而tag又是itemBean.getUrl() , 所以只要下载链接唯一,那么这个监听是不会响应到别的iteam上的.