博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android多线程+单线程+断点续传+进度条显示下载
阅读量:5812 次
发布时间:2019-06-18

本文共 17713 字,大约阅读时间需要 59 分钟。

#####效果图

#####白话分析: 多线程:肯定是多个线程咯 断点:线程停止下载的位置 续传:线程从停止下载的位置上继续下载,直到完成任务为止。

#####核心分析: ######断点: 当前线程已经下载的数据长度

######续传: 向服务器请求上次线程停止下载位置的数据

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,就是一个线程类,用于下载线程分配好的任务。后面会贴出具体代码。

######创建数据库方法类 无非就是单例模式,封装一些增删改查等基础数据库方法,后面会贴出具体代码。 ######创建实体类 也就是创建ThreadInfoFileInfo这2个实体类,把下载文件信息和线程信息暂时存储起来。 ######引入的第三方开源库 NumberProgressBar是一个关于进度条的开源库,挺不错的。

#####代码具体分析 1.首先是创建实体类,文件的实体类FileInfo,肯定有fileName,url,length,finised,isStop,isDownloading这些属性。线程的实体类ThreadInfo肯定有threadId,start,end,completed,url这些属性。这些都很简单

//ThreadInfo.javapublic class ThreadInfo {private String threadId;private int start;private int end;private int compeleted;private String url;public ThreadInfo(){}public ThreadInfo(String threadId, int start, int end, int compeleted, String url) {    this.threadId = threadId;    this.start = start;    this.end = end;    this.compeleted = compeleted;    this.url = url;}public String getThreadId() {    return threadId;}public void setThreadId(String threadId) {    this.threadId = threadId;}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 getCompeleted() {    return compeleted;}public void setCompeleted(int compeleted) {    this.compeleted = compeleted;}public String getUrl() {    return url;}public void setUrl(String url) {    this.url = url;}复制代码

}

//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;}@Overridepublic 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);}@Overridepublic void onCreate(SQLiteDatabase db) {    db.execSQL(CREATE_TABLE_SQL);}@Overridepublic 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()});}//关闭dbpublic 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
map = new HashMap<>();private static int mThreadNum;private int fileSize;private boolean flag = false; //true第一次下载 false不是第一次下载private List
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;//自己定义的线程Idprivate FileInfo info;private ThreadInfo threadInfo;public  boolean isCompleted=false; //true为当前线程完成任务,false为当前线程未完成任务//保存新的startpublic 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;}@Overridepublic 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();}复制代码

######结束 大致操作就是这样,其实多线程也挺简单的。

转载地址:http://citbx.baihongyu.com/

你可能感兴趣的文章
USB 通信原理
查看>>
7zZip zip RAR iOS
查看>>
date命令的详细用法!
查看>>
UiAutomator源码分析之UiAutomatorBridge框架
查看>>
python 开发之selenium
查看>>
Xcode3.2.5中找不到Mac OS X - Command Line Utility -...
查看>>
css的div垂直居中的方法,百分比div垂直居中
查看>>
如何理解EM算法
查看>>
nginx 域名跳转一例~~~(rewrite、proxy)
查看>>
linux用户家目录无损迁移到独立硬盘
查看>>
文件查找
查看>>
shell编程前言(一)
查看>>
5、centos7.*配置yum的EPEL源及其它源
查看>>
JSON前后台简单操作
查看>>
shell中一些常见的文件操作符
查看>>
CentOS 7 装vim遇到的问题和解决方法
查看>>
JavaScript基础教程1-20160612
查看>>
使用第三方类、库需要注意的正则类RegexKitLite的使用
查看>>
iOS \U7ea2 乱码 转换
查看>>
FCN图像分割
查看>>