Android 断点续传下载

什么是断点续传:

可以知道当前下载进度,并且可以下载一部分的时候进行停止下载,下载一部分的时候进行继续下载

 

这里是郭霖的《Android第一行代码》中的示例代码

我看的时候是基于Android7.0的,我又加了新特性NotificationChannel通知渠道,还有下载完成扫描文件

 

分4部分:MainActivity,DownloadTask,DownloadService,DownloadListener

用到了回调接口,绑定服务,前台服务,子线程异步消息处理机制,IO操作,网络请求,通知

 

先从最简单的开始

申请权限和导入依赖

这里就一个网络权限,和一个写入权限(写入权限需要动态获取)

    
    

依赖就一个Okhttp

    implementation 'com.squareup.okhttp3:okhttp:3.4.1'

 

DownloadListener接口

这是下载用的一个回调接口,定义了5个方法,在Service中实例化并实现这5个回调方法,作用是更新进度,和下载结果的显示

public interface DownloadListener {
    //下载进度显示
    void onProgress(int progress);
    //下载状态显示,成功,失败,暂停,取消
    void onSuccess();
    void onFailed();
    void onPaused();
    void onCanceled();
}

MainActivity

这个是活动,启动服务的方式为绑定服务,所以初始化了ServiceConnection类和服务中自定义的DownloadBinder类,在连接成功时获取DownloadBinder对象

设置了三个按钮分别是开始下载,暂停下载,取消下载,具体实现是DownloadBinder中对应的这三个方法

在Activity创建的时候onCreate()中绑定服务,并且动态申请写入权限,在Activity销毁的Destroy()中解绑服务

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    //初始化对象
    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);
        Button pauseDownload= (Button) findViewById(R.id.pause_download);
        Button cancelDownload= (Button) findViewById(R.id.cancel_download);
        //下载按钮
        startDownload.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = 26)
            @Override
            public void onClick(View v) {
                String url="http://f.hiphotos.baidu.com/image/pic/item/b7fd5266d016092446517fdadd0735fae7cd34ff.jpg";
                downloadBinder.startDownload(url);
            }
        });
        //暂停按钮
        pauseDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                downloadBinder.pauseDownload();
            }
        });
        //取消按钮
        cancelDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                downloadBinder.cancelDownload();
            }
        });

        //绑定启动服务
        Intent intent=new Intent(this,DownloadService.class);
        bindService(intent,connection,BIND_AUTO_CREATE);

        //申请写入权限
        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);
        }


    }

    @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;
            default:
        }
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        //断开服务的绑定
        unbindService(connection);
    }
}

DownloadTask类

这个是开启子线程实现具体网络请求和下载操作的类

这里直接继承了AsyncTask<>异步消息处理类(本质也是Android异步消息处理机制),对于异步消息处理机制不懂的请看

https://blog.csdn.net/yh18668197127/article/details/86224318

构造方法获取Context对象和DownloadListener对象

在doInBackground进行具体下载操作

先判断本地文件大小和远程文件的大小是否一致判断是否下载成功

下载的时候网络请求当前已下载的byte到文件末尾,写入文件到本地的时候也是从当前已下载的末尾开始写入

在写入文件的时候调用publishProgress(progress);传递下载进度

在onProgressUpdate中接收下载进度参数与当前下载进度对比,进行下载进度的更新

在onPostExecute中接收返回值,根据返回值来更新下载状态

public class DownloadTask extends AsyncTask{
    private static final String TAG = "DownloadTask";
    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 Context context;

    private boolean isCanceled=false;
    private boolean isPaused=false;
    private int lastProgress=0;
    public DownloadTask(DownloadListener listener,Context context){
        this.listener=listener;
        this.context=context;
    }

    @Override
    protected Integer doInBackground(String ... params) {
        InputStream inputStream = null;
        RandomAccessFile savedFile = null;
        File file = null;
        try {
            long downloadedLength = 0;//已下载长度
            String downloadUrl = params[0];
            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));

            String directory= Environment.getExternalStorageDirectory().getPath();

            Log.i(TAG, "doInBackground: "+directory+" "+fileName);

