Android实现多线程断点续传


本文整理自网络,侵删。

本文实例为大家分享了Android实现多线程断点续传的具体代码,供大家参考,具体内容如下

多线程下载涉及到的知识点:

1、Service的使用:我们在Service中去下载文件;
2、Thread的使用:Service本身不支持耗时操作,所以我们要去开启线程;
3、Sqlite的使用:使用数据库来存储每个线程下载的文件的进度,和文件的下载情况;
4、权限:涉及到文件的读写就要用到权限;
5、BroadCastReceiver的使用:通过广播来更新下载进度;
6、线程池使用:使用线程池来管理线程,减少资源的浪费
7、HttpUrlConnection的使用:下载文件使用的
8、ListView和BaseAdapter的使用:下载列表的显示
9、RandomAccessFile使用

先解释一下我们要做什么:

1、我们现在有一个文件,然后要分成好几个线程去下载,那么我们需要将这个文件平分,然后分给各个线程去下载,而每个线程在下载的时候,你不一定啥时候点了暂停,那么就要记录我的下载进度,所以要用到数据库。

2、你可能又会问,怎么去知道谁下载哪呢?我们的HttpURLConnection可以通过他的setRequestProperty()方法设置下载范围,从哪开始到哪结束。

3、同样下载解决了,那么写文件呢,怎么往文件里面写呢,那么就要用到RandomAccessFile这个文件的特性了,从文件的任意位置开始写,是不是清晰了。

4、 还有问题就是怎么更新界面,用我们的广播,告诉什么时候去更新界面。

(实现的效果,是一个文件可以由多个线程下载,可以同时下载多个文件)

