列表断点续传

一、简介:

断点续传,这一概念经常听说,在项目中一直没有接触到,刚刚有点时间,自己做了一个列表下载的demo,先看图,如下:

列表断点续传

如上图功能所示,可以同时下载多个文件(我为了展示方便,限制了同时只允许两路线程下载),并可以随时暂停,下次进入程序时也可以按上次暂停位置继续下载;话不多说,我现在将展示我的实现过程;

二、实现过程:

(1)首先创建下载对象:

public class DownloadFileInfo {
    private int id;
    private String fileName;//文件名
    private String filePath;//文件下载路径
    private long totalSize;//文件总大小
    private long currSize;//当前已下载大小
    private boolean isFinish;//是否已经下载完成
    private boolean isDownload;//是否正在下载
            //get/set方法、toSting方法
            //根据需要还可重写equals方法,为了比较方便,我这里重写了此方法
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof DownloadFileInfo) {
            if (((DownloadFileInfo) obj).getFilePath().trim().equals(filePath.trim())) {
            return true;
            }
          }
        return false;

}}

(2)建立sqlite数据库,并完善其操作方法,作用是保存每个下载文件的信息,如文件总大小,当前下载位置等,此为断点续传的基础,

db.execSQL("create table if not exists t_flie_down(_id integer primary key autoincrement" +
        ",down_path varchar(500) not null" +
        ",total_size long not null" +
        ",curr_size long not null" +
        ",file_name varchar(255) not null" +
        ",is_finish integer)");
public class DBOperation {
    private static DBOperation operation;
    private DBOpenHelper helper;
  private static final String DB_TABLE = "t_flie_down";//表名
  private static final String ID = "_id";//id
  private static final String DOWN_PATH = "down_path";//下载路径
  private static final String TOTAL_SIZE = "total_size";//文件总大小
  private static final String CURR_SIZE = "curr_size";//当前下载数
  private static final String FILE_NAME = "file_name";//文件名
  private static final String IS_FINISH = "is_finish";//0完成1未完成

       private Lock lock = new ReentrantLock(); 

//获取静态实例
    public static DBOperation getInstance(Context context) {
        if (operation == null) {
            synchronized (DBOperation.class) {
                if (operation == null)
                    operation = new DBOperation(context);
            }
        }
        return operation;
    }

    private DBOperation(Context context) {
        helper = new DBOpenHelper(context);
    }

    //更新下载文件信息
    public int updateDownloadFileInfo(DownloadFileInfo info) {
        int result = -1;
        lock.lock();

        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(TOTAL_SIZE, info.getTotalSize());
        values.put(CURR_SIZE, info.getCurrSize());
        if (info.isFinish()) {
            values.put(IS_FINISH, 0);
        } else {
            values.put(IS_FINISH, 1);
        }
        result = db.update(DB_TABLE, values, DOWN_PATH + "=?", new String[]{info.getFilePath()});
        db.close();
        lock.unlock();
        return result;
    }

    //新增文件下载信息
    public long addDownloadFileInfo(DownloadFileInfo info) {
        long result = -1;
        lock.lock();
        if (isFileExists(info.getFilePath())) {
            return result;
        }
        SQLiteDatabase db = helper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(FILE_NAME, info.getFileName());
        values.put(DOWN_PATH, info.getFilePath());
        values.put(TOTAL_SIZE, info.getTotalSize());
        values.put(CURR_SIZE, info.getCurrSize());
        values.put(IS_FINISH, 1);
        result = db.insert(DB_TABLE, null, values);
        db.close();
        lock.unlock();
        return result;
    }

    //文件是否存在
    public boolean isFileExists(String path) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor c = db.query(DB_TABLE, null, DOWN_PATH + "=?", new String[]{path},
                null, null, null, null);
        if (c != null && c.moveToFirst()) {
            return true;
        }
        c.close();
        db.close();
        return false;
    }


    //查询当前下载文件状态信息
    public List<DownloadFileInfo> getDownloadFileInfos() {
        List<DownloadFileInfo> fileInfos = null;
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor c = db.query(DB_TABLE, null, null, null,
                null, null, null, null);
        if (c != null) {
            fileInfos = new ArrayList<DownloadFileInfo>();
            DownloadFileInfo info = null;
            while (c.moveToNext()) {
                info = new DownloadFileInfo();
                info.setId(c.getInt(c.getColumnIndex(ID)));
                info.setFileName(c.getString(c.getColumnIndex(FILE_NAME)));
                info.setFilePath(c.getString(c.getColumnIndex(DOWN_PATH)));
                info.setTotalSize(c.getInt(c.getColumnIndex(TOTAL_SIZE)));
                info.setCurrSize(c.getInt(c.getColumnIndex(CURR_SIZE)));
                if (c.getInt(c.getColumnIndex(IS_FINISH)) == 0) {
                    info.setFinish(true);
                } else {
                    info.setFinish(false);
                }
                fileInfos.add(info);
            }
        }
        c.close();
        db.close();
        return fileInfos;
    }
}

    (3)设计下载线程,我这边设计是一条线程下载一个文件,此为下载的核心,代码如下:

public class DownLoadThread extends Thread {
    private DownloadFileInfo info;
    private Context context;
    private DownloadListener listener;
    private boolean isStop = false;

    public DownLoadThread(DownloadFileInfo info, Context context, DownloadListener listener) {
        this.info = info;
        this.context = context;
        this.listener = listener;
    }

    //获取当前线程是否处于下载状态
    public boolean isDownload() {
        return info.isDownload();
    }

    //停止下载
    public void setStop() {
        isStop = true;
        info.setDownload(false);

    }

    //获取文件下载路径
    public String getDownloadPath() {
        return info.getFilePath();
    }

    @Override
    public void run() {
        getFileLength();
        HttpURLConnection conn = null;
        RandomAccessFile raf = null;
        InputStream is = null;
        info.setDownload(true);//当前正在下载
        isStop = false;
        try {
            URL url = new URL(info.getFilePath());
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(30 * 1000);
            conn.setRequestMethod("GET");
            long start = info.getCurrSize();
            LogPrint.println("当前已经下载:" + start);
            conn.setRequestProperty("Range", "bytes=" + start + "-" + info.getTotalSize());
            raf = new RandomAccessFile(FileUtil.createFile(info.getFileName()), "rw");
            raf.seek(start);
            is = conn.getInputStream();
            byte[] buffer = new byte[1024 * 8];
            int len;

            while ((len = is.read(buffer)) != -1) {
                raf.write(buffer, 0, len);
                start += len;
                info.setCurrSize(start);
                LogPrint.println("已下载:" + start);
                if (listener != null) {
                    listener.updateCurrState(info);
                }

                if (isStop) {
                    LogPrint.println("暂停=========================");
                    DBOperation.getInstance(context).updateDownloadFileInfo(info);
                    return;
                }
            }
            info.setDownload(false);
            info.setFinish(true);
            if (listener != null) {
                listener.downloadSuccess(info);
            }
            DBOperation.getInstance(context).updateDownloadFileInfo(info);
            LogPrint.println(info.getFileName() + "下载完成");
        } catch (MalformedURLException e) {
            LogPrint.println("文件下载URL转换异常" + e);
        } catch (IOException e) {
            LogPrint.println("文件下载IO异常" + e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    private void getFileLength() {
        if (info.getTotalSize() <= 0) {//如果文件没有下载过,则网络获取文件长度
            HttpURLConnection connection = null;
            try {
                URL url = new URL(info.getFilePath());
                connection = (HttpURLConnection) url.openConnection();
                connection.setConnectTimeout(30 * 1000);
                connection.setRequestMethod("GET");
                int length = -1;
                if (connection.getResponseCode() == 200) {
                    length = connection.getContentLength();
                }
                if (length < 0) {
                    return;
                }
                info.setTotalSize(length);
                info.setCurrSize(0);
                long i = DBOperation.getInstance(context).addDownloadFileInfo(info);
                LogPrint.println(info.getFileName() + "文件大小" + length + "/新增结果:" + i);
            } catch (MalformedURLException e) {
                LogPrint.println("URL转换异常" + e);
            } catch (IOException e) {
                LogPrint.println("网络连接异常" + e);
            } finally {
                if (connection != null)
                    connection.disconnect();
            }
        }
    }
}

在上面下载线程类中,应重点关注几个地方,

a.获取文件总大小  getFileLength() 方法

b.下载文件是的请求头设置,此为续传基础:

 conn.setRequestProperty("Range", "bytes=" + start + "-" + info.getTotalSize());

c.RandomAccessFile 文件配置,此为文件续写核心之处:

 raf = new RandomAccessFile(FileUtil.createFile(info.getFileName()), "rw");

 raf.seek(start);

d.配置监听,更新下载状态,我这边自己定义了DownloadListener监听:

public interface DownloadListener {
    void updateCurrState(DownloadFileInfo info);//更新状态

    void downloadSuccess(DownloadFileInfo info);//下载完成

}

(4)完成下载页面编写,分两个部分,一个是adapter的编写,一个是主页面;

    a.首先介绍一下adapter的编写:

    adapter编写时最重要的一点就是注意刷新,每个状态时要每个item单独进行刷新,互不影响;

public class DownloadAdapter extends BaseAdapter {
    private List<DownloadFileInfo> infos;
    private Context context;
    private List<DownLoadThread> downLoadThreads;
    private ListView listView;
    private DownloadFileInfo infoH;
    private LinearLayout itemLLH;
    private ProgressBar pH;
    private TextView tpH;
    private TextView tnH;
    private Button bnH;
    private Handler mHandler = new Handler() {
        @Override
        public void dispatchMessage(Message msg) {
            infoH = (DownloadFileInfo) msg.obj;
            itemLLH = (LinearLayout) listView.findViewWithTag(infoH.getFilePath());
            pH = (ProgressBar) itemLLH.findViewById(R.id.item_progress);
            tpH = (TextView) itemLLH.findViewById(R.id.item_tv_progress);
            tnH = (TextView) itemLLH.findViewById(R.id.item_name);
            bnH = (Button) itemLLH.findViewById(R.id.item_start_or_stop);
            switch (msg.what) {
                case 100:
                    setItemView(infoH, tnH, tpH, pH, bnH, 100);
                    break;
                case 101:
                    setItemView(infoH, tnH, tpH, pH, bnH, 101);
                    break;
                case 102:
                    setItemView(infoH, tnH, tpH, pH, bnH, 102);
                    break;
            }
        }
    };

    public DownloadAdapter(List<DownloadFileInfo> infos, Context context, ListView listView) {
        this.infos = infos;
        this.context = context;
        this.listView = listView;
        downLoadThreads = new ArrayList<>();
    }

    public void update(List<DownloadFileInfo> infos) {
        this.infos = infos;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return infos.size();
    }

    @Override
    public Object getItem(int position) {
        return infos.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = View.inflate(context, R.layout.item_download, null);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        final DownloadFileInfo info = (DownloadFileInfo) getItem(position);

        setItemView(info, holder.item_name, holder.item_tv_progress, holder.item_progress, holder.item_start_or_stop, 1);

        holder.item_ll.setTag(info.getFilePath());
        holder.item_start_or_stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LogPrint.println("点击按钮");
                if (((Button) v).getText().equals("start")) {
                    LogPrint.println("start>>stop");
                    if (downLoadThreads.size() >= 2) {
                        Toast.makeText(context, "只同时允许2个文件同时下载", Toast.LENGTH_LONG).show();
                        return;
                    }
                    DownLoadThread downLoadThread = new DownLoadThread(info, context, new DownloadListener() {
                        @Override
                        public void updateCurrState(DownloadFileInfo info) {
                            updateDownloadState(info);
                        }

                        @Override
                        public void downloadSuccess(DownloadFileInfo info) {
                            downloadSuccessFinish(info);
                        }
                    });
                    downLoadThread.start();
                    downLoadThreads.add(downLoadThread);

                } else if (((Button) v).getText().equals("stop")) {
                    LogPrint.println("stop>>start:" + info);
                    removeDownloadThread(info);
                }
            }
        });
        return convertView;
    }

