AsyncTask学习与实战

前言

Android 系统默认会在主线程(UI 线程)执行任务,但是如果有耗时程序就会阻塞 UI 线程,导致页面卡顿。这时候我们通常会将耗时任务放在独立的线程,然后通过 Handler 等线程间通信机制完成 UI 的刷新。很多时候我们也许只是想执行一个简单的任务,为此写一套 Handler 线程通信就会显得比较复杂,不用担心,Android 系统为我们提供了一个专门用于执行异步任务的工具——AsyncTask
在Android的android.os.AsyncTask的类前注释(2021-05),对AsyncTask有着比较负面的评价,但不会影响我们在短时间的后台线程操作中使用它:


AyncTask 旨在轻松和正确使用UI线程,但是最常见的用例是集成到UI中,这将导致Context泄漏,遗漏回调或者因配置更改导致crash等问题,在平台的不同版本中,它的行为也不一致,隐藏了doInBackground的异常,并且与直接使用Executor相比,并没有提供太多实用程序。AsyncTask是围绕Thread和Handler的辅助类,并不是通用的线程框架。理想情况下,AsyncTask应该用于短时间的操作(最多几秒钟),如果需要长时间保持线程运行,强烈建议您使用java提供的java.util.concurrent软件包,例如ThreadPoolExecutor和FutureTask


基本用法

声明AsyncTask

我们首先先来看一下AsynTask的基本用法,由于AsyncTask是一个抽象类,所以我们不能创建AsyncTask,应该是继承自AsyncTask实现一个它的子类,在继承时我们可以为AsyncTask类指定3个泛型参数,这3个参数的用途如下:

  • Params; 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
  • Progress; 后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进展单位
  • Result; 当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型
    因此一个最简单的自定义AsyncTask就可以写成如下方式:
public class MyAsyncTask extends AsyncTask {
    @Override
    protected void onPreExecute() {
        //运行在主线程,在doBackground前执行
        super.onPreExecute();
    }

    @Override
    protected void onPostExecute(Long aLong) {
        //执行完毕,更新UI
        super.onPostExecute(aLong);
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        //任务执行进度更新
        super.onProgressUpdate(values);
    }

    @Override
    protected Long doInBackground(URL... urls) {
        //执行后台耗时任务
        return null;
    }
}

这里我们把AsyncTask的第一个泛型参数指定为void,表示在执行AsyncTask的时候不需要传入参数给后台任务。第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位。第二个泛型参数指定为Boolean,则表示使用Boolean 数据来反馈执行结果。

常用重写方法

  • onPreExecute()
    这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
  • doInBackgroud(Params…)
    在执行完onPreExecute()方法之后立即被调用并且在子线程中执行,用来执行需要放在后台执行的耗时任务。在创建AsyncTask的时候传入的参数就是提供给doInBackgroud使用的。在后台任务执行完毕后,还需要将执行结果返回到onPostExecutes()中,同时我们也可以通过publishProgress(Progress…)方法来手动发布任务进度,进度将从子线程发送到UI线程中
  • onProgressUpdate(Progress…)
    当我们通过publishProgress(Params)发布进度之后,系统会回调此方法,用来获取任务执行进度并更新UI。这一步就完成了从子线程到主线程的通信,该方法在UI线程执行
  • onPostExecute(Result)
    当后台任务执行完毕,该方法被回调,同时标志着整个SyncTask结束,与onPreExecute相反,通过会在onPostExecute中做一些回调工作,比如提示“下载完成”,“加载失败”,“隐藏进度条”等等

实战

本节,是参考《第一行代码》第二版 第十章关于AsyncTask的例子,实现了下载QQ的安装包的例子

  • 创建SyncTask的子类
