OkHttp实现断点续传

来源:互联网 发布:php接口开发视频 编辑:程序博客网 时间:2024/06/08 03:46

1.导入依赖包

//retrofit, 基于Okhttp,考虑到项目中经常会用到retrofit,就导入这个了。compile 'com.squareup.retrofit2:retrofit:2.1.0'//ButterKnifecompile 'com.jakewharton:butterknife:7.0.1'//rxjava 本例中线程切换要用到,代替handlercompile 'io.reactivex:rxjava:1.1.6'compile 'io.reactivex:rxandroid:1.2.1'

2.继承ResponseBody,生成带进度监听的ProgressResponseBody

package com.example.hxl.breakpointresumeokhttp;import java.io.IOException;import okhttp3.MediaType;import okhttp3.ResponseBody;import okio.Buffer;import okio.BufferedSource;import okio.ForwardingSource;import okio.Okio;import okio.Source;/** * Created by hxl on 2017/2/24 at haiChou. * 进度响应体 */public class ProgressResponseBody extends ResponseBody{    //设置对外访问的进度监听    public interface ProgressListener {        void onPreExecute(long contentLength);        void update(long totalBytes, boolean done);    }    private final ResponseBody responseBody;    private final ProgressListener progressListener;    private BufferedSource bufferedSource;    public ProgressResponseBody(ResponseBody responseBody,                                ProgressListener progressListener) {        this.responseBody = responseBody;        this.progressListener = progressListener;        if(progressListener!=null){            progressListener.onPreExecute(contentLength());        }    }    @Override    public MediaType contentType() {        return responseBody.contentType();    }    @Override    public long contentLength() {        return responseBody.contentLength();    }    @Override    public BufferedSource source() {        if (bufferedSource == null) {            bufferedSource = Okio.buffer(source(responseBody.source()));        }        return bufferedSource;    }    private Source source(Source source) {        return new ForwardingSource(source) {            long totalBytes = 0L;            @Override            public long read(Buffer sink, long byteCount) throws IOException {                long bytesRead = super.read(sink, byteCount);                // read() returns the number of bytes read, or -1 if this source is exhausted.                totalBytes += bytesRead != -1 ? bytesRead : 0;                if (null != progressListener) {                    progressListener.update(totalBytes, bytesRead == -1);                }                return bytesRead;            }        };    }}

3.创建ProgressDownloader

package com.example.hxl.breakpointresumeokhttp;

import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;import okhttp3.Call;import okhttp3.Callback;import okhttp3.Interceptor;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response;import okhttp3.ResponseBody;/** * Created by hxl on 2017/2/24 at haiChou. * 创建ProgressDownloader带进度监听功能的辅助类 */public class ProgressDownloader {    public static final String TAG = "ProgressDownloader";    private ProgressResponseBody.ProgressListener progressListener;    private String url;    private OkHttpClient client;    private File destination;    private Call call;    public ProgressDownloader(String url, File destination, ProgressResponseBody.ProgressListener progressListener) {        this.url = url;        this.destination = destination;        this.progressListener = progressListener;        //在下载、暂停后的继续下载中可复用同一个client对象        client = getProgressClient();    }    //每次下载需要新建新的Call对象    private Call newCall(long startPoints) {        Request request = new Request.Builder()                .url(url)                .header("RANGE", "bytes=" + startPoints + "-")//断点续传要用到的,指示下载的区间                .build();        return client.newCall(request);    }    public OkHttpClient getProgressClient() {        // 拦截器,用上ProgressResponseBody        Interceptor interceptor = new Interceptor() {            @Override            public Response intercept(Chain chain) throws IOException {                Response originalResponse = chain.proceed(chain.request());                return originalResponse.newBuilder()                        .body(new ProgressResponseBody(originalResponse.body(), progressListener))                        .build();            }        };        return new OkHttpClient.Builder()                .addNetworkInterceptor(interceptor)                .build();    }    // startsPoint指定开始下载的点    public void download(final long startsPoint) {        call = newCall(startsPoint);        call.enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {            }            @Override            public void onResponse(Call call, Response response) throws IOException {                save(response, startsPoint);            }        });    }    public void pause() {        if(call!=null){            call.cancel();        }    }    private void save(Response response, long startsPoint) {        ResponseBody body = response.body();        InputStream in = body.byteStream();        FileChannel channelOut = null;        // 随机访问文件,可以指定断点续传的起始位置        RandomAccessFile randomAccessFile = null;        try {            randomAccessFile = new RandomAccessFile(destination, "rwd");            //Chanel NIO中的用法,由于RandomAccessFile没有使用缓存策略,直接使用会使得下载速度变慢,亲测缓存下载3.3秒的文件,用普通的RandomAccessFile需要20多秒。            channelOut = randomAccessFile.getChannel();            // 内存映射,直接使用RandomAccessFile,是用其seek方法指定下载的起始位置,使用缓存下载,在这里指定下载位置。            MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE, startsPoint, body.contentLength());            byte[] buffer = new byte[1024];            int len;            while ((len = in.read(buffer)) != -1) {                mappedBuffer.put(buffer, 0, len);            }        } catch (IOException e) {            e.printStackTrace();        }finally {            try {                in.close();                if (channelOut != null) {                    channelOut.close();                }                if (randomAccessFile != null) {                    randomAccessFile.close();                }            } catch (IOException e) {                e.printStackTrace();            }        }    }}
4.清单文件中添加网络权限和文件访问权限
<uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
5.测试
package com.example.hxl.breakpointresumeokhttp;import android.os.Environment;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.ProgressBar;import android.widget.Toast;import java.io.File;import butterknife.Bind;import butterknife.ButterKnife;import butterknife.OnClick;import rx.Observable;import rx.android.schedulers.AndroidSchedulers;import rx.functions.Action0;/** * 1.添加依赖 * 2.生成带进度监听的ProgressResponseBody * 3.创建ProgressDownloader * 4.清单文件中添加网络权限和文件访问权限 */public class MainActivity extends AppCompatActivity implements ProgressResponseBody.ProgressListener{    public static final String TAG = "MainActivity";    public static final String PACKAGE_URL = "http://gdown.baidu.com/data/wisegame/df65a597122796a4/weixin_821.apk";    @Bind(R.id.progressBar)    ProgressBar progressBar;    private long breakPoints;    private ProgressDownloader downloader;    private File file;    private long totalBytes;    private long contentLength;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);    }    @OnClick({R.id.downloadButton, R.id.cancel_button, R.id.continue_button})    public void onClick(View view) {        switch (view.getId()) {            case R.id.downloadButton:                // 新下载前清空断点信息                breakPoints = 0L;                file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "sample.apk");                downloader = new ProgressDownloader(PACKAGE_URL, file, this);                downloader.download(0L);                break;            case R.id.cancel_button:                downloader.pause();                Toast.makeText(this, "下载暂停", Toast.LENGTH_SHORT).show();                // 存储此时的totalBytes,即断点位置。                breakPoints = totalBytes;                break;            case R.id.continue_button:                downloader.download(breakPoints);                break;        }    }    @Override    public void onPreExecute(long contentLength) {        // 文件总长只需记录一次,要注意断点续传后的contentLength只是剩余部分的长度        if (this.contentLength == 0L) {            this.contentLength = contentLength;            progressBar.setMax((int) (contentLength / 1024));        }    }    @Override    public void update(long totalBytes, boolean done) {        // 注意加上断点的长度        this.totalBytes = totalBytes + breakPoints;        progressBar.setProgress((int) (totalBytes + breakPoints) / 1024);        if (done) {            // 切换到主线程            Observable                    .empty()                    .observeOn(AndroidSchedulers.mainThread())                    .doOnCompleted(new Action0() {                        @Override                        public void call() {                            Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();                        }                    })                    .subscribe();        }    }}
6.xml布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"   android:orientation="vertical"    tools:context="com.example.hxl.breakpointresumeokhttp.MainActivity">    <ProgressBar        android:id="@+id/progressBar"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginLeft="10dp"        android:layout_marginRight="10dp"        android:max="100"        style="?android:attr/progressBarStyleHorizontal" />    <Button        android:id="@+id/downloadButton"        android:layout_marginTop="10dp"        android:layout_marginLeft="10dp"        android:layout_marginRight="10dp"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="下载"/>    <Button        android:id="@+id/cancel_button"        android:layout_marginTop="10dp"        android:layout_marginLeft="10dp"        android:layout_marginRight="10dp"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="暂停"/>    <Button        android:id="@+id/continue_button"        android:layout_marginTop="10dp"        android:layout_marginLeft="10dp"        android:layout_marginRight="10dp"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="继续"/></LinearLayout>
7.效果图

原创粉丝点击