即拿即用-Android多线程断点下载

线程下载只需要确定好下载一个文件需要多少个线程,一般来说最好为3条线程,因为线程过多会占用系统资源,而且线程间的相互竞争也会导致下载变慢。

其次下载的时候将文件分割为三份(假设用3条线程下载)下载,在java中就要用到上次提到的RandomAccessFile这个API,它的开始结束为止用以下代码确定:

conn.setRequestProperty(“Range”, “bytes=” + start + “-” + end)

最后就是断点续传了,只需要才程序停止下载的时候记录下最后的下载位置就好了,当下次下载的时候从当前停止的位置开始下载。

这里写图片描述

这里写图片描述

MultiThreadActivity

在MultiThreadActivity 中创建listview展示列表,创建一个Receiver来接受下载进度并通知adapter来更新进度条

package com.bourne.httprequest.multiThreadDownload;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ListView;
import android.widget.Toast;

import com.bourne.httprequest.FileInfo;
import com.bourne.httprequest.R;

import java.util.ArrayList;
import java.util.List;

public class MultiThreadActivity extends AppCompatActivity {

    private ListView listView;
    private List<FileInfo> mFileList;
    private FileAdapter mAdapter;

    private String urlone = "http://dldir1.qq.com/qqmi/TencentVideo_V5.5.2.11955_848.apk";
    private String urltwo = "http://file.ws.126.net/opencourse/netease_open_androidphone.apk";
    private String urlthree = "http://downloads.jianshu.io/apps/haruki/JianShu-2.2.3-17040111.apk";
    private String urlfour = "http://codown.youdao.com/note/youdaonote_android_5.9.1_youdaoweb.apk";


    private UIRecive mRecive;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_multi_thread);

        // 初始化控件
        listView = (ListView) findViewById(R.id.list_view);
        mFileList = new ArrayList<FileInfo>();

        // 初始化文件对象
        FileInfo fileInfo1 = new FileInfo(0, urlone, getfileName(urlone), 0, 0);
        FileInfo fileInfo2 = new FileInfo(1, urltwo, getfileName(urltwo), 0, 0);
        FileInfo fileInfo3 = new FileInfo(2, urlthree, getfileName(urlthree), 0, 0);
        FileInfo fileInfo4 = new FileInfo(3, urlfour, getfileName(urlfour), 0, 0);

        mFileList.add(fileInfo1);
        mFileList.add(fileInfo2);
        mFileList.add(fileInfo3);
        mFileList.add(fileInfo4);

        mAdapter = new FileAdapter(this, mFileList);

        listView.setAdapter(mAdapter);

        mRecive = new UIRecive();

        //创建Receiver来接收下载进度,然后更新adapter
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(DownloadService.ACTION_UPDATE);
        intentFilter.addAction(DownloadService.ACTION_FINISHED);
        intentFilter.addAction(DownloadService.ACTION_START);
        registerReceiver(mRecive, intentFilter);
    }



    @Override
    protected void onDestroy() {
        unregisterReceiver(mRecive);
        super.onDestroy();
    }
    // 从URL地址中获取文件名,即/后面的字符
    private String getfileName(String url) {

        return url.substring(url.lastIndexOf("/") + 1);
    }

    // 从DownloadTadk中获取广播信息,更新进度条
    class UIRecive extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (DownloadService.ACTION_UPDATE.equals(intent.getAction())) {
                // 更新进度条的时候
                int finished = intent.getIntExtra("finished", 0);
                int id = intent.getIntExtra("id", 0);
                mAdapter.updataProgress(id, finished);

            } else if (DownloadService.ACTION_FINISHED.equals(intent.getAction())){
                // 下载结束的时候
                FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
                mAdapter.updataProgress(fileInfo.getId(), 0);
                Toast.makeText(MultiThreadActivity.this, mFileList.get(fileInfo.getId()).getFileName() + "下载完毕", Toast.LENGTH_SHORT).show();

            } else if (DownloadService.ACTION_START.equals(intent.getAction())){

            }
        }

    }

}

FileAdapter

点击开始和暂停的时候通知DownloadService

package com.bourne.httprequest.multiThreadDownload;

