一个多线程断点续传的案例

本文介绍了如何实现多线程断点续传的文件下载功能。通过划分文件下载区域,使用Http协议的Range字段进行分段下载,并利用RandomAccessFile进行文件的随机访问和存储。同时,通过Handler传递下载进度。在断点续传方面,利用DownloadInfo类和数据库保存下载信息,确保进程被杀后能恢复下载。文章详细讲解了DownloadTask、子线程、数据库操作和下载完成的回调机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大神有过该方面的博客,今天我也自己来实践一下,写的一般,还请大家多提意见,共同进步。

- 实现的原理

1.多线程下载:

首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount;得到每一段下载量;每一段的位置是i * range到(i + 1) * rang - 1,注意最后一段的位置是到filesize - 1;
通过Http协议的Range字段实现下载文件的分段;

通过Java类RandomAccessFile可以实现文件的随机访问,利用seek方法定位的文件的指定位置;

由HttpUrlConnection获取流来进行流的读写,实现文件的存储;

在下载过程中利用Handler来向外传递下载的信息。
2.断点续传:

对于每一个线程利用一个DownloadInfo类来保存下载的信息,每次在下载过程中向数据库更新信息(我也有想过只在下载暂停时进行更新,但那样的话我们的进程被杀掉时信息就无法保存下来)。在进行下载之前去访问数据库是否有记录存在,如果没有执行第一次下载的初始化,如果存在记录但下载文件不存在时,删掉数据库中的记录之后进行第一次下载的初始化,如果有记录且文件存在,则从数据库中取出信息。

- 执行下载操作的类DownloadTask

