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多线程+单线程+断点续传+进度条显示下载功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

时间: 2024-09-17 00:11:27

Android多线程+单线程+断点续传+进度条显示下载功能的相关文章

Android自定义多节点进度条显示的实现代码(附源码)

亲们里面的线段颜色和节点图标都是可以自定义的. 在没给大家分享实例代码之前,先给大家展示下效果图: main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rl_parent" xmlns:tools="http://schemas.android.com/tools" android:layou

利用curl下载文件(进度条显示) 代码片段

在项目中需要用到程序更新的功能,同事介绍说是curl中的开发库很牛x,又是跨平台(他们 总是这么喜欢跨平台的东西 *_*),于是下载这个包测试了一下,确实不错.准备正式用到项 目中,以下一个例子用于从互联网上抓取一个文件下载到本地,并加上进度条显示,做得挺 简陋,不过功能差不多就这样了. 程序运行预览. 首先需要 加入多线程的机制,因为程序一边在下载文件,一边在显示进度条,单线程的方式肯定不行 ,所以我用到了wxTimer来实现,在downloadMain.h 中定义了一个wxTimer,并做了

Android带进度条的下载图片示例(AsyncTask异步任务)

为什么要用异步任务? 在Android中只有在主线程才能对ui进行更新操作,而其它线程不能直接对ui进行操作 android本身是一个多线程的操作系统,我们不能把所有的操作都放在主线程中操作 ,比如一些耗时操作.如果放在主线程中 会造成阻塞 而当阻塞事件过长时 系统会抛出anr异常.所以我们要使用异步任务.android为我们提供了一个封装好的组件asynctask. AsyncTask可以在子线程中更新ui,封装简化了异步操作.适用于简单的异步处理.如果多个后台任务时就要使用Handler了

软件-java程序设计 下载 进度条显示

问题描述 java程序设计 下载 进度条显示 当我在一个页面点击下载下载软件时,能否将名称和进度条显示在另一个页面 我需要用java实现程序设计,如果可以的话麻烦贴出代码,在百度上看了很多, 没有找到可以使用的,谢谢了 解决方案 天生java做activex也很困难.不如用C++ 解决方案二: 如果是基于浏览器的话,不可以实现.因为服务器端程序不能直接控制浏览器读写文件,也不能获知下载进度. 除非你在客户端部署程序,比如activex控件. 解决方案三: 迅雷就是我说的,在客户端部署程序实现的.

Android实现支持进度条显示的短信备份工具类_Android

使用内容提供者读取短信内容,写入XML文件,进度条ProgressDialog更新备份进度.新知识点:子线程如何在在不使用Handler的情况下更新UI /** * 进行短信备份的工具类,支持进度条显示 * @author lian * */ public class SmsBackupUtils { private static class Data{ int progress; } /** * * @param context * 调用此工具类的Activity * @param pd *

Android 进度条显示在标题栏的实现方法

好吧,先给大家展示效果图: xml文件: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" > <Butt

Android实现支持进度条显示的短信备份工具类

使用内容提供者读取短信内容,写入XML文件,进度条ProgressDialog更新备份进度. 新知识点:子线程如何在在不使用Handler的情况下更新UI /** * 进行短信备份的工具类,支持进度条显示 * @author lian * */ public class SmsBackupUtils { private static class Data{ int progress; } /** * * @param context * 调用此工具类的Activity * @param pd *

php+ajax实现带进度条的上传图片功能【附demo源码下载】_php技巧

本文实例讲述了php+ajax实现带进度条的上传图片功能.分享给大家供大家参考,具体如下: 运行效果图如下: 代码如下: <?php if(isset($_FILES["FileInput"]) && $_FILES["FileInput"]["error"]== UPLOAD_ERR_OK) { ############ Edit settings ############## $UploadDirectory = 'F:

android项目实现带进度条的系统通知栏消息_Android

我们在做Android开发的时候经常会遇到后台线程执行的比如说下载文件的时候,这个时候我们希望让客户能看到后台有操作进行,这时候我们就可以使用进度条,那么既然在后台运行,为的就是尽量不占用当前操作空间,用户可能还要进行其他操作,最好的方法就是在通知栏有个通知消息并且有个进度条.本文给一个例子工读者参考. 效果图如下: 主界面只有一个按钮就不上文件了 通知栏显示所用到的布局文件content_view.xml <?xml version="1.0" encoding="u