**这里需要注意:**不可以在获取长度后直接去下载文件,因为,我们获取文件长度的时候需要使用的请求码是200,如果我们想要分段去下载(也就是设置了connection.setRequestProperty(“Range”,“bytes=”"之后就是分段下载了)那么使用到的请求码是206。所以我们这里要将这两个请求分开来写,我就一开始将两个写到一起了,但是是不可以的会报错,更不要想着通过请求码来区分,这个就更错了

1、下面贴出***服务类***的代码:

这里的工作主要就是开启下载任务和停止下载任务,还有就是获取下载文件的长度,并创建本地文件并设置长度。

public class DownLoadService extends Service {
? ? public static final int STATUS_START = 0;
? ? public static final int STATUS_STOP = 1;
? ? public static final String PATH = Environment.getExternalStorageDirectory().getAbsolutePath();
? ? private FileInfo mFileInfo;
? ? //统一管理DownLoadTask,有个文件下载就有个DownLoadTask,所以使用Map去管理,主要控制暂停
? ? private Map<Integer,Object> downtaskMap = new HashMap<>();
? ? private DownLoadTask downLoadTask;

? ? @Override
? ? public void onCreate() {
? ? ? ? super.onCreate();
? ? }

? ? @Override
? ? public int onStartCommand(Intent intent, int flags, int startId) {
? ? ? ? if (intent != null) {
? ? ? ? ? ? int status = intent.getIntExtra("status", 0);
? ? ? ? ? ? if (status == STATUS_START) {
? ? ? ? ? ? ? ? //开始下载
? ? ? ? ? ? ? ? mFileInfo = (FileInfo) intent.getSerializableExtra("fileinfo");
? ? ? ? ? ? ? ? DownLoadTask.sExecutorService.execute(new GetFileLenght(mFileInfo, this));

? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? //暂停下载
? ? ? ? ? ? ? ? mFileInfo = (FileInfo) intent.getSerializableExtra("fileinfo");
? ? ? ? ? ? ? ? Log.e("---------->","mFileInfo:"+mFileInfo);
? ? ? ? ? ? ? ? downLoadTask = (DownLoadTask) downtaskMap.get(mFileInfo.getId());
? ? ? ? ? ? ? ? if(downLoadTask!=null){
? ? ? ? ? ? ? ? ? ? downLoadTask.isPause = true;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }

? ? ? ? return super.onStartCommand(intent, flags, startId);
? ? }

? ? @Nullable
? ? @Override
? ? public IBinder onBind(Intent intent) {
? ? ? ? return null;
? ? }


? ? /**
? ? ?* 获得要下载的文件的长度,并创建本地文件
? ? ?* 不能和下载的线程写在一起
? ? ?*/
? ? class GetFileLenght extends Thread {
? ? ? ? private FileInfo fileInfo;
? ? ? ? private Context context;

? ? ? ? public GetFileLenght(FileInfo fileInfo, Context context) {
? ? ? ? ? ? this.fileInfo = fileInfo;
? ? ? ? ? ? this.context = context;
? ? ? ? }

? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? super.run();
? ? ? ? ? ? HttpURLConnection conn = null;
? ? ? ? ? ? RandomAccessFile raf = null;
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? URL url = new URL(fileInfo.getUrl());
? ? ? ? ? ? ? ? conn = (HttpURLConnection) url.openConnection();
? ? ? ? ? ? ? ? conn.setConnectTimeout(5000);
? ? ? ? ? ? ? ? conn.setRequestMethod("GET");

? ? ? ? ? ? ? ? int length = -1;
? ? ? ? ? ? ? ? if (conn.getResponseCode() == 200) {
? ? ? ? ? ? ? ? ? ? length = conn.getContentLength();
? ? ? ? ? ? ? ? ? ? if (length > 0) {
? ? ? ? ? ? ? ? ? ? ? ? //创建本地文件
? ? ? ? ? ? ? ? ? ? ? ? File file = new File(PATH, fileInfo.getFile_name());
? ? ? ? ? ? ? ? ? ? ? ? raf = new RandomAccessFile(file, "rwd");
? ? ? ? ? ? ? ? ? ? ? ? //设置本地文件的长度
? ? ? ? ? ? ? ? ? ? ? ? raf.setLength(length);
? ? ? ? ? ? ? ? ? ? ? ? fileInfo.setLength(length);
? ? ? ? ? ? ? ? ? ? ? ? //开始下载
? ? ? ? ? ? ? ? ? ? ? ? downLoadTask =new DownLoadTask(DownLoadService.this,fileInfo);
? ? ? ? ? ? ? ? ? ? ? ? downLoadTask.down();
? ? ? ? ? ? ? ? ? ? ? ? downtaskMap.put(fileInfo.getId(),downLoadTask);

? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? conn.disconnect();
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? if (raf != null) {
? ? ? ? ? ? ? ? ? ? ? ? raf.close();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }

}

2、DownLoadTask的代码,也就是真正的核心的地方

这里的关系是一个FileInfo对应一个DownLoadTask,一个DownLoadTask对应着多个线程

package com.example.a_0102.mylearn.download;

import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
?* 下载文件的内容
?*/

public class DownLoadTask {
? ? private Context context;
? ? private FileInfo fileInfo;
? ? private int countForThread = 3;//线程的数量
? ? private int mFinished;
? ? private DownLoadTaskImpl downLoadTask;
? ? private List<ThreadInfo> threadInfos;
? ? private List<DownLoadThread> downLoadThreads;
? ? public boolean isPause = false;
? ? public static ExecutorService sExecutorService = Executors.newCachedThreadPool();//共用一个线程池

? ? public DownLoadTask(Context context,FileInfo fileInfo) {
? ? ? ? this.fileInfo = fileInfo;
? ? ? ? this.context = context;
? ? ? ? downLoadTask = new DownLoadTaskImpl(context);
? ? }

? ? public void down(){
? ? ? ? threadInfos = downLoadTask.getThreadInfos(fileInfo.getUrl());
? ? ? ? if(threadInfos.size() == 0){
? ? ? ? ? ? mFinished = 0;
? ? ? ? ? ? //计算每个线程应下载的长度
? ? ? ? ? ? int every_length = fileInfo.getLength()/countForThread;
? ? ? ? ? ? for(int i = 0;i<countForThread;i++){
? ? ? ? ? ? ? ? ThreadInfo threadInfo = new ThreadInfo();
? ? ? ? ? ? ? ? threadInfo.setStart_flag(i*every_length);
? ? ? ? ? ? ? ? threadInfo.setEnd_flag((i+1)*every_length-1);
? ? ? ? ? ? ? ? threadInfo.setFinished(0);
? ? ? ? ? ? ? ? threadInfo.setUrl(fileInfo.getUrl());
? ? ? ? ? ? ? ? threadInfo.setThread_id(i);
? ? ? ? ? ? ? ? //可能不能平分,最后一个线程的长度为剩余的所有
? ? ? ? ? ? ? ? if(i == countForThread-1){
? ? ? ? ? ? ? ? ? ? threadInfo.setEnd_flag(fileInfo.getLength());
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? downLoadTask.insertThreadInfo(threadInfo);
? ? ? ? ? ? ? ? threadInfos.add(threadInfo);
? ? ? ? ? ? }
? ? ? ? }else {
? ? ? ? ? ? //该文件一共下载了多少了
? ? ? ? ? ? mFinished = fileInfo.getFinished();
? ? ? ? }

? ? ? ? downLoadThreads = new ArrayList<>();
? ? ? ? DownLoadThread downLoadThread = null;
? ? ? ? for(int i = 0;i<threadInfos.size();i++){
? ? ? ? ? ? downLoadThread = new DownLoadThread(threadInfos.get(i));
// ? ? ? ? ? ?downLoadThread.start();
? ? ? ? ? ? DownLoadTask.sExecutorService.execute(downLoadThread);//执行线程,相当于开启个线程使用这个就不需要使用.start方法
? ? ? ? ? ? downLoadThreads.add(downLoadThread);
? ? ? ? }
? ? }

? ? //真正开始下载文件的线程
? ? class DownLoadThread extends Thread{
? ? ? ? private ThreadInfo threadInfo;
? ? ? ? private boolean isFinished;//该线程是否结束

? ? ? ? public DownLoadThread(ThreadInfo threadInfo) {
? ? ? ? ? ? this.threadInfo = threadInfo;
? ? ? ? ? ? Log.e("------------->","threadInfo:"+threadInfo);
? ? ? ? }

? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? super.run();

? ? ? ? ? ? HttpURLConnection connection = null;
? ? ? ? ? ? RandomAccessFile accessFile = null;
? ? ? ? ? ? InputStream inputStream = null;
? ? ? ? ? ? Intent intent = new Intent();
? ? ? ? ? ? intent.setAction("UPDATE_PROGRESSBAR");
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? URL url = new URL(threadInfo.getUrl());
? ? ? ? ? ? ? ? connection = (HttpURLConnection) url.openConnection();
? ? ? ? ? ? ? ? connection.setRequestMethod("GET");
? ? ? ? ? ? ? ? connection.setConnectTimeout(5000);

?? ??? ??? ??? ?//下载开始的范围是,这个线程的开始下载的地方+已经下载的进度
? ? ? ? ? ? ? ? long start = threadInfo.getStart_flag()+threadInfo.getFinished();
? ? ? ? ? ? ? ? //设置下载的范围
? ? ? ? ? ? ? ? connection.setRequestProperty("Range","bytes="+start+"-"+threadInfo.getEnd_flag());
? ? ? ? ? ? ? ? File file = new File(DownLoadService.PATH,fileInfo.getFile_name());
? ? ? ? ? ? ? ? accessFile = new RandomAccessFile(file,"rwd");
? ? ? ? ? ? ? ? //设置文件写入位置
? ? ? ? ? ? ? ? accessFile.seek(start);
? ? ? ? ? ? ? ? int len = -1;
? ? ? ? ? ? ? ? byte[] bytes = new byte[1024];
? ? ? ? ? ? ? ? if(connection.getResponseCode() == 206){
? ? ? ? ? ? ? ? ? ? inputStream = connection.getInputStream();
? ? ? ? ? ? ? ? ? ? long time = System.currentTimeMillis();
? ? ? ? ? ? ? ? ? ? while ((len = inputStream.read(bytes))!=-1){
? ? ? ? ? ? ? ? ? ? ? ? accessFile.write(bytes,0,len);
? ? ? ? ? ? ? ? ? ? ? ? //文件整体的下载进度
? ? ? ? ? ? ? ? ? ? ? ? mFinished+=len;
? ? ? ? ? ? ? ? ? ? ? ? threadInfo.setFinished(threadInfo.getFinished()+len);
? ? ? ? ? ? ? ? ? ? ? ? //每1秒钟发送一个广播更新界面
? ? ? ? ? ? ? ? ? ? ? ? if(System.currentTimeMillis()-time>1000){
? ? ? ? ? ? ? ? ? ? ? ? ? ? time = System.currentTimeMillis();
? ? ? ? ? ? ? ? ? ? ? ? ? ? //以便区分下载的是那个文件
? ? ? ? ? ? ? ? ? ? ? ? ? ? intent.putExtra("id",fileInfo.getId());
? ? ? ? ? ? ? ? ? ? ? ? ? ? intent.putExtra("length",fileInfo.getLength());
? ? ? ? ? ? ? ? ? ? ? ? ? ? intent.putExtra("finished",mFinished);
? ? ? ? ? ? ? ? ? ? ? ? ? ? context.sendBroadcast(intent);
? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? //暂停更新数据库
? ? ? ? ? ? ? ? ? ? ? ? if(isPause){
? ? ? ? ? ? ? ? ? ? ? ? ? ? downLoadTask.updateThreadInfo(threadInfo,threadInfo.getThread_id(),threadInfo.getUrl());
? ? ? ? ? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? Log.e("------------>","线程结束:"+threadInfo.toString());
? ? ? ? ? ? ? ? ? ? isFinished = true;
? ? ? ? ? ? ? ? ? ? downLoadTask.updateThreadInfo(threadInfo,threadInfo.getThread_id(),threadInfo.getUrl());
? ? ? ? ? ? ? ? ? ? checkAllThreadFinish();
? ? ? ? ? ? ? ? }

? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }finally {
? ? ? ? ? ? ? ? connection.disconnect();
? ? ? ? ? ? ? ? if(inputStream!=null){
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? inputStream.close();
? ? ? ? ? ? ? ? ? ? ? ? accessFile.close();
? ? ? ? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }

? ? ? ? //所有的线程下载完成
? ? ? ? private synchronized void checkAllThreadFinish(){
? ? ? ? ? ? boolean finishAll = true;
? ? ? ? ? ? for(DownLoadThread downLoadThread:downLoadThreads){
? ? ? ? ? ? ? ? if(!downLoadThread.isFinished){
? ? ? ? ? ? ? ? ? ? finishAll = false;
? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? if(finishAll){
? ? ? ? ? ? ? ? downLoadTask.deleteThreadInfo(fileInfo.getUrl());
? ? ? ? ? ? ? ? //有些时候可能刚好下完,但是那1秒的时候没有取到所以进度可能停在97%,所以这样处理保证视觉的效果,可以直接将mFinished替换为fileInfo.getLength()。
? ? ? ? ? ? ? ? Intent intent = new Intent();
? ? ? ? ? ? ? ? intent.setAction("UPDATE_PROGRESSBAR");
? ? ? ? ? ? ? ? intent.putExtra("id",fileInfo.getId());
? ? ? ? ? ? ? ? intent.putExtra("length",fileInfo.getLength());
? ? ? ? ? ? ? ? intent.putExtra("finished",mFinished);
? ? ? ? ? ? ? ? context.sendBroadcast(intent);
? ? ? ? ? ? }
? ? ? ? }
? ? }

}

3、界面的代码

阅读剩余部分

相关阅读 >>

android开发笔记之android中数据的存储方式(一)

python实现从sql型数据库读写dataframe型数据的方法【基于pandas】

Sqlite3 top的查询及limit语法介绍

初识Sqlite3数据库

navicat图形化界面之navicatpremium12安装与使用教程

c#Sqlite数据库的搭建及使用技巧

Sqlite时间戳转时间语句(时间转时间戳)

android平台中实现数据存储的5种方式

python 连接各类主流数据库的实例代码

python编写通讯录通过数据库存储实现模糊查询功能

更多相关阅读请进入《Sqlite》频道 >>


数据库系统概念 第6版
书籍

数据库系统概念 第6版

机械工业出版社

本书主要讲述了数据模型、基于对象的数据库和XML、数据存储和查询、事务管理、体系结构等方面的内容。



打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...

    暂无评论...