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

来源:互联网 发布:黑客技术入门教程软件 编辑:程序博客网 时间:2024/06/06 00:31

线程下载只需要确定好下载一个文件需要多少个线程,一般来说最好为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

参考

  • Android实战:多线程多文件断点续传下载+通知栏控制
0 0
原创粉丝点击