线程下载只需要确定好下载一个文件需要多少个线程,一般来说最好为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);
}
}
完整代码地址
https://github.com/mocn26169/HttpRequest-Demo