Android多线程+单线程+断点续传+进度条显示下载功能
效果图
白话分析:
多线程:肯定是多个线程咯
断点:线程停止下载的位置
续传:线程从停止下载的位置上继续下载,直到完成任务为止。
核心分析:
断点:
当前线程已经下载的数据长度
续传:
向服务器请求上次线程停止下载位置的数据
con.setrequestproperty("range", "bytes=" + start + "-" + end);
分配线程:
int currentpartsize = filesize / mthreadnum;
定义位置
定义线程开始下载的位置和结束的位置
for (int i = 0; i < mthreadnum; i++) { int start = i * currentpartsize;//计算每条线程下载的开始位置 int end = start + currentpartsize-1;//线程结束的位置 if(i==mthreadnum-1){ end=filesize; }}
创建数据库:
由于每一个文件要分成多个部分,要被不同的线程同时进行下载。当然要创建线程表,保存当前线程下载开始的位置和结束的位置,还有完成进度等。创建file表,保存当前下载的文件信息,比如:文件名,url,下载进度等信息
线程表:
public static final string create_table_sql="create table "+table_name+"(_id integer primary " +"key autoincrement, threadid, start , end, completed, url)";
file表:
public static final string create_table_sql="create table "+table_name+"(_id integer primary" + " key autoincrement ,filename, url, length, finished)";
创建线程类
无非就2个类,一个是线程管理类downloadmanager.java,核心方法:start(),stop(),restart(),addtask().clear()。另一个是线程任务类
downloadtask.java,就是一个线程类,用于下载线程分配好的任务。后面会贴出具体代码。
创建数据库方法类
无非就是单例模式,封装一些增删改查等基础数据库方法,后面会贴出具体代码。
创建实体类
也就是创建threadinfo和fileinfo这2个实体类,把下载文件信息和线程信息暂时存储起来。
引入的第三方开源库
numberprogressbar是一个关于进度条的开源库,挺不错的。直达链接
代码具体分析
1.首先是创建实体类,文件的实体类fileinfo,肯定有filename,url,length,finised,isstop,isdownloading这些属性。线程的实体类threadinfo肯定有threadid,start,end,completed,url这些属性。这些都很简单
//thredinfo.java public class fileinfo { private string filename; //文件名 private string url; //下载地址 private int length; //文件大小 private int finished; //下载已完成进度 private boolean isstop=false; //是否暂停下载 private boolean isdownloading=false; //是否正在下载 public fileinfo(){ } public fileinfo(string filename,string url){ this.filename=filename; this.url=url; } public string getfilename() { return filename; } public void setfilename(string filename) { this.filename = filename; } public string geturl() { return url; } public void seturl(string url) { this.url = url; } 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 boolean isstop() { return isstop; } public void setstop(boolean stop) { isstop = stop; } public boolean isdownloading() { return isdownloading; } public void setdownloading(boolean downloading) { isdownloading = downloading; } @override public string tostring() { return "fileinfo{" + "filename='" + filename + '\'' + ", url='" + url + '\'' + ", length=" + length + ", finished=" + finished + ", isstop=" + isstop + ", isdownloading=" + isdownloading + '}'; }} //fileinfo.java public class fileinfo { private string filename; //文件名 private string url; //下载地址 private int length; //文件大小 private int finished; //下载已完成进度 private boolean isstop=false; //是否暂停下载 private boolean isdownloading=false; //是否正在下载 public fileinfo(){ } public fileinfo(string filename,string url){ this.filename=filename; this.url=url; } public string getfilename() { return filename; } public void setfilename(string filename) { this.filename = filename; } public string geturl() { return url; } public void seturl(string url) { this.url = url; } 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 boolean isstop() { return isstop; } public void setstop(boolean stop) { isstop = stop; } public boolean isdownloading() { return isdownloading; } public void setdownloading(boolean downloading) { isdownloading = downloading; } @override public string tostring() { return "fileinfo{" + "filename='" + filename + '\'' + ", url='" + url + '\'' + ", length=" + length + ", finished=" + finished + ", isstop=" + isstop + ", isdownloading=" + isdownloading + '}'; }}
2.实体类写完了,那么接下来写创建一个类,继承sqliteopenhelper类,来管理数据库连接,主要作用:管理数据库的初始化,并允许应用程序通过该类获取sqlitedatabase对象。
public class threadhelper extends sqliteopenhelper{ public static final string table_name="downthread"; public static final string create_table_sql="create table "+table_name+"(_id integer primary " +"key autoincrement, threadid, start , end, completed, url)"; public threadhelper(context context, string name, int version) { super(context, name, null, version); } @override public void oncreate(sqlitedatabase db) { db.execsql(create_table_sql); } @override public void onupgrade(sqlitedatabase db, int oldversion, int newversion) { }}
3.接下来封装一些数据库的增删改查操作,用的单例模式,用双重检验锁实现单例。好处:既能很大程度上确保线程安全,又能实现延迟加载。 缺点:使用volatile关键字会使jvm对该代码的优化丧失,影响性能。并且在一些高并发的情况,仍然可能会创建多个实例,这称为双重检验锁定失效。单例模式
public class thread { private sqlitedatabase db; public static final string db_name="downthread.db3"; public static final int version=1; private context mcontext; private volatile static thread t=null; private thread(){ mcontext= baseapplication.getcontext(); db=new threadhelper(mcontext,db_name,version).getreadabledatabase(); } public static thread getinstance(){ if(t==null){ synchronized (thread.class){ if(t==null){ t=new thread(); } } } return t; } public sqlitedatabase getdb(){ return db; } //保存当前线程下载进度 public synchronized void insert(threadinfo threadinfo){ contentvalues values=new contentvalues(); values.put("threadid",threadinfo.getthreadid()); values.put("start",threadinfo.getstart()); values.put("end",threadinfo.getend()); values.put("completed",threadinfo.getcompeleted()); values.put("url",threadinfo.geturl()); long rowid=db.insert(threadhelper.table_name,null,values); if(rowid!=-1){ utilslog.i("插入线程记录成功"); }else{ utilslog.i("插入线程记录失败"); } } //查询当前线程 下载的进度 public synchronized threadinfo query(string threadid,string queryurl){ cursor cursor=db.query(threadhelper.table_name,null,"threadid= ? and url= ?",new string[]{threadid,queryurl},null,null,null); threadinfo info=new threadinfo(); if(cursor!=null){ while (cursor.movetonext()){ int start=cursor.getint(2); int end=cursor.getint(3); int completed=cursor.getint(4); string url=cursor.getstring(5); info.setthreadid(threadid); info.setstart(start); info.setend(end); info.setcompeleted(completed); info.seturl(url); } cursor.close(); } return info; } //更新当前线程下载进度 public synchronized void update(threadinfo info){ contentvalues values=new contentvalues(); values.put("start",info.getstart()); values.put("completed",info.getcompeleted()); db.update(threadhelper.table_name,values,"threadid= ? and url= ?",new string[]{info.getthreadid(),info.geturl()}); } //关闭db public void close(){ db.close(); } //判断多线程任务下载 是否第一次创建线程 public boolean isexist(string url){ cursor cursor=db.query(threadhelper.table_name,null,"url= ?",new string[]{url},null,null,null); boolean isexist=cursor.movetonext(); cursor.close(); return isexist; } public synchronized void delete(threadinfo info){ long rowid=db.delete(threadhelper.table_name,"url =? and threadid= ?",new string[]{info.geturl(),info.getthreadid()}); if(rowid!=-1){ utilslog.i("删除下载线程记录成功"); }else{ utilslog.i("删除下载线程记录失败"); } } public synchronized void delete(string url){ long rowid=db.delete(threadhelper.table_name,"url =? ",new string[]{url}); if(rowid!=-1){ utilslog.i("删除下载线程记录成功"); }else{ utilslog.i("删除下载线程记录失败"); } }}
4.基本的准备操作我们已经完成了,那么开始写关于下载的类吧。首先写的肯定是downloadmanager类,就是管理任务下载的类。不多说,直接看代码。
public class downloadmanager { private map<string, fileinfo> map = new hashmap<>(); private static int mthreadnum; private int filesize; private boolean flag = false; //true第一次下载 false不是第一次下载 private list<downloadtask> threads; private static fileinfo minfo; private static resultlistener mlistener; public static executorservice executorservice = executors.newcachedthreadpool(); public static file file; private int totalcomleted; private downloadmanager() { threads = new arraylist<>(); } public static downloadmanager getinstance(fileinfo info, int threadnum,resultlistener listener) { mlistener = listener; mthreadnum = threadnum; minfo = info; return downloadmanagerholder.dlm; } private static class downloadmanagerholder { private static final downloadmanager dlm = new downloadmanager(); } public void start() { totalcomleted=0; clear(); final fileinfo newinfo = download.getinstance().querydata(minfo.geturl()); newinfo.setdownloading(true); map.put(minfo.geturl(),newinfo); prepare(newinfo); } //停止下载任务 public void stop() { map.get(minfo.geturl()).setdownloading(false); map.get(minfo.geturl()).setstop(true); } public void clear(){ if(threads.size()>0){ threads.clear(); } } //重新下载任务 public void restart() { stop(); try { file file = new file(com.cmazxiaoma.downloader.download.downloadmanager.file_path, map.get(minfo.geturl()).getfilename()); if (file.exists()) { file.delete(); } java.lang.thread.sleep(100); } catch (interruptedexception e) { e.printstacktrace(); } download.getinstance().resetdata(minfo.geturl()); start(); } //获取当前任务状态, 是否在下载 public boolean getcurrentstate() { return map.get(minfo.geturl()).isdownloading(); } //添加下载任务 public void addtask(fileinfo info) { //判断数据库是否已经存在此下载信息 if (!download.getinstance().isexist(info)) { download.getinstance().insertdata(info); map.put(info.geturl(), info); } else { download.getinstance().delete(info); download.getinstance().insertdata(info); utilslog.i("map已经更新"); map.remove(info.geturl()); map.put(info.geturl(), info); } } private void prepare(final fileinfo newinfo) { new java.lang.thread(){ @override public void run() { httpurlconnection con = null; randomaccessfile raf=null; try { //连接资源 url url = new url(newinfo.geturl()); utilslog.i("url=" + url); con = (httpurlconnection) url.openconnection(); con.setconnecttimeout(2 * 1000); con.setrequestmethod("get"); int length = -1; utilslog.i("responsecode=" + con.getresponsecode()); if (con.getresponsecode() == 200) { length = con.getcontentlength(); utilslog.i("文件大小=" + length); } if (length <= 0) { return; } //创建文件保存路径 file dir = new file(com.cmazxiaoma.downloader.download.downloadmanager.file_path); if (!dir.exists()) { dir.mkdirs();//建立多级文件夹 } newinfo.setlength(length); filesize = length; utilslog.i("当前线程id=" + java.lang.thread.currentthread().getid() + ",name=" + java.lang.thread.currentthread().getname()); int currentpartsize = filesize / mthreadnum; file = new file(com.cmazxiaoma.downloader.download.downloadmanager.file_path, newinfo.getfilename()); raf = new randomaccessfile(file, "rwd"); raf.setlength(filesize); if (thread.getinstance().isexist(newinfo.geturl())) { flag = false; } else { flag = true; } for (int i = 0; i < mthreadnum; i++) { if (flag) { utilslog.i("第一次多线程下载"); int start = i * currentpartsize;//计算每条线程下载的开始位置 int end = start + currentpartsize-1;//线程结束的位置 if(i==mthreadnum-1){ end=filesize; } string threadid = "xiaoma" + i; threadinfo threadinfo = new threadinfo(threadid, start, end, 0,newinfo.geturl()); thread.getinstance().insert(threadinfo); downloadtask thread = new downloadtask(threadinfo,newinfo, threadid, start, end, 0); downloadmanager.executorservice.execute(thread); threads.add(thread); } else { utilslog.i("不是第一次多线程下载"); threadinfo threadinfo = thread.getinstance().query("xiaoma" + i, newinfo.geturl()); downloadtask thread = new downloadtask(threadinfo,newinfo,threadinfo.getthreadid(),threadinfo.getstart(),threadinfo.getend(),threadinfo.getcompeleted());//这里出现过问题 downloadmanager.executorservice.execute(thread); threads.add(thread); } } boolean iscompleted=false; while(!iscompleted){ iscompleted=true; for(downloadtask thread:threads){ totalcomleted+=thread.completed; if(!thread.iscompleted){ iscompleted=false; } } if(newinfo.isstop()){ totalcomleted=0; return; } message message=new message(); message.what=0x555; message.arg1=filesize; message.arg2=totalcomleted; handler.sendmessage(message); if(iscompleted){ totalcomleted=0; //任务线程全部完成,清空集合 clear(); handler.sendemptymessage(0x666); return; } totalcomleted=0; java.lang.thread.sleep(1000); } }catch (exception e) { e.printstacktrace(); }finally { try { if (con != null) { con.disconnect(); } if(raf!=null){ raf.close(); } } catch (ioexception e) { e.printstacktrace(); } } } }.start(); } private handler handler=new handler(){ @override public void handlemessage(message msg) { super.handlemessage(msg); switch (msg.what){ case 0x555: if(mlistener!=null){ mlistener.progress(msg.arg1,msg.arg2); } break; case 0x666: if(mlistener!=null){ mlistener.comleted(); } break; } } };}
5.接下来呢,就是downloadtask类了,就是一个线程下载类。
public class downloadtask extends java.lang.thread{ private int start;//当前线程的开始下载位置 private int end;//当前线程结束下载的位置 private randomaccessfile raf;//当前线程负责下载的文件大小 public int completed=0;//当前线程已下载的字节数 private string threadid;//自己定义的线程id private fileinfo info; private threadinfo threadinfo; public boolean iscompleted=false; //true为当前线程完成任务,false为当前线程未完成任务 //保存新的start public int finshed=0; public int newstart=0; public downloadtask(threadinfo threadinfo,fileinfo info,string threadid, int start, int end,int completed){ this.threadinfo=threadinfo; this.info=info; this.threadid=threadid; this.start=start; this.end=end; this.completed=completed; } @override public void run() { httpurlconnection con = null; try { utilslog.i("start="+start+",end="+end+",completed="+completed+",threadid="+getthreadid()); url url = new url(info.geturl()); con = (httpurlconnection) url.openconnection(); con.setconnecttimeout(2 * 1000); con.setrequestmethod("get"); con.setrequestproperty("range", "bytes=" + start + "-"+end);//重点 raf=new randomaccessfile(downloadmanager.file,"rwd"); //从文件的某一位置写入 raf.seek(start); if (con.getresponsecode() == 206) { //文件部分下载 返回码是206 inputstream is = con.getinputstream(); byte[] buffer = new byte[4096]; int hasread = 0; while ((hasread = is.read(buffer)) != -1) { //写入文件 raf.write(buffer, 0, hasread); //单个文件的完成程度 completed += hasread; threadinfo.setcompeleted(completed); //保存新的start finshed=finshed+hasread;//这里出现过问题,嘻嘻 newstart=start+finshed; threadinfo.setstart(newstart); //utilslog.i("thread:"+getthreadid()+",completed=" + completed); //停止下载 if (info.isstop()) { utilslog.i("isstop="+info.isstop()); //保存下载进度 utilslog.i("现在thread:"+getthreadid()+",completed=" + completed); thread.getinstance().update(threadinfo); return; } } //删除该线程下载记录 thread.getinstance().delete(threadinfo); iscompleted=true; thread.getinstance().update(threadinfo); utilslog.i("thread:"+getthreadid()+"已经完成任务!--"+"completed="+completed); } } catch (exception e) { if (con != null) { con.disconnect(); } try { if (raf != null) { raf.close(); } } catch (ioexception e1) { e1.printstacktrace(); } } } public string getthreadid() { return threadid; }}
6.接口,就是一个监听下载进度的接口,也是很简单。
public interface resultlistener{ void progress(int max, int progress); void comleted();}
结束
大致操作就是这样,其实多线程也挺简单的。
以上所述是小编给大家介绍的android多线程+单线程+断点续传+进度条显示下载功能,希望对大家有所帮助
上一篇: ddb是什么格式?ddb文件怎么打开?ddb打开方法介绍
下一篇: 通用JS事件写法实现代码