多线程下载,断点续传原理解析和代码实现

  • 多线程下载
    • 实现步骤
    • 注意的问题
      • 代码实现
        • 进度条用单独一个LinearLayout
        • 获取文件总大小
        • 为将要下载的文件预留空间
        • 计算每个子线程要下载的字节范围
        • 下载子线程是部分请求设置请求头
        • 设置子线程写入位置并写入文件
        • 判断下载完成
        • 关于RandomAccessFile类
          • 该案例中用到了以下方法
    • 断点续传
      • 原理
        • 每写入一次就保存一次当前写入的位置
        • 子下载线程开启时检测是否有断点续传
        • 进度条的设置
    • 开源项目实现多线程下载
      • 源码下载

多线程下载

实现步骤:

  1. 先获取到服务器上的资源大小
  2. 在客户端创建一个大小和服务器一样的文件
  3. 假设开3个线程,算出每个线程下载的的开始位置和结束位置
  4. 开启线程去下载

注意的问题

  1. 测试时为了保证效果,使用.exe安装程序 做测试
    只要丢了一个字节该程序便不能使用
  2. 先使用javase工程写,再移植到Android工程
  3. RandomAccessFile类。创建从其中随机读取或者写入的访问流
  4. 元数据:数据的数据(数据的属性),比如一个文件的名称,大小,路径等都是这个文件的元数据

代码实现

布局:
多线程下载,断点续传原理解析和代码实现_第1张图片

进度条用单独一个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//計算每個線程應該下載的字節範圍
    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//使用打气筒服务用布局文件创建一个进度条对象
    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() {
@Override
public void onSuccess(ResponseInfo 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) {

}

源码下载

http://download.csdn.net/detail/jianbiao426/9553462

你可能感兴趣的:(Android,安卓,Anroid)