断点续传 进度条 开 始 暂停 继续的简单按钮实现

来源:互联网 发布:为什么优酷提示没网络 编辑:程序博客网 时间:2024/06/07 00:17

1.  断点续传原理

在本地下载过程中要使用数据库实时存储到底存储到文件的哪个位置了,这样点击开始继续传递时,才能通过HTTP的GET请求中的setRequestProperty()方法可以告诉服务器,数据从哪里开始,到哪里结束。同时在本地的文件写入时,RandomAccessFileseek()方法也支持在文件中的任意位置进行写入操作。同时通过广播将子线程的进度告诉Activity的ProcessBar。

 

2.  Activity的按钮响应

当点击开始按钮时,将url写在了FileInfo类的对象info中并通过Intent从Activity传递到了Service中。这里使用setAction()来区分是开始按钮还是暂停按钮。


首先是相关的权限 在清单文件里面 添加

    <uses-permission android:name="android.permission.INTERNET"/>    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />

第二就是在App的build里面添加相关的依赖

  compile 'com.squareup.retrofit2:converter-gson:2.0.1'    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.1'    compile 'io.reactivex:rxandroid:1.1.0'    compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'    compile 'com.google.code.gson:gson:2.8.2'    compile 'com.android.support:recyclerview-v7:24.0.0'//静态图片    compile 'com.facebook.fresco:fresco:0.12.0'// 支持 GIF 动图,需要添加    compile 'com.facebook.fresco:animated-gif:0.12.0'    compile 'com.android.support:design:23.4.0'    compile 'com.jcodecraeer:xrecyclerview:1.3.2'    compile 'com.squareup.okio:okio:1.5.0'
第三就是在对应的主页面的布局。xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="com.eightgroup.yuekaodashixunxl.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:onClick="downloadButtons"        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:onClick="cancel_buttons"        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:onClick="continue_buttons"        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>

第三步创建对应的两个类

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();            }        }    }}
第二个 需要调用她的方法和继承ResponsBoby需要对进度条的监听

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;            }        };    }}

下面就是主页面对应的activity或者fragment
public class MainActivity extends AppCompatActivity implements ProgressResponseBody.ProgressListener {    public static final String TAG = "MainActivity";    public static final String PACKAGE_URL = "http://2449.vod.myqcloud.com/2449_bfbbfa3cea8f11e5aac3db03cda99974.f20.mp4";    private 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);        progressBar = (ProgressBar) findViewById(R.id.progressBar);    }    public void downloadButtons(View view){        // 新下载前清空断点信息        breakPoints = 0L;        // 下载的位置        file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "sample.mp4");        downloader = new ProgressDownloader(PACKAGE_URL, file, this);        downloader.download(0L);    }    public void cancel_buttons(View view){        downloader.pause();        Toast.makeText(this, "下载暂停", Toast.LENGTH_SHORT).show();        // 存储此时的totalBytes,即断点位置。        breakPoints = totalBytes;    }    public void continue_buttons(View view){        downloader.download(breakPoints);    }    @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();        }    }}





阅读全文
0 0
原创粉丝点击