package com.example.servicebestpratice;

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.OkHttp;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class DownloadTask extends AsyncTask<String, Integer, Integer> {
     
    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 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;
        try {
     
            long downloadedLength = 0;//记录已下载的文件长度
            String downloadUrl = params[0];
            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
            String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
            Log.d(TAG, "doInBackground directory is" + directory);
            file = new File(directory + fileName);
            if (file.exists()) {
     
                downloadedLength = file.length();
                Log.d(TAG, "doInBackground file is exists, downloadLength is:" + downloadedLength);
            }
            long contentLength = getContentLength(downloadUrl);
            Log.d(TAG, "get contentLength from url,the length:" + contentLength);
            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) {
     
                Log.e(TAG, "doInBackground,response is null");
                return TYPE_FAILED;
            }
            is = response.body().byteStream();
            savedFile = new RandomAccessFile(file, "rw");
            savedFile.seek(downloadedLength);
            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 + downloadedLength) * 100 / contentLength);
                    Log.d(TAG, "loading ,the progress is " + progress);
                    publishProgress(progress);
                }
            }
            Log.d(TAG, "response.body.close");

            response.body().close();
            return TYPE_SUCCESS;


        } catch (Exception e) {
     
            Log.e(TAG, e.getMessage());
            e.printStackTrace();
        } finally {
     
            try {
     
                if (is != null) {
     
                    is.close();
                }
                if (savedFile != null) {
     
                    savedFile.close();
                }
                if (isCanceled && file != null) {
     
                    file.delete();
                }
            } catch (Exception e) {
     
                Log.e(TAG, e.getMessage());
                e.printStackTrace();
                Log.e(TAG, "download failed");
            }


        }
        return TYPE_FAILED;
    }

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

    /**
     * 用于通知最终的下载结果
     *
     * @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;
    }

    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.close();
            Log.i(TAG, "getContentLength response.isSuccessful(),the contentLength is" + contentLength);
            return contentLength;
        } else {
     
            Log.d(TAG, "getContentLength response is:" + response.isSuccessful() + ",downloadUrl is" + downloadUrl);
        }
        return 0;
    }
}
  • 创建下载的后台Servicre类
package com.example.servicebestpratice;

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.util.Log;
import android.widget.Toast;

import androidx.core.app.NotificationCompat;

import java.io.File;

public class DownloadService extends Service {
     
    private static final String TAG="DownloadService";
    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;
            stopForeground(true);
            Toast.makeText(DownloadService.this,"Canceled",Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onCanceled() {
     

        }
    };
    public DownloadService() {
     
    }

    private DownloadBinder mBinder=new DownloadBinder();
    @Override
    public IBinder onBind(Intent intent) {
     
        return mBinder;
    }


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

    class DownloadBinder extends Binder{
     
        public void startDownload(String url){
     
            if(downloadTask==null){
     
                downloadUrl=url;
                downloadTask=new DownloadTask(listener);
                //调用execute 开启下载
                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();
                    Log.d(TAG,directory);
                    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 Notification getNotification(String title,int progress){
     
        Intent intent=new Intent(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+"%");
            //显示进度:最大进度,当前进度,是否使用模糊进度条
            builder.setProgress(100,progress,false);
        }
        return builder.build();
    }
}
  • 构建下载接口
package com.example.servicebestpratice;

public interface DownloadListener {
     
    /**
     *通知当前下载进度
     * @param progress
     */
    void onProgress(int progress);

    void onSuccess();

    void onFailed();

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

    void onCanceled();
}
  • Activity 的UI 样式

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <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"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="https://down.qq.com/qqweb/QQ_1/android_apk/Android_8.7.0.5295_537068059.apk"/>
LinearLayout>
  • MainActivity类
package com.example.servicebestpratice;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

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.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
     

    private DownloadService.DownloadBinder downloadBinder;
    
    //构建ServiceConnection的实现对象,通过onServiceConnected实现来创建Binder对象
    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);//启动服务
        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 onClick(View v) {
     
        if(downloadBinder==null){
     
            return;
        }
        switch (v.getId()){
     
            case R.id.start_download:
                String url="https://down.qq.com/qqweb/QQ_1/android_apk/Android_8.7.0.5295_537068059.apk";

                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, @NonNull String[] permissions, @NonNull int[] grantResults) {
     
        switch (requestCode){
     
            case 1:
                if(grantResults.length>0 && grantResults[0]!=PackageManager.PERMISSION_GRANTED){
     
                    Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDestroy() {
     
        super.onDestroy();
        unbindService(connection);
    }
}

参考资料

  • 《第一行代码(第二版)》
  • http://www.imooc.com/wiki/androidlesson/asynctask.html

你可能感兴趣的:(Android)