多线程下载
实现步骤:
- 先获取到服务器上的资源大小
- 在客户端创建一个大小和服务器一样的文件
- 假设开3个线程,算出每个线程下载的的开始位置和结束位置
- 开启线程去下载
注意的问题
- 测试时为了保证效果,使用.exe安装程序 做测试
只要丢了一个字节该程序便不能使用 - 先使用javase工程写,再移植到Android工程
- RandomAccessFile类。创建从其中随机读取或者写入的访问流
- 元数据:数据的数据(数据的属性),比如一个文件的名称,大小,路径等都是这个文件的元数据
代码实现
布局:
进度条用单独一个LinearLayout
再设置它的布局内容为ProgressBar
获取文件总大小
//連接服務器
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
//獲取返回狀態碼
int code = conn.getResponseCode();
if(code == 200) {
//請求成功,獲取服務器返回文件的大小
int length = conn.getContentLength();
Log.d("文件大小為:", length + "");
}
为将要下载的文件预留空间
//請求成功,獲取服務器返回文件的大小
int length = conn.getContentLength();
LogUtil.d("文件大小為:", length + "");
RandomAccessFile raf = new RandomAccessFile(sdPath + "/" + getFileName(urlStr), "rw");
//為將要下載的文件預先分配空間
raf.setLength(length);
计算每个子线程要下载的字节范围
//開啟多個線程,分段下載
for(int i=0; i<threadCount; i++) {
//計算每個線程應該下載的字節範圍
int startIndex = i * blockSize;
int endIndex = (i + 1) * blockSize - 1;
//如果是最後一個線程,就下載到文件末尾
if(i == threadCount - 1) {
endIndex = length - 1;
}
new DownLoadThread(startIndex, endIndex, i).start();
}
下载子线程是部分请求,设置请求头
HttpURLConnection conn = connct2Server(urlStr);
//設置Range頭屬性
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
设置子线程写入位置并写入文件
if(code == 206) {
//部份資源請求成功
LogUtil.d("線程" + threadId + "請求成功", code + "");
//拿到要寫入的文件
RandomAccessFile raf = new RandomAccessFile(sdPath + "/" + getFileName(urlStr), "rw");
//設置要寫入的文件光標位置
raf.seek(startIndex);
//獲取輸入流并寫入文件
InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buffer = new byte[1024 * 1024];
int len = 0;
while((len = bis.read(buffer)) != -1) {
raf.write(buffer, 0, len);
}
raf.close();
bis.close();
}
判断下载完成
//下載完畢后提示
synchronized (DownLoadThread.class) {
threadCount --;
if(threadCount == 0) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "下載完畢", 1).show();
}
});
}
}
关于RandomAccessFile类
此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。
该案例中用到了以下方法
构造方法:RandomAccessFile raf = new RandomAccessFile(sdPath + "/" + getFileName(urlStr), "rw");
写入方法:raf.write(buffer, 0, len);
设置文件指针位置:raf.seek(startIndex);
断点续传
原理
当线程被结束时,把当前下载到的文件字节下标保存到文件中。
当下载线程再次开启时,从文件中读取下载到的位置,从这里开始下载。
每写入一次就保存一次当前写入的位置
fos.close();
//使用原来的方法可能导致没有来得及保存
RandomAccessFile pos = new RandomAccessFile(sdDir + "/" + getFileName(path) + "Thread-" + threadId + ".txt", "rwd");
pos.write(String.valueOf(currentTreadPostion).getBytes());
pos.close();
子下载线程开启时,检测是否有断点续传
把从文件读取出来 的位置赋值给startIndex
//打开保存断点位置的文件
File file = new File(sdDir + "/" + getFileName(path) + "Thread-" + threadId + ".txt");
if(file.exists() && file.length() > 0) {
//说明中断过,把上次断点位置读取出来
FileInputStream fis = new FileInputStream(file);
BufferedReader br= new BufferedReader(new InputStreamReader(fis));
String lastPos = br.readLine();
//获取上次下载的进度条位置
pbLastPosition = Integer.parseInt(lastPos) - startIndex;
//改变下一次的开始位置
startIndex = Integer.parseInt(lastPos);
br.close();
}
进度条的设置
每下载一次就设置一次进度条的位置
//进度条的初始化
ll_progress.removeAllViews();//加载进度条之前先清除之前的进度条
//动态加载进度条
for(int i=0; i<threadCount; i++) {
//使用打气筒服务用布局文件创建一个进度条对象
ProgressBar pb = (ProgressBar) View.inflate(getApplicationContext(), R.layout.pb, null);
pb.setMax(100);//设置进度条的最大值
pb.setProgress(50);//设置进度条的当前进度
pbs.add(pb);//进度条对象添加 到集合
ll_progress.addView(pb);//添加一个进度条到总布局
}
//进度条的更新,子线程可以更新进度条
pbs.get(threadId).setMax(pbMax);
pbs.get(threadId).setProgress(pbLastPosition + total);
开源项目实现多线程下载
了解了多线程断点下载的原理,在实际应用中更多的是使用开源项目,可以大大降低成本,节省时间。
使用方法如下 :
导入xUtils包,使用就行
path = tv_url.getText().toString().trim();
//创建HttpUtils对象
HttpUtils httpUtils = new HttpUtils();
//autoResume支持断点续传
httpUtils.download(path, "mnt/sdcard/haha.exe", true, new RequestCallBack<File>() {
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
Toast.makeText(getApplicationContext(), "下载成功", 1).show();
}
//更新进度条的方法
@Override
public void onLoading(long total, long current, boolean isUploading) {
pb.setMax((int)total);
pb.setProgress((int)current);
super.onLoading(total, current, isUploading);
}
@Override
public void onFailure(HttpException error, String msg) {
}