安卓下载实例

作者:郭霖老大

这是郭霖老大的服务知识实战代码

想要动手实战的话可以看看下面知识:

异步消息处理机制 AsyncTask用法 如何定义服务 活动与服务的通信

步骤:

    1.定义一个Interface类DownloadListener可以在Download中根据下载任务的结果进行UI更新操作

    2.定义一个下载任务类DownloadTask继承AsynTask类,然后重写doInBackground()方法子线程中执行

一些耗时的网络请求和文件进度和内容更新操作

    3.创建一个DownloadService服务,在其中创建DonwloadListener匿名类实例,并在匿名类中实现一些方法。这些方法就是根据下载任务返回的结果在UI线程中更新UI。另外为了活动和服务可以通信我们创建了DownloadBinder类,可以开始下载,暂停下载与取消下载,这些方法可以在活动中调用

    4.在MainActivity类中创建ServiceConnection的匿名类,在onServiceConnected()方法中获取到DownloadBinder的实例。

给三个按钮设置监听器,启动服务和绑定服务,然后请求访问SD卡权限,根据不同监听结果调用downloadBinder的方法控制下载进度

下面是一个回调接口类DownloadListener

package com.gougoucompany.clarence.servicebestpractice;

/**
 * Created by Clarence on 2018/4/16.
 */

public interface DownloadListener {

    void onProgress(int progress); //通知当前进度

    void onSuccess(); //通知下载成功

    void onFailed(); //通知下载失败

    void onPaused(); //通知下载暂停

    void onCanceled(); //通知下载取消
}

定义一个服务DownloadService类

/*
* @Author: Clarence
* @Date:   2018-04-17 23:24:52
* @Last Modified by:   Clarence
* @Last Modified time: 2018-04-17 23:25:18
*/
package com.gougoucompany.clarence.servicebestpractice;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
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.util.Log;
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() {
        @Override
        public void onProgress(int progress) {
            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, "Paused", Toast.LENGTH_SHORT).show();

        }

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

    //DownloadServer可以和活动进行通信
    private DownloadBinder mBinder = new DownloadBinder();

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

    //开始下载,停止下载,和取消下载方法
    class DownloadBinder extends Binder {

        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();
            }
            if(downloadUrl != null) {
                //取消下载时需将文件删除,并将通知关闭
                String fileName = downloadUrl.substring(downloadUrl.
                        lastIndexOf("/"));
                String directory = Environment.getExternalStoragePublicDirectory(
                        Environment.DIRECTORY_DOWNLOADS
                ).getPath();
                Log.d("DownloadService", "下载的文件路径是: " + directory + fileName);
                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();
            }
        }
    }

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

    private Notification getNotification(String title, int progress) {
        Intent intent = new Intent(DownloadService.this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
        builder.setContentIntent(pi);
        builder.setContentTitle(title);
        if(progress >= 0) {
            //当progress大于或等于0时才需要显示下载进度
            builder.setContentText(progress + "%");
            //setProgress()方法有三个参数,作用分别是传入通知的最大进入,第二个参数传入通知的当前进度,第三个参数表示是否使用模糊进度条
            builder.setProgress(100, progress, false);
        }
        return builder.build();

    }
}

下面是一个异步消息处理的封装类,我们可以在子线程中完成耗时的操作,处理网络请求和写入文件

DownloadTask类

package com.gougoucompany.clarence.servicebestpractice;

import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;


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 Clarence on 2018/4/16.
 * 3个泛型参数分别是:
 * 1.第一个泛型参数指定为String,表示在执行任务的时候传入一个字符串参数给后台任务
 * 2.第二个泛型参数指定为Integer,表示使用整形数据来作为进度显示单位
 * 3.第三个泛型参数指定为Integer,表示使用整形数据来反馈执行结果
 */

public class DownloadTask extends AsyncTask<String, Integer,Integer> {

    private static final int TYPE_SUCCESS = 0;
    private static final int TYPE_FAILED = 1;
    private static final int TYPE_PAUSED = 2;
    private static final int TYPE_CANCELED = 3;

    //DownloadListener是一个回调接口,用于对下载过程中的各种状态进行监听和回调
    private DownloadListener listener;

    private boolean isCanceled = false;

    private boolean isPaused = false;

    private int lastProgress;

    public DownloadTask(DownloadListener listener){
        this.listener = listener;
    }

