即拿即用-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
- 即拿即用-Android多线程断点下载
- android多线程断点下载
- android 多线程断点下载
- android多线程断点下载
- android多线程断点下载
- android,多线程断点下载
- android多线程断点下载
- android多线程断点下载
- android 多线程断点下载
- Android多线程断点下载
- android多线程断点下载
- Android多线程断点下载
- Android多线程断点下载
- Android多线程断点下载
- Android多线程断点下载
- android----断点多线程下载
- Android多线程断点下载
- Android-多线程断点下载
- STM32的AFIO时钟何时开启
- poj 3259
- 【IMWeb】作业-select制作
- web项目发布过程
- 各种前端框架中的路由原理解析
- 即拿即用-Android多线程断点下载
- cdq分治——bzoj1176: [Balkan2007]Mokia/bzoj2683: 简单题
- ethtool 命令输出的注意点
- 第三十七讲项目一 位乘大于位和
- 【IMWeb训练营作业】select组件
- BZOJ 1296: [SCOI2009]粉刷匠 动态规划
- U盘安装KALI
- SAPUI5 (32)
- 前端面试—html语义化