Android 启动服务配合AsyncTask 使用OKHttp 实现断点下载大文件实例

    前言:最近重读了下“第一行代码”,看到“第一行代码”的一个小项目,特写这篇博客梳理下流程。这个项目实现了使用OKHttp 断点下载大文件,通过服务在下载的过程中暂停和取消并更新通知消息,下面看下效果图:

Android 启动服务配合AsyncTask 使用OKHttp 实现断点下载大文件实例_第1张图片

首先总结一句话,在Android的多线程处理中,尽量做到在子线程中进行耗时操作,在主线程中更新界面UI。好了,下面开始写这个项目。

一。首先创建一个回调接口,用于对下载过程中的各种状态进行监听和回调,代码如下:

public interface DownloadListener {
    // 通知下载进度
    void onProgress(int progress);
    // 通知下载成功
    void onSuccess();
    // 通知下载失败
    void onFailed();
    // 通知下载暂停
    void onPaused();
    //通知下载失败
    void onCanceled();
}

    好吧,这里插一点题外话,回调函数。主要说一下回调函数的机制

    (1)。设置接口,定义回调函数

public interface MyListener {
    void onClick();
}

    (2)。提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者。 

public class Realize {
    private MyListener myListener;

    public void setMyListener(MyListener myListener) {
        this.myListener = myListener;
    }

    public void doSth() {
        myListener.onClick();
    }
}

    (3)。当特定的时间或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

public class Test {

    public static void main(String[] args) {
        Realize realize = new Realize();
        realize.setMyListener(new MyListener() {
            @Override
            public void onClick() {
                System.out.println("特定事件或条件发生了");
            }
        });

        realize.doSth();
    }
}

    上面就是一个回调函数的流程,仔细理解下,就能理解回调的实现了,下面回到正题。

二。使用AsyncTask实现下载功能的处理。  

    emmm,这里还要插入一下的AsyncTask的解释.AsyncTask的英文一种轻量级的异步任务类,这个类可以执行后台操作,并在用户界面上发布结果,而不必处理线程和处理程序.OK,先举个例子:

public class DownloadTask extends AsyncTask {

    /**
     * 刚开始执行的时候调用,可以用于进行一些界面上的初始化操作,比如说显示一个进度条对话框
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();

    }

    /**
     * 这里的代码会在子线程中执行,可以执行耗时操作
     *
     * @param voids
     * @return
     */
    @Override
    protected Boolean doInBackground(Void... voids) {
        //  反馈当前任务的进度,执行完这个方法会调用onProgressUpdate 方法
        publishProgress(50);
        return null;
    }

    /**
     * 根据返回的数据更新UI
     *
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);

    }
    
    /**
     * 后台任务执行完毕通过return语句进行返回时  也可以根据返回的数据更新UI
     *
     * @param aBoolean
     */
    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);

    }
}

     简单的来说,使用AsyncTask的诀窍就是,在doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。如果想要启动这个任务,只需编写以下代码即可:

                 new DownloadTask()。execute();

    继续回到正题(这样讲着讲着会不会歪楼呀)先在app / build.gradle文件中的依赖中添加OKHttp依赖

        编译'com.squareup.okhttp3:okhttp:3.4.1'

    下面编写下载功能,新建一个DownloadFileTask继承AsyncTask。

//   第一个参数  传给后台参数   第二个 使用整型数据作为进度显示单位   第三个  使用整型数据反馈执行结果
public class DownloadFileTask extends AsyncTask {

    // 下载成功
    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 isCancelled = false;
    // 是否暂停
    private boolean isPaused = false;
    // 当前进度
    private int lastProgress;

    /**
     * 带监听的构造函数
     *
     * @param listener
     */
    public DownloadFileTask(DownloadListener listener) {
        this.listener = listener;
    }

