OKHttp实现大文件的断点续传

来源:互联网 发布:大华网络监控安装步骤 编辑:程序博客网 时间:2024/06/06 14:21

本文的亮点:

(1)网络请求用OKHttp进行下载大文件

(2)实现了大文件的断点续传

(3)取消下载时,删除已经下载的文件。

实现效果图:

             

直接给出工程:

(1)定义一个接口:DownloadListener.java

package com.example.servicebestpractice;/** * Created by Administrator on 2017/2/23. */public interface DownloadListener {    /**     * 通知当前的下载进度     * @param progress     */    void onProgress(int progress);    /**     * 通知下载成功     */    void onSuccess();    /**     * 通知下载失败     */    void onFailed();    /**     * 通知下载暂停     */    void onPaused();    /**     * 通知下载取消事件     */    void onCanceled();}
(2)开启一个异步下载任务:DownloadTask.java

package com.example.servicebestpractice;import android.os.AsyncTask;import android.os.Environment;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response;/** * Created by Administrator on 2017/2/23. *//** * String 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。 * Integer 后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。 * Integer 当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。 */public class DownloadTask extends AsyncTask<String,Integer,Integer> {    public static final int TYPE_SUCCESS=0;    public static final int TYPE_FAILED=1;    public static final int TYPE_PAUSED=2;    public static final int TYPE_CANCELED=3;    private DownloadListener listener;    private boolean isCanceled=false;    private boolean isPaused=false;    private int lastProgress;    public DownloadTask(DownloadListener listener) {        this.listener = listener;    }    /**     * 这个方法中的所有代码都会在子线程中运行,我们应该在这里处理所有的耗时任务。     * @param params     * @return     */    @Override    protected Integer doInBackground(String... params) {        InputStream is=null;        RandomAccessFile savedFile=null;        File file=null;        long downloadLength=0;   //记录已经下载的文件长度        //文件下载地址        String downloadUrl=params[0];        //下载文件的名称        String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));        //下载文件存放的目录        String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();        //创建一个文件        file=new File(directory+fileName);        if(file.exists()){            //如果文件存在的话,得到文件的大小            downloadLength=file.length();        }        //得到下载内容的大小        long contentLength=getContentLength(downloadUrl);        if(contentLength==0){            return TYPE_FAILED;        }else if(contentLength==downloadLength){            //已下载字节和文件总字节相等,说明已经下载完成了            return TYPE_SUCCESS;        }        OkHttpClient client=new OkHttpClient();        /**         * HTTP请求是有一个Header的,里面有个Range属性是定义下载区域的,它接收的值是一个区间范围,         * 比如:Range:bytes=0-10000。这样我们就可以按照一定的规则,将一个大文件拆分为若干很小的部分,         * 然后分批次的下载,每个小块下载完成之后,再合并到文件中;这样即使下载中断了,重新下载时,         * 也可以通过文件的字节长度来判断下载的起始点,然后重启断点续传的过程,直到最后完成下载过程。         */        Request request=new Request.Builder()                .addHeader("RANGE","bytes="+downloadLength+"-")  //断点续传要用到的,指示下载的区间                .url(downloadUrl)                .build();        try {            Response response=client.newCall(request).execute();            if(response!=null){                is=response.body().byteStream();                savedFile=new RandomAccessFile(file,"rw");                savedFile.seek(downloadLength);//跳过已经下载的字节                byte[] b=new byte[1024];                int total=0;                int len;                while((len=is.read(b))!=-1){                    if(isCanceled){                        return TYPE_CANCELED;                    }else if(isPaused){                        return TYPE_PAUSED;                    }else {                        total+=len;                        savedFile.write(b,0,len);                        //计算已经下载的百分比                        int progress=(int)((total+downloadLength)*100/contentLength);                        //注意:在doInBackground()中是不可以进行UI操作的,如果需要更新UI,比如说反馈当前任务的执行进度,                        //可以调用publishProgress()方法完成。                        publishProgress(progress);                    }                }                response.body().close();                return TYPE_SUCCESS;            }        } catch (IOException e) {            e.printStackTrace();        }finally {            try{                if(is!=null){                    is.close();                }                if(savedFile!=null){                    savedFile.close();                }                if(isCanceled&&file!=null){                    file.delete();                }            }catch (Exception e){                e.printStackTrace();            }        }        return TYPE_FAILED;    }    /**     * 当在后台任务中调用了publishProgress(Progress...)方法之后,onProgressUpdate()方法     * 就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以     * 对界面进行相应的更新。     * @param values     */    protected void onProgressUpdate(Integer...values){        int progress=values[0];        if(progress>lastProgress){            listener.onProgress(progress);            lastProgress=progress;        }    }    /**     * 当后台任务执行完毕并通过Return语句进行返回时,这个方法就很快被调用。返回的数据会作为参数     * 传递到此方法中,可以利用返回的数据来进行一些UI操作。     * @param status     */    @Override    protected void onPostExecute(Integer status) {        switch (status){            case TYPE_SUCCESS:                listener.onSuccess();                break;            case TYPE_FAILED:                listener.onFailed();                break;            case TYPE_PAUSED:                listener.onPaused();                break;            case TYPE_CANCELED:                listener.onCanceled();                break;            default:                break;        }    }    public void  pauseDownload(){        isPaused=true;    }    public void cancelDownload(){        isCanceled=true;    }    /**     * 得到下载内容的大小     * @param downloadUrl     * @return     */    private long getContentLength(String downloadUrl){        OkHttpClient client=new OkHttpClient();        Request request=new Request.Builder().url(downloadUrl).build();        try {            Response response=client.newCall(request).execute();            if(response!=null&&response.isSuccessful()){                long contentLength=response.body().contentLength();                response.body().close();                return contentLength;            }        } catch (IOException e) {            e.printStackTrace();        }        return  0;    }}

(3)开启一个服务:DownloadService.java

package com.example.servicebestpractice;import android.app.Notification;import android.app.NotificationManager;import android.app.PendingIntent;import android.app.Service;import android.content.Context;import android.content.Intent;import android.graphics.BitmapFactory;import android.os.Binder;import android.os.Environment;import android.os.IBinder;import android.support.v4.app.NotificationCompat;import android.widget.Toast;import java.io.File;/** * 为了保证DownloadTask可以一直在后台运行,我们还需要创建一个下载的服务。 */public class DownloadService extends Service {    private DownloadTask downloadTask;    private String downloadUrl;    private DownloadListener listener=new DownloadListener() {        /**         * 构建了一个用于显示下载进度的通知         * @param progress         */        @Override        public void onProgress(int progress) {            //NotificationManager的notify()可以让通知显示出来。            //notify(),接收两个参数,第一个参数是id:每个通知所指定的id都是不同的。第二个参数是Notification对象。            getNotificationManager().notify(1,getNotification("Downloading...",progress));        }        /**         * 创建了一个新的通知用于告诉用户下载成功啦         */        @Override        public void onSuccess() {            downloadTask=null;            //下载成功时将前台服务通知关闭,并创建一个下载成功的通知            stopForeground(true);            getNotificationManager().notify(1,getNotification("Download Success",-1));            Toast.makeText(DownloadService.this,"Download Success",Toast.LENGTH_SHORT).show();        }        /**         *用户下载失败         */        @Override        public void onFailed() {            downloadTask=null;            //下载失败时,将前台服务通知关闭,并创建一个下载失败的通知            stopForeground(true);            getNotificationManager().notify(1,getNotification("Download Failed",-1));            Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show();        }        /**         * 用户暂停         */        @Override        public void onPaused() {            downloadTask=null;            Toast.makeText(DownloadService.this,"Download Paused",Toast.LENGTH_SHORT).show();        }        /**         * 用户取消         */        @Override        public void onCanceled() {            downloadTask=null;            //取消下载,将前台服务通知关闭,并创建一个下载失败的通知            stopForeground(true);            Toast.makeText(DownloadService.this,"Download Canceled",Toast.LENGTH_SHORT).show();        }    };    private DownloadBinder mBinder=new DownloadBinder();    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }    /**     * 为了要让DownloadService可以和活动进行通信,我们创建了一个DownloadBinder对象     */    class DownloadBinder extends Binder{        /**         * 开始下载         * @param url         */        public void  startDownload(String url){           if(downloadTask==null){               downloadUrl=url;               downloadTask=new DownloadTask(listener);               //启动下载任务               downloadTask.execute(downloadUrl);               startForeground(1,getNotification("Downloading...",0));               Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();           }        }        /**         * 暂停下载         */        public void pauseDownload(){            if(downloadTask!=null){                downloadTask.pauseDownload();            }        }        /**         * 取消下载         */        public void cancelDownload(){            if(downloadTask!=null){                downloadTask.cancelDownload();            }else {                if(downloadUrl!=null){                    //取消下载时需要将文件删除,并将通知关闭                    String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));                    String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();                    File file=new File(directory+fileName);                    if(file.exists()){                        file.delete();                    }                    getNotificationManager().cancel(1);                    stopForeground(true);                    Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();                }            }        }    }    /**     * 获取NotificationManager的实例,对通知进行管理     * @return     */    private NotificationManager getNotificationManager(){        return (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);    }    /**     *     * @param title     * @param progress     * @return     */    private Notification getNotification(String title,int progress){        Intent intent=new Intent(this,MainActivity.class);        //PendingIntent是等待的Intent,这是跳转到一个Activity组件。当用户点击通知时,会跳转到MainActivity        PendingIntent pi=PendingIntent.getActivity(this,0,intent,0);        /**         * 几乎Android系统的每一个版本都会对通知这部分功能进行获多或少的修改,API不稳定行问题在通知上面凸显的尤其严重。         * 解决方案是:用support库中提供的兼容API。support-v4库中提供了一个NotificationCompat类,使用它可以保证我们的         * 程序在所有的Android系统版本中都能正常工作。         */        NotificationCompat.Builder builder=new NotificationCompat.Builder(this);        //设置通知的小图标        builder.setSmallIcon(R.mipmap.ic_launcher);        //设置通知的大图标,当下拉系统状态栏时,就可以看到设置的大图标        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));        //当通知被点击的时候,跳转到MainActivity中        builder.setContentIntent(pi);        //设置通知的标题        builder.setContentTitle(title);        if(progress>0){            //当progress大于或等于0时,才需要显示下载进度            builder.setContentText(progress+"%");            builder.setProgress(100,progress,false);        }        return builder.build();    }}

(4)MainActivity.java

package com.example.servicebestpractice;import android.Manifest;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.content.pm.PackageManager;import android.os.Bundle;import android.os.IBinder;import android.support.v4.app.ActivityCompat;import android.support.v4.content.ContextCompat;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.Toast;public class MainActivity extends AppCompatActivity implements View.OnClickListener{    private DownloadService.DownloadBinder downloadBinder;    private ServiceConnection connection=new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            downloadBinder=(DownloadService.DownloadBinder) service;        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Button startDownload=(Button) findViewById(R.id.start_download);        startDownload.setOnClickListener(this);        Button pauseDownload=(Button) findViewById(R.id.pause_download);        pauseDownload.setOnClickListener(this);        Button cancelDownload=(Button)findViewById(R.id.cancel_download);        cancelDownload.setOnClickListener(this);        Intent intent=new Intent(this,DownloadService.class);        //这一点至关重要,因为启动服务可以保证DownloadService一直在后台运行,绑定服务则可以让MaiinActivity和DownloadService        //进行通信,因此两个方法的调用都必不可少。        startService(intent);  //启动服务        bindService(intent,connection,BIND_AUTO_CREATE);//绑定服务        /**         *运行时权限处理:我们需要再用到权限的地方,每次都要检查是否APP已经拥有权限         * 下载功能,需要些SD卡的权限,我们在写入之前检查是否有WRITE_EXTERNAL_STORAGE权限,没有则申请权限         */        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){            ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);        }    }    public void onClick(View v){        if(downloadBinder==null){            return;        }        switch (v.getId()){            case R.id.start_download:                //String url="http://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";                String url="http://10.0.2.2:8080/ChromeSetup.exe";                downloadBinder.startDownload(url);                break;            case R.id.pause_download:                downloadBinder.pauseDownload();                break;            case R.id.cancel_download:                downloadBinder.cancelDownload();                break;            default:                break;        }    }    /**     * 用户选择允许或拒绝后,会回调onRequestPermissionsResult     * @param requestCode  请求码     * @param permissions     * @param grantResults  授权结果     */    @Override    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults) {       switch (requestCode){           case 1:               if(grantResults.length>0&&grantResults[0]!= PackageManager.PERMISSION_GRANTED){                   Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();                   finish();               }           break;       }    }    @Override    protected void onDestroy() {        super.onDestroy();        //解除绑定服务        unbindService(connection);    }}
(5)简单的布局文件:activity_main.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:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="com.example.servicebestpractice.MainActivity">    <Button        android:id="@+id/start_download"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Start Download"/>    <Button        android:id="@+id/pause_download"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Pause Download"/>    <Button        android:id="@+id/cancel_download"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Cancel Download"/></LinearLayout>
(6)AndroidMainfest.xml

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.example.servicebestpractice">    <uses-permission android:name="android.permission.INTERNET" />    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />    <application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:supportsRtl="true"        android:theme="@style/AppTheme">        <activity android:name=".MainActivity">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>        <service            android:name=".DownloadService"            android:enabled="true"            android:exported="true"></service>    </application></manifest>
(7)build.gradle

 compile 'com.squareup.okhttp3:okhttp:3.4.1'
(8)最后给出项目下载地址:

https://github.com/Microstrong0305/OKHttp_DownloadFile

0 0
原创粉丝点击