public DownloadTask(String downloadUrl, int threadNum, String fileptah, Context context) {//没有sd卡的时候,filepath传入“”空值
        this.downloadUrl = downloadUrl;
        this.threadNum = threadNum;
        this.filePath = fileptah;
        this.mContext = context;
        this.downFileName = fileptah+".tmp  ";
        sqlTool = new DownlaodSqlTool(mContext);
    }

    @Override
    public void run() {
        //super.run();
        downloadInfos = sqlTool.getInfos(downloadUrl);
        if (downloadInfos.size()== 0 ){
            initFirst();
        }
        startThread();
    }

    private void initFirst() {
        try {
            Log.i(TAG,fiele+"-----------"+"第一次下载");
            URL url = new URL(downloadUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            fileSize = connection.getContentLength();//获取文件的长度
            File file = new File(downFileName);
            if (!file.exists()) {
                file.createNewFile();   
            }
            // 本地访问文件
            RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
            accessFile.setLength(fileSize);
            accessFile.close();
            connection.disconnect();

            // 计算每条线程下载的数据长度
            blockSize = (fileSize % threadNum) == 0 ? fileSize / threadNum
                    : fileSize / threadNum + 1;
            //保存每条线程下载的信息
            for (int i = 0; i < threadNum - 1; i++) {
                //线程id,开始位置,结束位置,完成的下载大小,下载地址
                DownloadInfo info = new DownloadInfo(i, i * blockSize, (i + 1) * blockSize
                        - 1, 0, downloadUrl);
                downloadInfos.add(info);
            }
            //保存最后一条线程下载的信息
            DownloadInfo info = new DownloadInfo(threadNum - 1, (threadNum - 1)
                    * blockSize, fileSize - 1, 0, downloadUrl);
            downloadInfos.add(info);
            sqlTool.insertInfos(downloadInfos);//创建数据
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void startThread() {
        //开启多条线程去下载
        if (downloadInfos != null) {
            for (DownloadInfo infos : downloadInfos) {
                Log.i(TAG,fiele+"-----------"+infos.getThreadId()+"开启下载线程");
                FileDownloadThread thread =  new FileDownloadThread(this,downFileName,infos);

                executorService.execute(thread);
                downloadThreads.add(thread);
            }
        }
    }
    /**
     * 暂停下载
     */
    public void pauseDownload(){
        for(FileDownloadThread downloadThread : downloadThreads){
            if (downloadThread!=null) {
                downloadThread.setPause(true);
                Log.i(TAG,fiele+"-----------"+downloadThread.threadId+"暂停了");
            }
        }
    }
    /**
     * 开始下载
     */
    public void startDownload(){
        downloadThreads.clear();
        //downloadInfos.clear();
        Log.i(TAG,"网络来了继续开始下");
        //initThread();
        //run();
        startThread();
    }

    @Override
    public void pauseCallBack(DownloadInfo threadBean) {
        sqlTool.updataInfos(threadBean.getThreadId(), threadBean.getCompeleteSize(), threadBean.getUrl());//更新下载进度到数据库
    }

    @Override
    public synchronized void threadDownLoadFinished(DownloadInfo downloadInfo) {
        for( DownloadInfo info:downloadInfos){
            if(info.getThreadId() == downloadInfo.getThreadId()){
                //从列表中将已下载完成的线程信息移除
                downloadInfos.remove(info);
                Log.i(TAG,fiele+"-----------"+downloadInfo.getThreadId()+"下载完成了");
                break;
            }
        }
        if(downloadInfos.size()==0){//如果列表size为0 则所有线程已下载完成
            //删除数据库中的信息
            sqlTool.delete(downloadUrl);
            sqlTool.closeDb();
            File file = new File(downFileName);
            File newf = new File(filePath);
            file.renameTo(newf);
        }

    }

- 执行下载任务的子线程

  public FileDownloadThread(DownloadCallBack callBack, String file, DownloadInfo infos) {
        this.mCallBack =callBack;
        this.file = file;
        this.threadId = infos.getThreadId();
        this.startPos = infos.getStartPos();
        this.endPos = infos.getEndPos();
        this.downloadLength = infos.getCompeleteSize();
        this.downloadUrl = infos.getUrl();
        this.mDownloadInfo =infos;
        blockSize = endPos - startPos + 1;
    }

    @Override
    public void run() {
        super.run();
        HttpURLConnection connection = null;
        BufferedInputStream bis = null;
        RandomAccessFile raf = null;
        try {
            connection = (HttpURLConnection) new URL(downloadUrl).openConnection();
            connection.setConnectTimeout(5000);
            connection.setRequestMethod("GET");
            connection.setAllowUserInteraction(true);
            //设置当前线程下载的起点、终点(第一次下载的话,是开始位置+0,后面就是开始位置+已经下载的进度)
            connection.setRequestProperty("Range", "bytes="
                    + (startPos + downloadLength) + "-" + endPos);
            raf = new RandomAccessFile(file, "rwd");
            //从文件的指定位置开始读写
            raf.seek(startPos + downloadLength);

            byte[] buffer = new byte[1024];
            bis = new BufferedInputStream(connection.getInputStream());
            int len = -1;
            while ((len = bis.read(buffer, 0, 1024)) != -1) {
                raf.write(buffer, 0, len);//写入文件
                downloadLength += len;//已下载的进度叠加
                //sqlTool.updataInfos(threadId, downloadLength, downloadUrl);//更新下载进度到数据库
                if(mPause){
                    mCallBack.pauseCallBack(mDownloadInfo);
                    return;
                }
                if (downloadLength >= blockSize) {//单个线程下载完成
                  mCallBack.threadDownLoadFinished(mDownloadInfo);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bis != null) {
                    bis.close();
                }
                raf.close();
                connection.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public void setPause(boolean pause) {
        mPause = pause;
    }

- 保存下载信息的数据库操作

创建数据库的操作

/**
 * 利用数据库来记录下载信息
 * @author acer
 */
public class DownLoadHelper extends SQLiteOpenHelper{

    private static final String SQL_NAME = "download.db";
    private static final int DOWNLOAD_VERSION=1;

    public DownLoadHelper(Context context) {
        super(context, SQL_NAME, null, DOWNLOAD_VERSION);
        // TODO Auto-generated constructor stub
    }

     /**
     * 在download.db数据库下创建一个download_info表存储下载信息
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "
                + "start_pos integer, end_pos integer, compelete_size integer,url char)");
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

数据库的增删改查

  /**
     * 创建下载的具体信息
     */
    public void insertInfos(List<DownloadInfo> infos) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        for (DownloadInfo info : infos) {
            String sql = "insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";
            Object[] bindArgs = { info.getThreadId(), info.getStartPos(),
                    info.getEndPos(), info.getCompeleteSize(), info.getUrl() };
            database.execSQL(sql, bindArgs);
        }
    }

    /**
     * 得到下载具体信息
     */
    public List<DownloadInfo> getInfos(String urlstr) {
        List<DownloadInfo> list = new ArrayList<DownloadInfo>();
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";
        Cursor cursor = database.rawQuery(sql, new String[] { urlstr });
        while (cursor.moveToNext()) {
            DownloadInfo info = new DownloadInfo(cursor.getInt(0),
                    cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),
                    cursor.getString(4));
            list.add(info);
        }
        return list;
    }

    /**
     * 更新数据库中的下载信息
     */
    public void updataInfos(int threadId, int compeleteSize, String urlstr) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql = "update download_info set compelete_size=? where thread_id=? and url=?";
        Object[] bindArgs = { compeleteSize, threadId, urlstr };
        database.execSQL(sql, bindArgs);
    }
    /**
     * 关闭数据库
     */
    public void closeDb() {
        dbHelper.close();
    }

    /**
     * 下载完成后删除数据库中的数据
     */
    public void delete(String url) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        database.delete("download_info", "url=?", new String[] { url });
    }

- 保存下载信息实体类

public class DownloadInfo {
    /**
     * 保存每个下载线程下载信息类
     * 
     */
private int threadId;// 下载器id
    private int startPos;// 开始点
    private int endPos;// 结束点
    private int compeleteSize;// 完成度
    private String url;// 下载文件的URL地址

    public DownloadInfo(int threadId, int startPos, int endPos,
                        int compeleteSize, String url) {
        this.threadId = threadId;
        this.startPos = startPos;
        this.endPos = endPos;
        this.compeleteSize = compeleteSize;
        this.url = url;
    }

    public DownloadInfo() {
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public int getThreadId() {
        return threadId;
    }

    public void setThreadId(int threadId) {
        this.threadId = threadId;
    }

    public int getStartPos() {
        return startPos;
    }

    public void setStartPos(int startPos) {
        this.startPos = startPos;
    }

    public int getEndPos() {
        return endPos;
    }

    public void setEndPos(int endPos) {
        this.endPos = endPos;
    }

    public int getCompeleteSize() {
        return compeleteSize;
    }

    public void setCompeleteSize(int compeleteSize) {
        this.compeleteSize = compeleteSize;
    }

    @Override
    public String toString() {
        return "DownloadInfo [threadId=" + threadId + ", startPos=" + startPos
                + ", endPos=" + endPos + ", compeleteSize=" + compeleteSize
                + "]";
    }

- 下载完成的回调

public interface DownloadCallBack {
    /**
     * 暂停回调
     * @param threadBean
     */
    void pauseCallBack(DownloadInfo threadBean);

    /**
     * 线程下载完毕
     * @param threadBean
     */
    void threadDownLoadFinished(DownloadInfo threadBean);

Demo下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值