    /**
     * 在后台执行具体的下载逻辑  是在子线程里面 可以执行耗时操作
     */
    @Override
    protected Integer doInBackground(String... strings) {
        // 文件输入流
        InputStream is = null;
        RandomAccessFile accessFile = null;
        File file = null;
        // 记录已下载的文件长度
        long downloadedLength = 0;
        // 获取下载的URL地址
        String downloadUrl = strings[0];
        // 从URL下载地址中截取下载的文件名
        String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
        // 获取SD卡的Download 目录
        String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
        //  得到要保存的文件
        file = new File(directory + fileName);
        // 如果文件已经存在  获取文件的长度
        if (file.exists()) {
            downloadedLength = file.length();
        }
        try {
            //   获取待下载文件的字节长度
            long contentLength = getContentLength(downloadUrl);
            //  如果待下载文件的字节长度为0 说明待下载文件有问题
            if (contentLength == 0) {
                return TYPE_FAILED;
            } else if (contentLength == downloadedLength) {
                //   已下载字节和文件总字节相等 说明已经下载完了
                return TYPE_SUCCESS;
            }
            //  获取OkHttpClient 对象
            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) {
                // 读取服务器响应的数据
                is = response.body().byteStream();
                // 获取随机读取文件类  可以随机读取一个文件中指定位置的数据
                accessFile = new RandomAccessFile(file, "rw");
                // 跳过已下载的字节
                accessFile.seek(downloadedLength);
                //指定每次读取文件缓存区的大小为1KB
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                //   每次读取的字节长度
                while ((len = is.read(b)) != -1) {
                    if (isCancelled) {
                        return TYPE_CANCELED;
                    } else if (isPaused) {
                        return TYPE_PAUSED;
                    } else {
                        // 读取的全部字节的长度
                        total += len;
                        // 写入每次读取的字节长度
                        accessFile.write(b, 0, len);
                        // 计算已下载的百分比
                        int progress = (int) ((total + downloadedLength) * 100 / contentLength);
                        // 更新进度条
                        publishProgress(progress);
                    }
                }
                // 关闭连接  返回成功
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭输入流
                if (is != null) {
                    is.close();
                }
                // 关闭文件
                if (accessFile != null) {
                    accessFile.close();
                }
                Log.d("TAG", "这里永远都会执行 ");
                // 如果是取消的  就删除掉文件
                if (isCancelled && file != null) {
                    file.delete();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    /**
     * 获取下载文件的长度
     *
     * @param downloadUrl
     * @return
     * @throws IOException
     */
    private long getContentLength(String downloadUrl) throws IOException {
        // 获取OkHttpClient
        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.close();
            return contentLength;
        }
        return 0;
    }

    /**
     * 在界面上更新当前的下载进度
     *
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        int progress = values[0];
        if (progress > lastProgress) {
            listener.onProgress(progress);
            lastProgress = progress;
        }
    }

    /**
     * 用于通知最后的下载结果
     *
     * @param integer
     */
    @Override
    protected void onPostExecute(Integer integer) {
        super.onPostExecute(integer);
        switch (integer) {
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
        }
    }

    /**
     * 暂停下载
     */
    public void pauseDownload() {
        isPaused = true;
    }

    /**
     * 取消下载
     */
    public void cancelDownload() {
        isCancelled = true;
    }
}

 

三。创建一个下载的服务 

 

    没错,还得简单的介绍下服务(这么墨迹的吗)。一般我们都调用startService()方法来启动服务,并调用stopService()方法来停止这个服务,但这样启动服务后,活动无法干预到服务到底执行了怎样的逻辑。这时候就要用onBind()方法了,对服务进行绑定之后,就可以调用服务里的Binder提供的方法了.OK,下面举个个子子。首先创建一个MyService,代码如下:

public class MyService extends Service {

    private static final String TAG = "MyService";

    private DownloadBinder mBinder = new DownloadBinder();

    // 创建 DownloadBinder 实例  随便定义了两个方法
    class DownloadBinder extends Binder {

        public void startDownload() {
            Log.d(TAG, "startDownload: executed");
        }

        public int getProgress() {
            Log.d(TAG, "getProgress: executed");
            return 0;
        }
    }

    /**
     * 返回这个DownloadBinder 实例
     *
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    
}

    然后在活动中绑定服务,实现活动去指挥服务去干什么。

public class FirstActivity extends AppCompatActivity {

    private Button btnBind;
    private Button btnUnBind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);

        btnBind = (Button) findViewById(R.id.btnBind);
        btnUnBind = (Button) findViewById(R.id.btnUnBind);

        btnBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 绑定服务
                Intent intent = new Intent(FirstActivity.this, MyService.class);
                bindService(intent, connection, BIND_AUTO_CREATE);
            }
        });

        btnUnBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //  解绑服务
                unbindService(connection);
            }
        });

    }

    private MyService.DownloadBinder downloadBinder;

    ServiceConnection connection = new ServiceConnection() {
        /**
         * 活动与服务绑定成功后
         * @param name
         * @param service
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 向下转型获得DownloadBinder 实例 就能调用DownloadBinder的方法 进而控制服务的逻辑
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        /**
         * 活动与服务解绑后
         * @param name
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

}

    好的,了解到活动如何去控制服务以后,下面正式写下载的服务,代码如下:    

public class DownloadService extends Service {
    // 下载的异步操作类
    private DownloadFileTask downloadFileTask;
    // 下载地址
    private String downloadUrl;

    private static final String TAG = "DownloadService";
    // 下载状态的回调
    private DownloadListener listener = new DownloadListener() {
        /**
         *  更新下载进度状态
         * @param progress
         */
        @Override
        public void onProgress(int progress) {
            Log.d(TAG, "onProgress: -------" + progress);
            getNotificationManager().notify(1, getNotification("Downloading", progress));
        }

        /**
         * 下载成功
         */
        @Override
        public void onSuccess() {
            Log.d(TAG, "onSuccess: -------------");
            downloadFileTask = null;
            //下载成功时将前台服务通知关闭,并创建一个下载成功的通知
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Success", -1));
            Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();
        }

        /**
         * 下载失败
         */
        @Override
        public void onFailed() {
            Log.d(TAG, "onFailed: -------------");
            downloadFileTask = null;
            // 下载失败时将前台服务通知关闭,并创建一个下载失败的通知
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Failed", -1));
            Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show();
        }

        /**
         * 下载暂停
         */
        @Override
        public void onPaused() {
            Log.d(TAG, "onPaused: -------------");
            downloadFileTask = null;
            Toast.makeText(DownloadService.this, "Download Paused", Toast.LENGTH_SHORT).show();
        }

        /**
         * 下载取消
         */
        @Override
        public void onCanceled() {
            Log.d(TAG, "onCanceled: -------------");
            downloadFileTask = null;
            stopForeground(true);
            Toast.makeText(DownloadService.this, "Download Canceled", Toast.LENGTH_SHORT).show();
        }
    };

    DownloadBinder mBinder = new DownloadBinder();

    /**
     * 返回这个DownloadBinder 实例
     *
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    // 创建 DownloadBinder 实例 
    class DownloadBinder extends Binder {
        // 开始下载
        public void startDownload(String url) {
            Log.d(TAG, "startDownload--------: 开始下载");
            if (downloadFileTask == null) {
                downloadUrl = url;
                downloadFileTask = new DownloadFileTask(listener);
                downloadFileTask.execute(downloadUrl);
                startForeground(1, getNotification("DownLoading", 0));
                Toast.makeText(DownloadService.this, "Downloading", Toast.LENGTH_SHORT).show();
            }
        }

        // 暂停下载
        public void pauseDownload() {
            Log.d(TAG, "pauseDownload--------: 暂停下载");
            if (downloadFileTask != null) {
                downloadFileTask.pauseDownload();
            }
        }

        // 取消下载
        public void cancelDownload() {
            Log.d(TAG, "cancelDownload--------: 取消下载---" + downloadFileTask);
            if (downloadFileTask != null) {
                downloadFileTask.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();
                }
            }
        }
    }

    /**
     * 获取通知栏管理器
     *
     * @return
     */
    public NotificationManager getNotificationManager() {
        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }

    /**
     * 设置通知栏的样式 并获取通知栏的实例
     *
     * @param title
     * @param progress
     * @return
     */
    private Notification getNotification(String title, int progress) {
        Intent[] intents = new Intent[]{(new Intent(this, DownloadActivity.class))};
        PendingIntent pi = PendingIntent.getActivities(this, 0, intents, 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) {
            builder.setContentText(progress + "%");
            builder.setProgress(100, progress, false);
        }
        return builder.build();
    }
    
}

四,接下来在活动中绑定这个服务就可以了。