    //设置item展示
    private void setItemView(DownloadFileInfo info, TextView item_name,
                             TextView item_tv_progress, ProgressBar item_progress, Button item_start_or_stop, int type) {
        item_name.setText(info.getFileName());
        if (info.getTotalSize() != 0) {
            item_tv_progress.setText((int) ((double) (info.getCurrSize() * 100) / (double) info.getTotalSize()) + " %");
            item_progress.setMax(100);
            item_progress.setProgress((int) ((double) (info.getCurrSize() * 100) / (double) info.getTotalSize()));
        } else {
            item_tv_progress.setText("0 %");
        }

        if (info.isFinish()) {
            item_start_or_stop.setVisibility(View.INVISIBLE);
        } else {
            LogPrint.println("type=" + type + "/" + info.isDownload());
            if (info.isDownload()) {
                item_start_or_stop.setText("stop");
            } else {

                item_start_or_stop.setText("start");
            }
        }
    }

    //当下载完成时,更新数据
    private void downloadSuccessFinish(DownloadFileInfo info) {
        for (int i = 0; i < infos.size(); i++) {
            if (infos.get(i).equals(info)) {
                infos.get(i).setCurrSize(info.getCurrSize());
                infos.get(i).setTotalSize(info.getTotalSize());
                infos.get(i).setFinish(info.isFinish());
                infos.get(i).setDownload(info.isDownload());
                mHandler.obtainMessage(101, infos.get(i)).sendToTarget();
                break;
            }
        }
        for (int i = 0; i < downLoadThreads.size(); i++) {
            if (downLoadThreads.get(i).getDownloadPath().equals(info.getFilePath())) {
                downLoadThreads.remove(i);
                break;
            }
        }
    }