import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.bourne.httprequest.FileInfo;
import com.bourne.httprequest.R;

import java.util.List;

public class FileAdapter extends BaseAdapter {

    private Context mContext = null;
    private List<FileInfo> mFilelist = null;

    private LayoutInflater layoutInflater;


    public FileAdapter(Context mContext, List<FileInfo> mFilelist) {
        super();
        this.mContext = mContext;
        this.mFilelist = mFilelist;
        layoutInflater = LayoutInflater.from(mContext);
    }

    @Override
    public int getCount() {
        return mFilelist.size();
    }

    @Override
    public Object getItem(int position) {
        return mFilelist.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;
        final FileInfo mFileInfo = mFilelist.get(position);
        if(convertView == null) {
            convertView = layoutInflater.inflate(R.layout.item, null);
            viewHolder = new ViewHolder();
            viewHolder.textview = (TextView) convertView.findViewById(R.id.file_textview);
            viewHolder.startButton = (Button) convertView.findViewById(R.id.start_button);
            viewHolder.stopButton = (Button) convertView.findViewById(R.id.stop_button);
            viewHolder.progressBar = (ProgressBar) convertView.findViewById(R.id.progressBar2);


            viewHolder.textview.setText(mFileInfo.getFileName());
            viewHolder.progressBar.setMax(100);

            viewHolder.startButton.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(mContext, DownloadService.class);
                    intent.setAction(DownloadService.ACTION_START);
                    intent.putExtra("fileInfo", mFileInfo);
                    mContext.startService(intent);
                }
            });
            viewHolder.stopButton.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(mContext, DownloadService.class);
                    intent.setAction(DownloadService.ACTION_STOP);
                    intent.putExtra("fileInfo", mFileInfo);
                    mContext.startService(intent);
                }
            });

            convertView.setTag(viewHolder);
        } else{
            viewHolder = (ViewHolder) convertView.getTag();
        }


        viewHolder.progressBar.setProgress(mFileInfo.getFinished());


        return convertView;
    }
    /**
     * 更新列表中的进度条
     *
     */
    public void updataProgress(int id , int progress) {
        FileInfo info = mFilelist.get(id);
        info.setFinished(progress);
        notifyDataSetChanged();
    }

    static class ViewHolder{
        TextView textview;
        Button startButton;
        Button stopButton;
        ProgressBar progressBar;
    }

}

DownloadService

在DownloadService创建一个InitThread来获取文件长度,获取完毕之后启动一个MultiDownloadTask来下载任务。

注意获取获取文件长度的InitThread不是直接new一个了,而是用线程池来进行操作

   InitThread initThread = new InitThread(fileInfo);
   MultiDownloadTask.sExecutorService.execute(initThread);

完整代码

package com.bourne.httprequest.multiThreadDownload;

import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;


import com.bourne.httprequest.FileInfo;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 下载任务类,用于执行下载任务,并且将下载进度传到Activity中
 */
public class DownloadService extends Service {

    public static final String ACTION_START = "ACTION_START";
    public static final String ACTION_STOP = "ACTION_STOP";
    public static final String ACTION_UPDATE = "ACTION_UPDATE";
    public static final String ACTION_FINISHED = "ACTION_FINISHED";
    // 文件的保存路径
    public static final String DownloadPath = Environment.getExternalStorageDirectory().getAbsolutePath()
            + "/download/";
    public static final int MSG_INIT = 0;

