Android多线程下载断点续传

先上图看卡结果:

GITHUB:Android多线程下载断点续传

如图所示点击下载就开始下载,点击停止就会停止再次点击下载就会接着下载了。

设计思路是这样的:

首先通过广播将下载信息传递给DownService,DownService根据文件URL获取文件大小,再通过DownTask将下载任务分配,并且通过广播当点击停止下载时将下载进度保存在数据库中,当点击开始下载时再从数据库中获取到保存的进度,继续下载。

代码结构:

核心类是 DownLoadService,java 和DownTask.java将这两个类贴出来:

package com.example.downloaddemo.services;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.http.HttpStatus;
import com.example.downloaddemo.enties.FileInfo;
import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

public class DownLoadService extends Service {
    // 设置存储路劲
    public static final String DOWN_PATH = Environment
            .getExternalStorageDirectory().getAbsolutePath() + "/downloads";
    public static final String ACTION_START = "ACTION_START";
    public static final String ACTION_STOP = "ACTION_STOP";
    public static final String ACTION_FINISH = "ACTION_FINISH";
    public static final String ACTION_UPDATE = "ACTION_UPDATE";
    public static final int MSG_INIT = 0;
    // private DownLoadTask mDownLoadTask=null;
    private Map<Integer, DownLoadTask> mTasks = new LinkedHashMap<Integer, DownLoadTask>();
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 获取Activity传来的数据
        if (ACTION_START.equals(intent.getAction())) {
            FileInfo fileinfo = (FileInfo) intent
                    .getSerializableExtra("fileinfo");
            InitThread minitThread=new InitThread(fileinfo);
            DownLoadTask.sExecutorService.execute(minitThread);
            Log.i("test", "start:" + fileinfo.toString());
        } else if (ACTION_STOP.equals(intent.getAction())) {
FileInfo fileinfo = (FileInfo) intent
    .getSerializableExtra("fileinfo");
            // 从集合中获取下载任务
    DownLoadTask task = mTasks.get(fileinfo.getId());
            if (task != null) {
                // 停止下载任务
                task.isPause = true;
            }
            Log.i("test", "stop:" + fileinfo.toString());
        }
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public IBinder onBind(Intent arg0) {
        // TODO Auto-generated method stub
        return null;
    }
    Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            case MSG_INIT:
            FileInfo info = (FileInfo) msg.obj;
            Log.i("test", "init:" + info.toString());
        // 开启下载任务,默认为三个线程下载
        DownLoadTask task = new DownLoadTask(DownLoadService.this,info, 3);
        task.downLoad();
        // 把下载任务添加到集合中
        mTasks.put(info.getId(), task);
        break;
        default:
        break;
            }
        }
    };
    /**
     *
     * 初始化子线程
     */
    class InitThread extends Thread {
        private FileInfo mfileInfo = null;
        public InitThread(FileInfo mfileInfo) {
            super();
            this.mfileInfo = mfileInfo;
        }
        @Override
        public void run() {
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            try {
                // 连接网络文件
                URL url = new URL(mfileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int length = -1;
                // 获取文件长度
    if (conn.getResponseCode() == HttpStatus.SC_OK) {
                    length = conn.getContentLength();
                }
                if (length <= 0) {
                    return;
                }
                File dir = new File(DOWN_PATH);
                if (!dir.exists()) {
                    dir.mkdir();
                }
                // 在本地文件并设置长度
    File file = new File(dir,mfileInfo.getFileName());
                // 特殊的输出流能够 任任意位置写入
                raf = new RandomAccessFile(file,"rwd");
                // 设置本地文件的长度
                raf.setLength(length);
                mfileInfo.setLength(length);
                mHandler.obtainMessage(MSG_INIT, mfileInfo).sendToTarget();
            } catch (Exception e) {
                // TODO: handle exception
            } finally {
                try {
                    // 关闭流操作和网络连接操作
                    raf.close();
                    conn.disconnect();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            super.run();
        }

    }

}

下面是DownLoadTask.java的代码:

package com.example.downloaddemo.services;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.http.HttpStatus;
import com.example.downloaddemo.db.ThreadDAO;
import com.example.downloaddemo.db.ThreadDAOImpl;
import com.example.downloaddemo.enties.FileInfo;
import com.example.downloaddemo.enties.ThreadInfo;
import android.content.Context;
import android.content.Intent;
/**
 *
 * 下载任务
 *
 */
public class DownLoadTask {
    private Context mContext = null;
    private FileInfo mFileInfo = null;
    private ThreadDAO mDao = null;
    private int mfinished = 0;
    public boolean isPause = false;
    private int threadCount = 1;// 线程数量
    private List<DownLoad> mThreadList = null;// 线程集合方便管理分段下载线程
//使用带缓存型池子,先查看池中有没有以前建立的线程,如果有,
//就reuse.如果没有,就建一个新的线程加入池中
//缓存型池子通常用于执行一些生存期很短的异步型任务,
//能reuse的线程,必须是timeout IDLE内的池中线程,
//缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
    public static ExecutorService sExecutorService=Executors.newCachedThreadPool();
    public DownLoadTask(Context mContext, FileInfo mFileInfo, int threadCount) {
        super();
        this.mContext = mContext;
        this.mFileInfo = mFileInfo;
        mDao = new ThreadDAOImpl(mContext);
    }
    public void downLoad() {
        // 读取数据库的线程信息
List<ThreadInfo> mThreadInfos =    mDao.getThreads(mFileInfo.getUrl());
        if (mThreadInfos.size() == 0) {
            // 获取每个线程下载的长度
            int length = mFileInfo.getLength() / threadCount;
            // 创建线程下载信息
            for (int i = 0; i < threadCount; i++) {
            ThreadInfo threadInfo = new ThreadInfo(i, mFileInfo.getUrl(), i* length, (i + 1) * length, 0);
                if (i == threadCount - 1) {
                    threadInfo.setEnd(mFileInfo.getLength());
                }
                // 添加到线程信息集合中
                mThreadInfos.add(threadInfo);
                // 向数据库中插入线程信息
                mDao.insertThread(threadInfo);
            }
        }
        mThreadList = new ArrayList<DownLoadTask.DownLoad>();
        // 启动多个线程来下载
        for (ThreadInfo info : mThreadInfos) {
            DownLoad download = new DownLoad(info);
    DownLoadTask.sExecutorService.execute(download);
            mThreadList.add(download);
        }
    }
    /**
     * 判断下载线程是否都下载完毕
     */
    private synchronized void checkAllThreadsFinished() {
        boolean allFinished = true;
        for (DownLoad download : mThreadList) {
            if (!download.isfinished) {
            allFinished = false;
            break;
            }
        }
        if (allFinished) {
            // 下载完毕删除线程信息
            mDao.deleteThread(mFileInfo.getUrl());
            // 发送广播到Activity
            Intent intent = new Intent(DownLoadService.ACTION_FINISH);
            intent.putExtra("fileInfo", mFileInfo);
            mContext.sendBroadcast(intent);
        }
    }
    class DownLoad extends Thread {
        private ThreadInfo mThreadInfo = null;
        public boolean isfinished = false;// 表示线程是否下载完毕
        public DownLoad(ThreadInfo mThreadInfo) {
            super();
            this.mThreadInfo = mThreadInfo;
        }
        @Override
        public void run() {
            // 打开连接
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            InputStream ins = null;
            try {
                URL url = new URL(mThreadInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
                // 设置下载位置
                conn.setRequestProperty("Range",
                        "bytes=" + "-" + mThreadInfo.getEnd());
                // 设置文件写入位置
                File file = new File(DownLoadService.DOWN_PATH,
                        mFileInfo.getFileName());

                raf = new RandomAccessFile(file, "rwd");
                //移动到指定位置
                raf.seek(start);
                Intent intent = new Intent(DownLoadService.ACTION_UPDATE);
                mfinished += mThreadInfo.getFinished();
                // 开始下载
                if (conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
                    // 读取数据
                    ins = conn.getInputStream();
                    byte[] buffer = new byte[1024 * 4];
                    int length = -1;
            long time = System.currentTimeMillis();
            while ((length = ins.read(buffer)) != -1) {
                        // 写入文件
                        raf.write(buffer, 0, length);
                        // 把下载进度发送广播更新UI
                        // 累加整文件完成进度
                        mfinished += length;
                // 累加每个线程完成的进度mThreadInfo.setFinished(mThreadInfo.getFinished()+ length);
        if (System.currentTimeMillis() - time > 1000) {
    time = System.currentTimeMillis();
    intent.putExtra("finished", mfinished * 100
    / mFileInfo.getLength());
    intent.putExtra("id", mFileInfo.getId());
        mContext.sendBroadcast(intent);
    }
        // 下载暂停保存进度
    if (isPause) {
                            mDao.updateThread(mThreadInfo.getUrl(),
                                    mThreadInfo.getId(),
                                    mThreadInfo.getFinished());
                return;
            }
}
                    isfinished = true;
                    // 检查下载任务是否完成
                    checkAllThreadsFinished();
                }

            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                try {
                    conn.disconnect();
                    ins.close();
                    raf.close();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            super.run();
        }
    }

}

没写之前我在想多线程下载之后如何下载下来的刷数据拼接在一起,后来查看JavaAPI之后使用了用Java的RandomAccessFile操作就可以写入到指定的位,DownLoadService获取长度后,就在存储的位置创建该文件。

HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            try {
                // 连接网络文件
                URL url = new URL(mfileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int length = -1;
                // 获取文件长度
                if (conn.getResponseCode() == HttpStatus.SC_OK) {
                    length = conn.getContentLength();
                }
                if (length <= 0) {
                    return;
                }
                File dir = new File(DOWN_PATH);
                if (!dir.exists()) {
                    dir.mkdir();
                }
                // 在本地文件并设置长度
                File file = new File(dir, mfileInfo.getFileName());
                // 特殊的输出流能够 任任意位置写入
                raf = new RandomAccessFile(file, "rwd");
                // 设置本地文件的长度
                raf.setLength(length);
                mfileInfo.setLength(length);
                mHandler.obtainMessage(MSG_INIT, mfileInfo).sendToTarget();

在写入文件时:不同的线程获取对应的数据写在对应的文件位置就可以了不存在拼接问题。举个栗子:

T1下载0–5;T2下载6—-10;T3下载11—-15,就可以啦下载完写到对应得位置即可。

在写入的时候设置么每写入 byte[1024 * 4],通知UI更新进度条,并进行总的进度累加,下载完后在判断多个线程下载一个文件是不是都下载完成了,如果下载完成了就通知弹出下载完成提示。

核心代码如下:

// 打开连接
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            InputStream ins = null;
            try {
                URL url = new URL(mThreadInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
                // 设置下载位置
                conn.setRequestProperty("Range",
                        "bytes=" + "-" + mThreadInfo.getEnd());
                // 设置文件写入位置
                File file = new File(DownLoadService.DOWN_PATH,
                        mFileInfo.getFileName());
                raf = new RandomAccessFile(file, "rwd");
                //移动到指定位置
                raf.seek(start);
                Intent intent = new Intent(DownLoadService.ACTION_UPDATE);
                mfinished += mThreadInfo.getFinished();
                // 开始下载
                if (conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
                    // 读取数据
                    ins = conn.getInputStream();
                    byte[] buffer = new byte[1024 * 4];
                    int length = -1;
                    long time = System.currentTimeMillis();
                    while ((length = ins.read(buffer)) != -1) {
                        // 写入文件
                        raf.write(buffer, 0, length);
                        // 把下载进度发送广播更新UI
                        // 累加整文件完成进度
                        mfinished += length;
                        // 累加每个线程完成的进度
                        mThreadInfo.setFinished(mThreadInfo.getFinished()
                                + length);
                        if (System.currentTimeMillis() - time > 1000) {
                            time = System.currentTimeMillis();
                            intent.putExtra("finished", mfinished * 100
                                    / mFileInfo.getLength());
                            intent.putExtra("id", mFileInfo.getId());
                            mContext.sendBroadcast(intent);
                        }
                        // 下载暂停保存进度
                        if (isPause) {
                            mDao.updateThread(mThreadInfo.getUrl(),
                                    mThreadInfo.getId(),
                                    mThreadInfo.getFinished());
                            return;
                        }
                    }
                    isfinished = true;
                    // 检查下载任务是否完成
                    checkAllThreadsFinished();

线程通过使用线程池来管理

ExecutorService sExecutorService=Executors.newCachedThreadPool();

//使用带缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse.如果没有,就建一个新的线程加入池中

//缓存型池子通常用于执行一些生存期很短的异步型任务,能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。

GITHUB:

时间: 2024-10-16 15:35:22

Android多线程下载断点续传的相关文章

android程序----&gt;android多线程下载(二)

上篇我们讲到了android中下载的断点续传问题,今天我们开始学习下载的多线程问题.本次的多线程源码下载:androdi中多线程下载的实现代码.有关断点续传的问题,请参见博客:android程序---->android多线程下载(一) 目录导航 android中多线程下载的思路 android中多线程中的原理说明 android中多线程下载的实现 友情链接 android中多线程下载的思路 一. 多线程下载的步骤说明: 第一步: 我们要获得下载资源的的长度,用http请求中HttpURLConn

android程序----&gt;android多线程下载(一)

多线程下载是加快下载速度的一种方式,通过开启多个线程去执行一个任务,可以使任务的执行速度变快.多线程的任务下载时常都会使用得到断点续传下载,就是我们在一次下载未结束时退出下载,第二次下载时会接着第一次下载的进度继续下载.对于android中的下载,我想分多个部分去讲解分析.今天,我们就首先开始android中下载断点续传代码的实现.关于多线程下载单个文件的实现,请参见博客:android程序---->android多线程下载(二) 目录导航 android中断点续传的思路 android断点续传

Andoid 更好的Android多线程下载框架

概述 为什么是更好的Android多线程下载框架呢,原因你懂的,广告法嘛! 本篇我们我们就来聊聊多线程下载框架,先聊聊我们框架的特点: 多线程 多任务 断点续传 支持大文件 可以自定义下载数据库 高度可配置,像超时时间这类 业务数据和下载数据分离 下面我们在说下该框架能实现那些的应用场景: 该框架可以很方便的下载单个文件,并且显示各种状态,包括开始下载,下载中,下载失败,删除等状态. 也可以实现常见的需要下载功能应用,比如:某某手机助手,在该应用内可以说是下载是核心功能,所以对框架的稳定性,代码

无废话Android之smartimageview使用、android多线程下载、显式意图激活另外一个activity,检查网络是否可用定位到网络的位置、隐式意图激活另外一个activity、隐式意图的配置,自定义隐式意图、在不同activity之间数据传递(5)

1.smartimageview使用 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"

Java--使用多线程下载,断点续传技术原理(RandomAccessFile)

一.基础知识 1.什么是线程?什么是进程?它们之间的关系? 可以参考之前的一篇文章:java核心知识点学习----并发和并行的区别,进程和线程的区别,如何创建线程和线程的四种状态,什么是线程计时器 简单说一个进程可以由多个线程组成,一个操作系统可以多个进程,它们都是可以同时进行工作的. 2.什么是下载?如何多线程进行下载?如何断点续传? 广义上说,凡是在屏幕上看到的不属于本地计算机上的内容,皆是通过"下载"得来.狭义上人们只认为那些自定义了下载文件的本地磁盘存储位置的操作才是"

多线程下载 断点续传

package wml.dl;import java.io.BufferedInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.util.Properties; import wml.dl.io.BufferedRandomOutputStream; *

Android 多线程下载,断点续传,线程池

你可以在这里看到这个demo的源码: https://github.com/onlynight/MultiThreadDownloader 效果图 这张效果图是同时开启三个下载任务,限制下载线程数量的效果图. 多线程下载原理 多线程下载的原理就是将下载任务分割成一个个小片段再将每个小片段分配给各个线程进行下载. 例如一个文件大小为100M,我们决定使用4个线程下载,那么每个线程下载的大小即为25M,每个线程的起始以及结束位置依次如下: 0: 0-25M 1: 25-50M 2: 50-75M 3

J哥---------Android 多线程下载 仿下载助手(改进版)

首先声明一点: 这里的多线程下载 并不是指的 多个线程下载一个 文件,而是 每个线程 负责一个文件.真正的多线程 希望后面能给大家带来.  -------------  欢迎 爱学习的小伙伴 加群  -------------  -------------android交流群:230274309------------- -------------一起分享,一起进步!  需要你们-------------- --------------  期待各位爱学习的小伙伴们 的到来------------

Android 多线程下载原理剖析

今天带来一个多线程下载的 例子.先看一下效果,点击 下载 开始下载,同时显示下载进度,下载完成,变成程 安装,点击安装 提示 安装应用. 界面效果 这里写图片描述 线程池 ThreadPoolExecutor 在下面介绍实现下载原理的时候,我想尝试倒着来说,这样是否好理解一点? 我们都知道,下载助手,比如360, 百度的 手机助手,下载APP 的时候 ,都可以同时下载多个,所以,下载肯定是多线程的,所以我们就需要一个线程工具类 来管理我们的线程,这个工具类的核心,就是 线程池. 线程池Threa