Android多文件断点续传(一)——数据封装以及界面实现

Android多文件断点续传在很多应用场景中都会运用到,更重要的是相对于简单的下载功能,断点续传在下载文件过程中能带来非常好的用户体验。本系列教程将围绕一个简单Demo介绍多文件断点续传的实现方式。

先看效果图,源码在教程结尾提供。

Android多文件断点续传(一)——数据封装以及界面实现_第1张图片

Demo所涉及主要内容如下:

1. Service:用于后台处理下载文件的逻辑。

2. SQLite : 用于保存下载进度。

3. EventBus : 用于分发和接收下载进度。

4. ThreadPool : 用于管理下载线程。

一. 封装实体类

我们需要将下载的文件信息和下载线程的信息分别封装起来。

/**
 * Created by kun on 2016/11/10.
 * 下载文件信息
 */
public class FileBean implements Serializable {

    private int id;
    private String fileName;
    private String url;
    private int length;
    private int finished;

    .... //Constructor,get,set
}
/**
 * Created by kun on 2016/11/10.
 * 下载线程信息
 */
public class ThreadBean implements Serializable{

    private int id;
    private String url;
    private int start;
    private int end;
    private int finished;

    .... //Constructor,get,set
}

FileBean 中封装了下载文件的信息:id、下载路径、文件名称、文件长度和已下载的长度。

ThreadBean 中封装了下载线程的信息:id、下载路径、下载起始位置、下载结束位置和已下载的长度。

二.绘制布局以及添加数据

在效果图中我们看到界面很简单,这里用RecyclerView来实现。


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
LinearLayout>

在Activity中我们初始化RecyclerView并添加几个下载数据


    private void initView(){
        recyclerview = (RecyclerView) findViewById(R.id.recyclerview);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerview.setLayoutManager(layoutManager);
    }

    private void initData() {
        FileBean fileBean1 = new FileBean(0, "instmobilemgr.exe", url1, 0);
        FileBean fileBean2 = new FileBean(1, "QQDownload_Setup_48_773_400.exe", url2, 0);
        FileBean fileBean3 = new FileBean(2, "QQPlayer_Setup_39_936.exe", url3, 0);
        FileBean fileBean4 = new FileBean(3, "QQMusicForYQQ.exe", url4, 0);
        List fileBeanList = new ArrayList<>();
        fileBeanList.add(fileBean1);
        fileBeanList.add(fileBean2);
        fileBeanList.add(fileBean3);
        fileBeanList.add(fileBean4);
        adaper = new RecyclerViewListAdapter(this, fileBeanList);
        recyclerview.setAdapter(adaper);
    }

三.RecyclerViewListAdapter

/**
 * Created by kun on 2016/11/11.
 */
public class RecyclerViewListAdapter extends RecyclerView.Adapter<RecyclerViewListAdapter.ViewHolder> {

    List datas;
    Context context;

    public RecyclerViewListAdapter(Context context, List datas) {
        if (datas == null) datas = new ArrayList<>();
        this.datas = datas;
        this.context = context;
    }

    ... ...

    //自定义的ViewHolder,持有每个Item的的所有界面元素
    public static class ViewHolder extends RecyclerView.ViewHolder {
        TextView textName;
        ProgressBar progressBar;
        Button btnStart;
        Button btnPause;

        public ViewHolder(View convertView) {
            super(convertView);
            textName = (TextView) convertView.findViewById(R.id.textName);
            progressBar = (ProgressBar) convertView.findViewById(R.id.progressBar);
            btnStart = (Button) convertView.findViewById(R.id.btnStart);
            btnPause = (Button) convertView.findViewById(R.id.btnPause);
        }
    }

    //将数据与界面进行绑定的操作
    @Override
    public void onBindViewHolder(final ViewHolder viewHoder, final int position) {
        final FileBean fileBean = datas.get(position);
        viewHoder.textName.setText(fileBean.getFileName());
        if(fileBean.getLength()!=0) {
            viewHoder.progressBar.setProgress((int)(fileBean.getFinished()*1.0f/fileBean.getLength()*100));
        }
        viewHoder.btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent startIntent = new Intent(context, DownloadService.class);
                startIntent.setAction(DownloadService.ACTION_START);
                startIntent.putExtra("FileBean", fileBean);
                context.startService(startIntent);
            }
        });
        viewHoder.btnPause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent pauseIntent = new Intent(context, DownloadService.class);
                pauseIntent.setAction(DownloadService.ACTION_PAUSE);
                pauseIntent.putExtra("FileBean", fileBean);
                context.startService(pauseIntent);
            }
        });
    }

    private long curTime = 0;
     public void updateProgress(FileBean fileBean) {
         int i = 0;
        for(FileBean data:datas){
            if(data.getId() == fileBean.getId()){
                data.setLength(fileBean.getLength());
                data.setFinished(fileBean.getFinished());
                if(System.currentTimeMillis()-curTime >500) {
                    curTime = System.currentTimeMillis();
                    notifyDataSetChanged();
                }
                return;
            }
            i++;
        }
    }
}

在这里我只贴出了关键代码,完整代码请查看源码。这里需要注意的是定义了一个公共的方法:updateProgress()。用于外部调用刷新进度条,这里传入了一个FileBean,通过Id对比我们可以获取到对应的数据,将文件长度和文件已下载完成的进度赋值过去,然后通过notifyDataSetChanged方法通知UI更新。这里对刷新做了限制,最快为500毫秒刷新一次。

刷新列表本来是采用notifyItemChanged(int position)方法,可惜会出现闪烁现象,找不到比较合理的解决方案,希望在此能抛砖引玉。

接着我们看一下onBindViewHolder方法中开始按钮和暂停按钮的点击事件。可以看到是这里主要启动了一个DownloadService,将对应的FileBean和Action传递到Service中,接着由Service在后台处理下载的逻辑。

到这里界面基本就处理完了,在进入DownloadService处理前我们还需要先准备好数据库,欢迎继续阅读下一篇。

Android多文件断点续传(二)——实现数据库储存下载信息

你可能感兴趣的:(断点续传)