原理:
多线程下载就是分为多个线程去下载文件,每个线程对应文件的每段数据的下载模块,每个文件的片段只有一个线程去处理。
假如:一个文件长度为10,被三个线程切分,最终线程的分配每段的大小就是3、3、4,这些都是每个线程要下载的文件大小。
带着疑问:
1.如何获取文件的大小
2.如何获取分段的网络数据?
3.如何分割下载的数据?
4.如何把多段网络数据写入到一个文件里?RadomAccessFile
5.如何判断整个文件是否下载完成?
线程工具类:
获取文件长度,根据线程数量切分每个线程下载的区间
代码实现
/**
* 开始下载
* 1.获取文件的大小
* 2.构造多个线程的对象
* fileurl 下载文件的服务器地址
* maxThreadCount 线程数量
* filename 下载后文件的名称
*/
public void startDownLoad(final ProgressBar progressBar, final Context context, final String fileUrl, final int maxThreadCount, String fileName) {
this.readestTask = maxThreadCount;
// 创建本地 下载的文件
final String targetFilePathAndName = Environment.getExternalStorageDirectory() + File.separator + fileName;
new Thread() {
@Override
public void run() {
super.run();
try {
// 获取文件的长度
HttpURLConnection connection = getConnection(fileUrl,0,0);
contentLength = connection.getContentLength();
connection.disconnect();
Log.d(TAG, "doHttpTask: thread=contentLength" + contentLength);
// 创建本地的临时文件
RandomAccessFile file = new RandomAccessFile(targetFilePathAndName, "rw");
file.setLength(contentLength);// 设置本地文件的大小
progressBar.setMax(contentLength);
file.close();
Log.d(TAG, "doHttpTask: thread=" + totalCount);
// 线程池的对象创建
ExecutorService executorService = Executors.newFixedThreadPool(maxThreadCount);
partSize = contentLength / maxThreadCount;
for (int x = 0; x < maxThreadCount; x++) {
// 创建多个线程,使用线程池管理
// 获取 每段文件的下载起点位置
int startPos = x * partSize;
int endPos = (x+1)*partSize-1;
//最后一个线程,下载剩余的文件大小。
if (x == maxThreadCount - 1) {
endPos = contentLength-1;
}
DownLoadTask downLoadTask = new DownLoadTask(new DownLoadTask.IUpdateProgress() {
@Override
public void update(int progress) {
progressBar.incrementProgressBy(progress);
Log.d(TAG, "update: 进度条==" + progressBar.getProgress() );
}
}, context, startPos, endPos, fileUrl, targetFilePathAndName);
// 开始执行线程的任务
executorService.execute(downLoadTask);
}
executorService.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
public static HttpURLConnection getConnection(String fileUrl, int start, int end) throws IOException {
URL url = new URL(fileUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10 * 1000);
conn.setRequestMethod("GET");
if (start >= 0 && end > 0) {
///设置请求部分资源 Range表示随机位置 重点1
conn.setRequestProperty("Range", "bytes=" + start + "-" + end); //设置当前线程从start开始 到 end结束
}
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Referer", fileUrl);
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Accept-Encoding", "identity");
return conn;
}
每个线程下载每段数据
public class DownLoadTask implements Runnable {
//下载的起点
private int startPos;
public String fileUrl;
private final int endPos;
private RandomAccessFile currentPart;
private static final String TAG = "DownLoadTask";
IUpdateProgress updateProgress;
private Context context;
public int currentDownLoaded;
/**
* @param startPos 下载的起点
* @param endPos 下载的大小
* @param fileUrl 服务器的文件地址
* @param targetFilePathAndName 本地文件的路径
* @throws IOException
*/
public DownLoadTask(IUpdateProgress updateProgress, Context context, int startPos, int endPos,
String fileUrl, String targetFilePathAndName) throws IOException {
this.updateProgress = updateProgress;
this.context = context;
this.startPos = startPos;
this.fileUrl = fileUrl;
this.endPos = endPos;
this.currentPart = currentPart;
// 创建本地的文件,定位到起点位置,便于当前线程从起点位置开始下载
currentPart = new RandomAccessFile(targetFilePathAndName, "rw");// read write
currentPart.seek(startPos);
}
@Override
public void run() {
downItemTask();
}
private void downItemTask() {
try {
HttpURLConnection connection = MoreThreadUtils.getConnection(fileUrl,startPos,endPos);
InputStream in = connection.getInputStream();
byte[] bytes = new byte[1024];
int hasRead;
// 开始读取流的操作
while ((hasRead = in.read(bytes)) > 0) {
currentPart.write(bytes, 0, hasRead);// 写入数据到本地文件
if (updateProgress!=null){
updateProgress.update(hasRead);
}
// Thread.sleep(50);
}
Log.d(TAG, Thread.currentThread() + "--size=" + currentDownLoaded);
// 关闭各种流操作
currentPart.close();
in.close();
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
} finally {
MoreThreadUtils.readestTask--;
if (MoreThreadUtils.readestTask == 0) {// 当前下载的线程数量为0,下载完成
Log.d(TAG, "doHttpTask: 下载完成");
// installApk(context,targetFilePathAndName);
}
}
}
interface IUpdateProgress {
void update(int progress);
}
}