    private Map<Integer, MultiDownloadTask> mTasks = new LinkedHashMap<Integer, MultiDownloadTask>();

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

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 获得Activity传来的参数
        if (ACTION_START.equals(intent.getAction())) {
            FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
            Log.i("test", "START" + fileInfo.toString());
            InitThread initThread = new InitThread(fileInfo);
            MultiDownloadTask.sExecutorService.execute(initThread);
        } else if (ACTION_STOP.equals(intent.getAction())) {
            FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
            MultiDownloadTask task = mTasks.get(fileInfo.getId());
            if (task != null) {
                // 停止下载任务
                task.mIsPause = true;
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }

    // 从InitThread线程中获取FileInfo信息,然后开始下载任务
    Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                case MSG_INIT:
                    FileInfo fileInfo = (FileInfo) msg.obj;
                    Log.i("test", "INIT:" + fileInfo.toString());
                    // 获取FileInfo对象,开始下载任务
                    MultiDownloadTask task = new MultiDownloadTask(DownloadService.this, fileInfo, 3);
                    task.download();
                    // 把下载任务添加到集合中
                    mTasks.put(fileInfo.getId(), task);
                    // 发送启动下载的通知
                    Intent intent = new Intent(ACTION_START);
                    intent.putExtra("fileInfo", fileInfo);
                    sendBroadcast(intent);
                    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(5 * 1000);
                conn.setRequestMethod("GET");
                int code = conn.getResponseCode();
                int length = -1;
                if (code == HttpURLConnection.HTTP_OK) {
                    length = conn.getContentLength();
                }
                //如果文件长度为小于0,表示获取文件失败,直接返回
                if (length <= 0) {
                    return;
                }
                // 判断下载的文件是否存在,不存在则创建
                File dir = new File(DownloadPath);
                if (!dir.exists()) {
                    dir.mkdir();
                }
                // 创建本地文件
                File file = new File(dir, mFileInfo.getFileName());
                raf = new RandomAccessFile(file, "rwd");
                raf.setLength(length);
                // 设置文件长度
                mFileInfo.setLength(length);
                // 将FileInfo对象传给給Handler
                Message msg = Message.obtain();
                msg.obj = mFileInfo;
                msg.what = MSG_INIT;
                mHandler.sendMessage(msg);
//              msg.setTarget(mHandler);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (conn != null) {
                    conn.disconnect();
                }
                try {
                    if (raf != null) {
                        raf.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            super.run();
        }
    }

}

MultiDownloadTask

package com.bourne.httprequest.multiThreadDownload;