    /*这个方法中的所有代码都会在子线程中执行,我们应该在这个方法中处理所有的耗时任务。
    * 任务一旦完成,可以通过return语句来将任务的执行结果返回*/
    @Override
    protected Integer doInBackground(String... params) {
        InputStream is = null;
        RandomAccessFile savedFile = null;
        File file = null;
        try {
            long downloadedLength = 0; //记录已下载的文件长度
            String downloadUrl = params[0];
            //substring()方法从索引号为downloadUrl.lastIndexOf("/)返回的索引号开始截取
            String filename = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
            //获取内置SD卡的Download路径
            String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).
                    getPath();
            Log.d("DownloadTask", "下载的文件路径是: " + filename+directory);
            file = new File(directory + filename);
            //判断文件是否存在,如果存在则获取已经下载了的文件长度
            if(file.exists()){
                downloadedLength = file.length();
            }
            long contentLength = getContentLength(downloadUrl);
            //如果获得文件的长度是0则说明下载失败,如果等于已下载的文件长度,则说明下载成功结束
            if(contentLength == 0) {
                return TYPE_FAILED;
            } else if(contentLength == downloadedLength) {
                //已下载字节和文件总字节相等,说明已经下载完成了
                return TYPE_SUCCESS;
            }
            //创建一个OkHttpClient实例
            OkHttpClient client = new OkHttpClient();
            //我们要从下载了的文件之后继续下载,因此加入header开启断点续传的功能,指定从哪个字节开始下载
            Request request = new Request.Builder()
                    .addHeader("RANGE", "bytes=" + downloadedLength + "-")
                    .url(downloadUrl)
                    .build();
            Response response = client.newCall(request).execute();
            if(response != null){
                is = response.body().byteStream();
                savedFile = new RandomAccessFile(file, "rw");
                /*RandomAccessFile对象的seek()设置此文件开头测量文件指针偏移量,在该位置发生下一个读取或写入操作
                * 其实就是将文件指针移动到我们要开始写入的位置而已*/
                savedFile.seek(downloadedLength); //跳过已下载的字节
                byte b[] = new byte[1024];
                int total = 0;
                int len;
                /**
                 * 使用java的文件流方式,不断从网络上读取数据,不断写入到本地
                 * ,一直到文件全部下载完成为止。在这个过程中,我们还要判断用户有没有触发暂停
                 * 或者取消操作,如果有的话,则返回TYPE_PAUSED或TYPE_CANCELED来中断下载,如果没有
                 * 的话则实时计算当前的下载速度,然后调用publishProgress()方法进行通知
                 */
                while ((len = is.read(b)) != -1) {
                    if(isCanceled){
                        return TYPE_CANCELED;
                    } else if(isPaused){
                        return TYPE_PAUSED;
                    } else {
                        total += len;
                        //write(byte[] b, int offset, int len)方法从指定的字节数组开始到该文件偏移量off写入len字节
                        savedFile.write(b, 0, len);
                        //计算已下载的百分比
                        int progress = (int) ((total + downloadedLength) * 100 /
                                contentLength);
                        publishProgress(progress);
                    }
                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch(Exception 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()方法在界面上更新当前的下载进度
    @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_FAILED:
                listener.onFailed();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                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;
    }


}

下面是一个主活动MainActivity
/*
* @Author: Clarence
* @Date:   2018-04-17 23:26:00
* @Last Modified by:   Clarence
* @Last Modified time: 2018-04-17 23:49:36
*/
package com.gougoucompany.clarence.servicebestpractice;

import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private DownloadService.DownloadBinder downloadBinder;

    /**
     * 创建一个ServiceConnection的匿名类,然后在onServiceConnected方法中获取到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(this);
        pauseDownload.setOnClickListener(this);
        cancelDownload.setOnClickListener(this);
        Intent intent = new Intent(this, DownloadService.class);
        startService(intent); //启动服务
        //参数分别是Intent对象、ServiceConnection实例,标志位表示在活动和服务进行绑定后自动创建服务
        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);
            Log.d("MainActivity", "向用户请求权限");
        }
    }

    @Override
    public void onClick(View v) {
        if(downloadBinder == null) {
            return;
        }
        switch(v.getId()){
            case R.id.start_download:
                String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
                downloadBinder.startDownload(url);
                break;
            case R.id.pause_download:
                downloadBinder.pauseDownload();
                break;
            case R.id.cancel_download:
                downloadBinder.cancelDownload();
                break;
            default:
                break;
        }
    }

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //活动被销毁了,一定要记的对服务进行解绑
        unbindService(connection);
    }
}

下面是运行结果


猜你喜欢

转载自blog.csdn.net/qq_32252957/article/details/79965751