列表断点续传
一、简介:
断点续传,这一概念经常听说,在项目中一直没有接触到,刚刚有点时间,自己做了一个列表下载的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