    //下载时更新
    private void updateDownloadState(DownloadFileInfo info) {
        for (int i = 0; i < infos.size(); i++) {
            if (infos.get(i).equals(info)) {
                infos.get(i).setCurrSize(info.getCurrSize());
                infos.get(i).setTotalSize(info.getTotalSize());
                infos.get(i).setDownload(info.isDownload());
                mHandler.obtainMessage(100, infos.get(i)).sendToTarget();
                break;
            }
        }
    }

    //当页面关闭时,暂停下载
    public void close() {
        for (int i = 0; i < downLoadThreads.size(); i++) {
            downLoadThreads.get(i).setStop();
            downLoadThreads.remove(i);
        }
    }

    //暂停时在下载线程集合中,删除下载线程
    private void removeDownloadThread(DownloadFileInfo info) {
        for (int i = 0; i < downLoadThreads.size(); i++) {

            if (downLoadThreads.get(i).getDownloadPath().equals(info.getFilePath())) {
                LogPrint.println("关地址:" + downLoadThreads.get(i).getDownloadPath());
                downLoadThreads.get(i).setStop();
                downLoadThreads.remove(i);
                break;
            }
        }
        for (int i = 0; i < infos.size(); i++) {
            if (infos.get(i).equals(info)) {
                infos.get(i).setCurrSize(info.getCurrSize());
                infos.get(i).setTotalSize(info.getTotalSize());
                infos.get(i).setDownload(false);
                LogPrint.println("关地址1212121:" + infos.get(i));
                mHandler.obtainMessage(102, infos.get(i)).sendToTarget();
                break;
            }
        }
    }


    public class ViewHolder {
        @BindView(R.id.item_ll)
        LinearLayout item_ll;
        @BindView(R.id.item_name)
        TextView item_name;
        @BindView(R.id.item_tv_progress)
        TextView item_tv_progress;
        @BindView(R.id.item_start_or_stop)
        Button item_start_or_stop;
        @BindView(R.id.item_progress)
        ProgressBar item_progress;

        public ViewHolder(View view) {
            ButterKnife.bind(this, view);
        }
    }
}

b.主页面:

主页面主要是进行初始数据的加载:

@BindView(R.id.listview)
ListView listview;
private List<DownloadFileInfo> fileInfos;
private List<DownloadFileInfo> localFileInfos;
private DownloadAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);

    fileInfos = new ArrayList<>();
    adapter = new DownloadAdapter(fileInfos, this, listview);
    listview.setAdapter(adapter);
    setLocalInfo();


}

@Override
protected void onDestroy() {
    super.onDestroy();
    LogPrint.println("关闭页面");
    adapter.close();
}
//网络数据与本地数据进行对比
private void setLocalInfo() {
    setData();
    localFileInfos = DBOperation.getInstance(this).getDownloadFileInfos();
    LogPrint.println("localFileInfos============" + localFileInfos);
    if (localFileInfos != null && localFileInfos.size() > 0) {
        for (int i = 0; i < fileInfos.size(); i++) {
            LogPrint.println("localFileInfos=" + localFileInfos);

            for (int j = 0; j < localFileInfos.size(); j++) {
                if (fileInfos.get(i).equals(localFileInfos.get(j))) {

                    fileInfos.get(i).setFinish(localFileInfos.get(j).isFinish());
                    fileInfos.get(i).setTotalSize(localFileInfos.get(j).getTotalSize());
                    fileInfos.get(i).setCurrSize(localFileInfos.get(j).getCurrSize());
                    localFileInfos.remove(j);
                    break;
                }
            }

        }
    }
    LogPrint.println("fileInfos=" + fileInfos);
    adapter.update(fileInfos);
}
//模拟网络数据
private void setData() {
    DownloadFileInfo info = new DownloadFileInfo();
    info.setFilePath("http://192.168.0.105:8080/loginbg.jpg");
    info.setFileName("loginbg.jpg");
    info.setCurrSize(0);
    info.setTotalSize(0);
    fileInfos.add(info);

    info = new DownloadFileInfo();
    info.setFilePath("http://192.168.0.105:8080/zhaop.rar");
    info.setFileName("zhaop.rar");
    info.setCurrSize(0);
    info.setTotalSize(0);
    fileInfos.add(info);

    info = new DownloadFileInfo();
    info.setFilePath("http://192.168.0.105:8080/mysql.zip");
    info.setFileName("mysql.zip");
    info.setCurrSize(0);
    info.setTotalSize(0);
    fileInfos.add(info);
}
}

故此,整个可以断点续传的列表就完成了,


以上,附上源码地址,供大家参考

https://download.****.net/download/shenyang2003112312/10473825