Android多线程+单线程+断点续传+进度条显示下载功能

作者:cmazxiaoma 时间:2023-08-13 15:48:26 

效果图

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多线程+单线程+断点续传+进度条显示下载功能网站的支持!

来源:http://blog.csdn.net/qq_22791091/article/details/72830470

标签:android,线程,断点续传,进度条下载
0
投稿

猜你喜欢

  • springboot+jwt+springSecurity微信小程序授权登录问题

    2022-10-13 03:36:22
  • 深入探讨JAVA中的异常与错误处理

    2023-06-11 00:30:24
  • Map集合之HashMap的使用及说明

    2022-07-23 15:56:56
  • Android RecyclerView加载不同布局简单实现

    2022-07-15 16:57:09
  • Netty分布式pipeline管道传播outBound事件源码解析

    2022-10-17 23:43:06
  • Android Studio使用recyclerview实现展开和折叠功能(在之前的微信页面基础之上)

    2023-10-31 23:08:55
  • C#移除字符串中的不可见Unicode字符 案例代码

    2023-04-28 19:06:06
  • Android自定义View实现比赛时间闪动效果

    2023-02-17 02:29:32
  • C#中Monitor对象与Lock关键字的区别分析

    2022-01-13 02:50:27
  • Java程序命令行参数用法总结

    2022-09-22 11:09:32
  • Java中的5种同步辅助类介绍

    2023-11-14 21:36:28
  • Springboot 使用maven release插件执行版本管理及打包操作

    2023-07-12 01:20:35
  • java中@SuppressWarnings注解用法详解

    2023-09-20 23:11:40
  • jdk8使用stream实现两个list集合合并成一个(对象属性的合并)

    2023-08-05 16:49:30
  • Android图片翻转动画简易实现代码

    2023-03-12 02:33:35
  • android studio实现简单的计算器小功能

    2022-07-22 17:53:26
  • Android自定义StickinessView粘性滑动效果

    2022-11-22 08:56:20
  • 深入理解Java设计模式之代理模式

    2022-01-14 07:42:00
  • C++调试追踪class成员变量的方法

    2022-11-21 01:32:12
  • 基于C#的UDP协议的同步通信实现代码

    2023-11-14 09:13:58
  • asp之家 软件编程 m.aspxhome.com