import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.bourne.httprequest.FileInfo;
import com.bourne.httprequest.multiThreadDownload.db.MultiDAOImple;
import com.bourne.httprequest.multiThreadDownload.db.MultiThreadDAO;
import com.bourne.httprequest.ThreadInfo;


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 MultiDownloadTask {

    private Context mComtext = null;
    private FileInfo mFileInfo = null;
    private MultiThreadDAO mDao = null;
    private int mFinished = 0;
    private int mThreadCount = 1;
    public boolean mIsPause = false;
    private List<DownloadThread> mThreadlist = null;
    public static ExecutorService sExecutorService = Executors.newCachedThreadPool();

    public MultiDownloadTask(Context comtext, FileInfo fileInfo, int threadCount) {
        super();
        this.mThreadCount = threadCount;
        this.mComtext = comtext;
        this.mFileInfo = fileInfo;
        this.mDao = new MultiDAOImple(mComtext);
    }

    public void download() {
        // 从数据库中获取下载的信息
        List<ThreadInfo> list = mDao.queryThreads(mFileInfo.getUrl());
        if (list.size() == 0) {
            int length = mFileInfo.getLength();
            int block = length / mThreadCount;
            for (int i = 0; i < mThreadCount; i++) {
                // 划分每个线程开始下载和结束下载的位置
                int start = i * block;
                int end = (i + 1) * block - 1;
                if (i == mThreadCount - 1) {
                    end = length - 1;
                }
                ThreadInfo threadInfo = new ThreadInfo(i, mFileInfo.getUrl(), start, end, 0);
                list.add(threadInfo);
            }
        }
        mThreadlist = new ArrayList<DownloadThread>();
        for (ThreadInfo info : list) {
            DownloadThread thread = new DownloadThread(info);
//          thread.start();
            // 使用线程池执行下载任务
            MultiDownloadTask.sExecutorService.execute(thread);
            mThreadlist.add(thread);
            // 如果数据库不存在下载信息,添加下载信息
            mDao.insertThread(info);
        }
    }

    public synchronized void checkAllFinished() {
        boolean allFinished = true;
        for (DownloadThread thread : mThreadlist) {
            if (!thread.isFinished) {
                allFinished = false;
                break;
            }
        }
        if (allFinished == true) {
            // 下載完成后,刪除数据库信息
            mDao.deleteThread(mFileInfo.getUrl());
            // 通知UI哪个线程完成下载
            Intent intent = new Intent(DownloadService.ACTION_FINISHED);
            intent.putExtra("fileInfo", mFileInfo);
            mComtext.sendBroadcast(intent);

        }
    }

    class DownloadThread extends Thread {
        private ThreadInfo threadInfo = null;
        // 标识线程是否执行完毕
        public boolean isFinished = false;

        public DownloadThread(ThreadInfo threadInfo) {
            this.threadInfo = threadInfo;
        }

        @Override
        public void run() {

            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            InputStream is = null;
            try {
                URL url = new URL(mFileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(5 * 1000);
                conn.setRequestMethod("GET");

                int start = threadInfo.getStart() + threadInfo.getFinished();
                // 设置下载文件开始到结束的位置
                conn.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd());
                File file = new File(DownloadService.DownloadPath, mFileInfo.getFileName());
                raf = new RandomAccessFile(file, "rwd");
                raf.seek(start);
                mFinished += threadInfo.getFinished();
                Intent intent = new Intent();
                intent.setAction(DownloadService.ACTION_UPDATE);
                int code = conn.getResponseCode();
                if (code == HttpURLConnection.HTTP_PARTIAL) {
                    is = conn.getInputStream();
                    byte[] bt = new byte[1024];
                    int len = -1;
                    // 定义UI刷新时间
                    long time = System.currentTimeMillis();
                    while ((len = is.read(bt)) != -1) {
                        raf.write(bt, 0, len);
                        // 累计整个文件完成进度
                        mFinished += len;
                        // 累加每个线程完成的进度
                        threadInfo.setFinished(threadInfo.getFinished() + len);
                        // 设置为500毫米更新一次
                        if (System.currentTimeMillis() - time > 1000) {
                            time = System.currentTimeMillis();
                            // 发送已完成多少
                            intent.putExtra("finished", mFinished * 100 / mFileInfo.getLength());
                            // 表示正在下载文件的id
                            intent.putExtra("id", mFileInfo.getId());
                            Log.i("test", mFinished * 100 / mFileInfo.getLength() + "");
                            // 发送广播給Activity
                            mComtext.sendBroadcast(intent);
                        }
                        if (mIsPause) {
                            mDao.updateThread(threadInfo.getUrl(), threadInfo.getId(), threadInfo.getFinished());
                            return;
                        }
                    }
                }
                // 标识线程是否执行完毕
                isFinished = true;
                // 判断是否所有线程都执行完毕
                checkAllFinished();

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

全文的重点就在这个类了

1、用了一个线程池ExecutorService来管理所有的下载任务
// 一个没有限制最大线程数的线程池
 public static ExecutorService sExecutorService = Executors.newCachedThreadPool();
2、将文件的大小切割为几个等分,然后每个等分都开启一个线程来进行下载
public void download() {
        // 从数据库中获取下载的信息
        List<ThreadInfo> list = mDao.queryThreads(mFileInfo.getUrl());
        if (list.size() == 0) {
            int length = mFileInfo.getLength();
            int block = length / mThreadCount;
            for (int i = 0; i < mThreadCount; i++) {
                // 划分每个线程开始下载和结束下载的位置
                int start = i * block;
                int end = (i + 1) * block - 1;
                if (i == mThreadCount - 1) {
                    end = length - 1;
                }
                ThreadInfo threadInfo = new ThreadInfo(i, mFileInfo.getUrl(), start, end, 0);
                list.add(threadInfo);
            }
        }
        mThreadlist = new ArrayList<DownloadThread>();
        for (ThreadInfo info : list) {
            DownloadThread thread = new DownloadThread(info);
            // 使用线程池执行下载任务
            MultiDownloadTask.sExecutorService.execute(thread);
            mThreadlist.add(thread);
            // 如果数据库不存在下载信息,添加下载信息
            mDao.insertThread(info);
        }
    }

《即拿即用-Android单线程断点下载》

《断点下载神器-RandomAccessFile》

《即拿即用-Android多线程断点下载》

完整代码地址

https://github.com/mocn26169/HttpRequest-Demo

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值