            file = new File(directory + fileName);
            if (file.exists()) {
                downloadedLength = file.length();
            }
            long contentLength = getContentLength(downloadUrl);
            if (contentLength == 0) {
                return TYPE_FAILED;
            } else if (contentLength == downloadedLength) {
                return TYPE_SUCCESS;
            }
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .addHeader("RANGE", "bytes=" + downloadedLength + "-")
                    .url(downloadUrl)
                    .build();
            Response response = client.newCall(request).execute();
            if (response != null) {
                inputStream = response.body().byteStream();
                savedFile = new RandomAccessFile(file, "rw");
                savedFile.seek(downloadedLength);
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                while ((len = inputStream.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 + downloadedLength) * 100 / contentLength);
                        publishProgress(progress);
                    }
                }
                //下载完成调用系统文件扫描机制,否则电脑连接显示不了文件
                MediaScannerConnection.scanFile(context, new String[] { file.getAbsolutePath() }, null, null);
                return TYPE_SUCCESS;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (savedFile != null) {
                    savedFile.close();
                }
                if (isCanceled && file != null) {
                    file.delete();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;
    }


    @Override
    protected void onProgressUpdate(Integer...values){
        int progress=values[0];
        if (progress>lastProgress){
            listener.onProgress(progress);
            lastProgress=progress;
        }
    }

    @Override
    protected void onPostExecute(Integer status){
        switch (status){
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
            default:
                break;
        }
    }

    public void pauseDownload(){
        isPaused=true;
    }

    public void cancelDownload(){
        isCanceled=true;
    }


    private long getContentLength(String downloadUrl) throws IOException {
        OkHttpClient client=new OkHttpClient();
        Request request=new Request.Builder()
                .url(downloadUrl)
                .build();
        Response response=client.newCall(request).execute();
        if (response!=null&&response.isSuccessful()){
            long contentLength=response.body().contentLength();
            response.body().close();
            return  contentLength;
        }
        return 0;
    }
}

DownloadService服务

自定义了DownloadBinder继承Binder

创建了DownloadListener对象并实现了它的五个回调方法

通过通知的方式来显示下载进度,和下载结果

//这是一个服务
public class DownloadService extends Service {

    final String CHANNEL_ID = "channel_id_1";
    final String CHANNEL_NAME = "channel_name_1";

    private DownloadTask downloadTask;
    private String downloadUrl;

    private DownloadListener listener = new DownloadListener() {
        @RequiresApi(api = 26)
        @Override
        public void onProgress(int progress) {
            getNotificationManager().notify(1, getNotification("Downloading...", progress));
        }

        @RequiresApi(api = 26)
        @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();
        }

        @RequiresApi(api = 26)
        @Override
        public void onFailed() {
            downloadTask = null;
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Failed", -1));
            Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPaused() {
            downloadTask = null;
            Toast.makeText(DownloadService.this, "Paused", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onCanceled() {
            downloadTask = null;
            stopForeground(true);
            Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
        }
    };

    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder {
        @RequiresApi(api = 26)
        public void startDownload(String url) {
            if (downloadTask == null) {
                downloadUrl = url;
                downloadTask = new DownloadTask(listener,getApplication());
                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();
            }
            if (downloadUrl != null) {
                String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
//                String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
                String directory = Environment.getExternalStorageDirectory().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();
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }


    private NotificationManager getNotificationManager() {
        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }

    @RequiresApi(api = 26)
    private Notification getNotification(String s, int progress) {
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivities(this, 0, new Intent[]{intent}, 0);

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            //只在Android O之上需要渠道
            NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID,
                    CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
            //如果这里用IMPORTANCE_NOENE就需要在系统的设置里面开启渠道,
            //通知才能正常弹出
            getNotificationManager().createNotificationChannel(notificationChannel);
        }

        Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID);
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
        builder.setContentIntent(pi);
        builder.setContentTitle(s);
        if (progress > 0) {
            builder.setContentText(progress + "%");
            builder.setProgress(100, progress, false);
        }
        return builder.build();
    }
}

 

你可能感兴趣的:(Android代码库)