单线程下载,并实现断点续传
2017-02-23 15:56
295 查看
作者: 夏至 转载请保留以下申明,谢谢
http://blog.csdn.net/u011418943/article/details/56674086
现在网络框架越来越多,对于下载一个文件来说,用一个别人封装好的库,基本很容易就可以搞定,比如用okhttp3这个比较优秀的框架,里面它封装好的网络框架就非常优秀。
当然,有时候我们需要自定义我们自己的下载器,比如显示一些其他要实现其他属性,这个时候,就不得不自己去封装了。其实也不是太难,就是可能在考虑上欠缺了吧。
这篇博客是看完慕课网的异步下载笔记,不了解的可以先去看。连接如下:http://www.imooc.com/learn/363
废话不多说,先上效果图:
![](http://img.blog.csdn.net/20170223155530199?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTQxODk0Mw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
工程下载地址如下:
https://git.oschina.net/zhengshaorui/filedownloaduesdb.git
扩展之后,我们可以实现apk的在线更新功能,效果图如下所示:
![](http://img.blog.csdn.net/20170223160935920?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTQxODk0Mw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
工程下载地址如下:
https://git.oschina.net/zhengshaorui/ApkAutoUpdateDemo.git
首先,通过 Serioliable 传递参数,定义一个实体类:
这里你可能有疑问,threadcount是什么gui,还有其他乱七八糟的属性,别急,这个是为了接下来的多线程的封装的。
然后,main函数中,把参数传递给service
service 中,先启动线程检测文件的大小;把它保存到Fileinfo里面,再启动下城下载:
其中下载方法 DownloadTask 如下所示:
log 和文件如下:
![](http://img.blog.csdn.net/20170223160036892?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTQxODk0Mw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](http://img.blog.csdn.net/20170223160059096?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTQxODk0Mw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
那么如何把文件保存到数据库中,实现断点续传呢 ,其实说白了,就是把上次的断点保存在文件、map或者数据库中。这里保存在数据库中
首先定义一个实体类,ThreadInfo:
ThreadInfo 是来记录线程信息的 ,所以,而数据的保存,我们用数据库来保存;首先,先定义表单:
上面的实现很简单,就是创建一个数据库,然后,我们需要给它添加 listener回调接口:
回调接口中,我们要实现的方法就是数据库的 增删查改,还有就是检查该数据库,是否有线程和判断线程是否存在。接下来就是数据库的实现方法了:
ok,这样,我们的续传的数据库就写好了,接下来就是对把线程信息给保存起来了;我们要修改一下我们的 DownloadTask 这个类:
首先,先初始化数据库:
然后修改其中的download:
然后修改DownloadThread:
这样就完成了。
http://blog.csdn.net/u011418943/article/details/56674086
现在网络框架越来越多,对于下载一个文件来说,用一个别人封装好的库,基本很容易就可以搞定,比如用okhttp3这个比较优秀的框架,里面它封装好的网络框架就非常优秀。
当然,有时候我们需要自定义我们自己的下载器,比如显示一些其他要实现其他属性,这个时候,就不得不自己去封装了。其实也不是太难,就是可能在考虑上欠缺了吧。
这篇博客是看完慕课网的异步下载笔记,不了解的可以先去看。连接如下:http://www.imooc.com/learn/363
废话不多说,先上效果图:
工程下载地址如下:
https://git.oschina.net/zhengshaorui/filedownloaduesdb.git
扩展之后,我们可以实现apk的在线更新功能,效果图如下所示:
工程下载地址如下:
https://git.oschina.net/zhengshaorui/ApkAutoUpdateDemo.git
首先,通过 Serioliable 传递参数,定义一个实体类:
/** * 用serializable就可以接受intent的参数了 * @author zhengshaorui * */ public class FileInfo implements Serializable{ private int id; private String apkurl; private String fileDir; private String fileName; private int length; private File Dir; private int finished; private int threadCount; public FileInfo() { super(); } public FileInfo(int id, String apkurl, String fileDir, String fileName, int length, File dir, int finished, int threadCount) { this.id = id; this.apkurl = apkurl; this.fileDir = fileDir; this.fileName = fileName; this.length = length; Dir = dir; this.finished = finished; this.threadCount = threadCount; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getApkurl() { return apkurl; } public void setApkurl(String apkurl) { this.apkurl = apkurl; } public String getFileName() { return fileName; } @Override public String toString() { return "FileInfo{" + "id=" + id + ", apkurl='" + apkurl + '\'' + ", fileDir='" + fileDir + '\'' + ", fileName='" + fileName + '\'' + ", length=" + length + ", Dir=" + Dir + ", finished=" + finished + ", threadCount=" + threadCount + '}'; } public void setFileName(String fileName) { this.fileName = fileName; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public int getFinished() { return finished; } public void setFinished(int finished) { this.finished = finished; } public String getFileDir() { return fileDir; } public void setFileDir(String fileDir) { this.fileDir = fileDir; } public File getDir() { return Dir; } public void setDir(File dir) { Dir = dir; } public int getThreadCount() { return threadCount; } public void setThreadCount(int threadCount) { this.threadCount = threadCount; } }
这里你可能有疑问,threadcount是什么gui,还有其他乱七八糟的属性,别急,这个是为了接下来的多线程的封装的。
然后,main函数中,把参数传递给service
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); fileDir = getFilesDir()+"/mydownload/"; mFileInfo = new FileInfo(0,"http://images.infzm.com/mobile/infzmreader.apk", fileDir,"north.apk",0,null,0); } public void start(View view){ Intent start = new Intent(this,DowaloadService.class); start.putExtra("fileinfo",mFileInfo); start.putExtra("service",1); startService(start); } public void stop(View view){ Intent start = new Intent(this,DowaloadService.class); start.putExtra("fileinfo",mFileInfo); start.putExtra("service",0); startService(start); }
service 中,先启动线程检测文件的大小;把它保存到Fileinfo里面,再启动下城下载:
public int onStartCommand(Intent intent, int flags, int startId) { FileInfo mFileInfo = (FileInfo) intent.getSerializableExtra("fileinfo"); int status = intent.getIntExtra("service",0); if (status == 1){ Log.d(TAG, "start: "+mFileInfo); }else{ Log.d(TAG, "stop: "); } new download(mFileInfo).start(); return super.onStartCommand(intent, flags, startId); } class download extends Thread{ FileInfo mFileInfo; public download(FileInfo fileinfo){ this.mFileInfo = fileinfo; } @Override public void run() { super.run(); HttpURLConnection conn = null; try { URL url = new URL(mFileInfo.getApkurl()); conn = (HttpURLConnection) url.openConnection(); //加上这句是为了防止connection.getContentLength()获取不到 conn.setRequestProperty("Accept-Encoding", "identity"); conn.setRequestMethod("GET"); conn.setReadTimeout(5000); conn.setConnectTimeout(5000); if (conn.getResponseCode() == 200){ int length = conn.getContentLength(); if (length < 0) return; else mFileInfo.setLength(length); } File dir = new File(mFileInfo.getFileDir()); if (!dir.exists()){ dir.mkdir(); } mFileInfo.setDir(dir); Log.d(TAG, "run: "+mFileInfo); new DownloadTask(mContext).download(mFileInfo); } catch (Exception e) { e.printStackTrace(); Log.d(TAG, "error: "+e.toString()); }finally { conn.disconnect(); } } }
其中下载方法 DownloadTask 如下所示:
public class DownloadTask { private static final String TAG = "zsr"; private Context mContext; private int downloadlength = 0; public DownloadTask(Context mContext) { this.mContext = mContext; } public void download(FileInfo fileInfo){ new DownloadThread(fileInfo).start(); } class DownloadThread extends Thread{ FileInfo mFileInfo; public DownloadThread(FileInfo mFileInfo) { this.mFileInfo = mFileInfo; } @Override public void run() { super.run(); HttpURLConnection conn = null; RandomAccessFile raf = null; BufferedInputStream bis = null; try { URL url = new URL(mFileInfo.getApkurl()); conn = (HttpURLConnection) url.openConnection(); //加上这句是为了防止connection.getContentLength()获取不到 conn.setRequestProperty("Accept-Encoding", "identity"); conn.setRequestMethod("GET"); conn.setReadTimeout(5000); conn.setConnectTimeout(5000); int start = 0; int end = mFileInfo.getLength(); //设置下载位置 conn.setRequestProperty("Range", "bytes="+start+"-"+end); //文件写入位置 File file = new File(mFileInfo.getDir(),mFileInfo.getFileName()); raf = new RandomAccessFile(file,"rwd"); raf.seek(start); int len = -1; byte[] bytes = new byte[1024*2]; bis = new BufferedInputStream(conn.getInputStream()); while( (len = bis.read(bytes)) != -1 ){ raf.write(bytes,0,len); downloadlength += len; Log.d(TAG, "run: "+downloadlength); } } catch (Exception e) { e.printStackTrace(); Log.d(TAG, "downloaderror: "+e.toString()); }finally { if (bis != null) try { bis.close(); if (raf != null) raf.close(); } catch (IOException e) { e.printStackTrace(); } if (conn != null) conn.disconnect(); } } } }
log 和文件如下:
那么如何把文件保存到数据库中,实现断点续传呢 ,其实说白了,就是把上次的断点保存在文件、map或者数据库中。这里保存在数据库中
首先定义一个实体类,ThreadInfo:
public class ThreadInfo { private int id; private String url; private int start; private int end; private int finished; public ThreadInfo() { super(); } public ThreadInfo(int id, String url, int start, int end, int finished) { super(); this.id = id; this.url = url; this.start = start; this.end = end; this.finished = finished; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getStart() { return start; } public void setStart(int start) { this.start = start; } public int getEnd() { return end; } public void setEnd(int end) { this.end = end; } public int getFinished() { return finished; } public void setFinished(int finished) { this.finished = finished; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } @Override public String toString() { return "ThreadInfo [id=" + id + ", url=" + url + ", start=" + start + ", end=" + end + ", finished=" + finished + "]"; } }
ThreadInfo 是来记录线程信息的 ,所以,而数据的保存,我们用数据库来保存;首先,先定义表单:
public class DBhelper extends SQLiteOpenHelper{ public static final String DB_NAME = "download.db"; public static final int VERSION = 1; private static final String SQL_CREATE_TABLE = "create table if not exists thread_info(" + "_id integer primary key autoincrement," + "thread_id integer," + "url text," + "start integer," + "end integer," + "finished interger)"; private static final String SQL_DROP = "drop table if exists thread_info"; public DBhelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL(SQL_CREATE_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) { // TODO Auto-generated method stub db.execSQL(SQL_DROP); db.execSQL(SQL_CREATE_TABLE); } }
上面的实现很简单,就是创建一个数据库,然后,我们需要给它添加 listener回调接口:
public interface DBTreadListener { /** * 插入线程信息 * @param threadInfo */ public void insertThread(ThreadInfo threadInfo); /** * 删除数据库,用两个标志位来判断 * @param url * @param thread_id */ public void deleteThread(String url, int thread_id); /** * 更新线程下载进度 finished * @param url * @param thread_id * @param finished */ public void updateThread(String url, int thread_id, int finished); /** * 查询文件的线程信息,以一个List的形式返回 * @param url * @return */ public List<ThreadInfo> getThreads(String url); /** * 判断线程是否存在,如果存在则更新,如果不存在则创建 * @param url * @return */ public boolean isThreadExsits(String url, int thread_id); }
回调接口中,我们要实现的方法就是数据库的 增删查改,还有就是检查该数据库,是否有线程和判断线程是否存在。接下来就是数据库的实现方法了:
/** * 数据库接口的实现 * * @author zhengshaorui * */ public class DBThreadListenerImp implements DBTreadListener { private DBhelper mDBhelper = null; public DBThreadListenerImp(Context context) { // 在接口实现的时候,创建数据库表 mDBhelper = new DBhelper(context, DBhelper.DB_NAME, null, DBhelper.VERSION); } public void insertThread(ThreadInfo threadInfo) { // TODO Auto-generated method stub SQLiteDatabase db = mDBhelper.getWritableDatabase(); db.execSQL( "insert into thread_info(thread_id,url,start,end,finished) values(?,?,?,?,?)", new Object[] {threadInfo.getId(), threadInfo.getUrl(), threadInfo.getStart(), threadInfo.getEnd(), threadInfo.getFinished() }); db.close(); } @Override public void deleteThread(String url, int thread_id) { // TODO Auto-generated method stub SQLiteDatabase db = mDBhelper.getWritableDatabase(); db.execSQL("delete from thread_info where url = ? and thread_id = ?", new Object[] { url, thread_id }); db.close(); } @Override public void updateThread(String url, int thread_id, int finished) { // TODO Auto-generated method stub Log.d("zsr", "finish: "+finished); SQLiteDatabase db = mDBhelper.getWritableDatabase(); db.execSQL( "update thread_info set finished = ? where url = ? and thread_id = ?", new Object[] { finished, url, thread_id }); db.close(); } public List<ThreadInfo> getThreads(String url) { List<ThreadInfo> mList = new ArrayList<ThreadInfo>(); SQLiteDatabase db = mDBhelper.getWritableDatabase(); Cursor cursor = db.rawQuery("select * from thread_info where url = ?", new String[] { url }); if (cursor.moveToFirst()) { do { ThreadInfo mThreadInfo = new ThreadInfo(); mThreadInfo.setUrl(cursor.getString(cursor .getColumnIndex("url"))); mThreadInfo.setId(cursor.getInt(cursor.getColumnIndex("thread_id"))); mThreadInfo.setStart(cursor.getInt(cursor .getColumnIndex("start"))); mThreadInfo.setEnd(cursor.getInt(cursor.getColumnIndex("end"))); mThreadInfo.setFinished(cursor.getInt(cursor .getColumnIndex("finished"))); mList.add(mThreadInfo); } while (cursor.moveToNext()); } db.close(); cursor.close(); return mList; } @Override public boolean isThreadExsits(String url, int thread_id) { boolean isExsits = false; SQLiteDatabase db = mDBhelper.getWritableDatabase(); Cursor cursor = db.rawQuery( "select * from thread_info where url = ? and thread_id = ?", new String[] { url, thread_id + "" }); isExsits = cursor.moveToNext(); db.close(); cursor.close(); return isExsits; } }
ok,这样,我们的续传的数据库就写好了,接下来就是对把线程信息给保存起来了;我们要修改一下我们的 DownloadTask 这个类:
首先,先初始化数据库:
private DBThreadListener mDbThreadListener; public DownloadTask(Context mContext) { this.mContext = mContext; mDbThreadListener = new DBThreadListenerImp(mContext); }
然后修改其中的download:
public void download(FileInfo fileInfo){ mFileInfo = fileInfo; List<ThreadInfo> threadInfos = mDbThreadListener.getThreads(fileInfo.getApkurl()); ThreadInfo mThreadInfo; if (threadInfos.size() == 0){ //第一次启动 mThreadInfo = new ThreadInfo(0,fileInfo.getApkurl(),0,fileInfo.getLength(),0,fileInfo.getLength()); }else{ mThreadInfo = threadInfos.get(0); } new DownloadThread(mThreadInfo).start(); }
然后修改DownloadThread:
class DownloadThread extends Thread{ ThreadInfo mThreadInfo; public DownloadThread(ThreadInfo threadinfo) { this.mThreadInfo = threadinfo; } @Override public void run() { super.run(); HttpURLConnection conn = null; RandomAccessFile raf = null; BufferedInputStream bis = null; //开始之前先检测是否数据库有线程信息 if (!mDbThreadListener.isThreadExsits(mThreadInfo.getUrl(),mThreadInfo.getId())){ mDbThreadListener.insertThread(mThreadInfo); //没有则把线程信息保存进去 } try { URL url = new URL(mThreadInfo.getUrl()); conn = (HttpURLConnection) url.openConnection(); //加上这句是为了防止connection.getContentLength()获取不到 conn.setRequestProperty("Accept-Encoding", "identity"); conn.setRequestMethod("GET"); conn.setReadTimeout(5000); conn.setConnectTimeout(5000); //设置下载位置 int start = mThreadInfo.getStart()+mThreadInfo.getFinished(); //起始位置 int end = mThreadInfo.getEnd(); //结束位置 conn.setRequestProperty("Range", "bytes="+start+"-"+end); //文件写入位置 File file = new File(mFileInfo.getDir(),mFileInfo.getFileName()); raf = new RandomAccessFile(file,"rwd"); raf.seek(start); int len = -1; downloadlength += len; //主要是暂停之后,重新下载,获取以前的进度 byte[] bytes = new byte[1024*2]; bis = new BufferedInputStream(conn.getInputStream()); while( (len = bis.read(bytes)) != -1 ){ raf.write(bytes,0,len); downloadlength += len; if (isPause) { //数据库保存当前信息 mDbThreadListener.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(), downloadlength); return; } //以百分比的形式传递 Log.d(TAG, "run: "+downloadlength*100/mFileInfo.getLength()); } //如果完成,则把以前的线程信息干掉 mDbThreadListener.deleteThread(mThreadInfo.getUrl(),mThreadInfo.getId()); } catch (Exception e) { e.printStackTrace(); Log.d(TAG, "downloaderror: "+e.toString()); }finally { if (bis != null) try { bis.close(); if (raf != null) raf.close(); } catch (IOException e) { e.printStackTrace(); } if (conn != null) conn.disconnect(); } } }
这样就完成了。
相关文章推荐
- 使文件下载的自定义连接支持 FlashGet 的断点续传多线程链接下载! C#/ASP.Net 实现
- http断点续传简单实现(java)
- 通过网页实现断点续传~!
- 使用.NET实现断点续传
- C#实现断点续传(转载)
- 一个实现FTP断点续传的类
- .Net/C#: 实现支持断点续传多线程下载的 Http Web 客户端工具类 (C# DIY HttpWebClient)
- 用Java实现断点续传
- 使用.NET实现断点续传
- 使用.NET实现断点续传(HTTP)
- 用Java实现断点续传(HTTP)转
- .Net/C#: 实现支持断点续传多线程下载的 Http Web 客户端工具类 (第2版) (C# DIY HttpWebClient)
- 用Java实现断点续传
- .Net/C#: 实现支持断点续传多线程下载的 Http Web 客户端工具类 (C# DIY HttpWebClient)
- 使文件下载的自定义连接支持 FlashGet 的断点续传多线程链接下载! JSP/Servlet 实现!
- 使文件下载的自定义连接支持 FlashGet 的断点续传多线程链接下载! C#/ASP.Net 实现!
- 一个实现FTP断点续传的类
- 实现支持断点续传多线程下载的 Http Web 客户端工具类()
- 在C++Builder下实现FTP断点续传
- 如何点对点实现多线程断点续传