    1.先实现下界面,修改xml中的代码,创建三个按钮。启动暂停取消




    

    2.在活动中绑定服务,让活动与服务进行通信,因为要对文件下载,还要动态的申请写文件的权限,代码如下:

public class DownloadActivity extends AppCompatActivity implements View.OnClickListener {
    // 开始下载按钮
    private Button btnStart;
    // 暂停下载按钮
    private Button btnPause;
    // 取消下载按钮
    private Button btnCancel;
    // 服务
    private Intent intent;
    // 下载操作的实例
    private DownloadService.DownloadBinder downloadBinder;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        /**
         * 活动与服务绑定成功后
         * @param name
         * @param service
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (DownloadService.DownloadBinder) service;
        }

        /**
         * 活动与服务解绑后
         * @param name
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download);
        btnStart = (Button) findViewById(R.id.btnStart);
        btnPause = (Button) findViewById(R.id.btnPause);
        btnCancel = (Button) findViewById(R.id.btnCancel);

        btnStart.setOnClickListener(this);
        btnPause.setOnClickListener(this);
        btnCancel.setOnClickListener(this);
        // 启动服务并绑定
        intent = new Intent(this, DownloadService.class);
        startService(intent);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
        // 获取写的权限
        if (ContextCompat.checkSelfPermission(DownloadActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(DownloadActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }
    }

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

    /**
     * 申请权限的返回结果
     *
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, 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(serviceConnection);
        stopService(intent);
    }
}

最后在AndroidManifest.xml中文件中声明使用的权限,还有别忘了服务也是需要声明的。

      这个项目的总结就到这里就结束了......,最后附上源代码 源码下载地址

 

 

你可能感兴趣的:(Android)