每天学习一个Android中的常用框架——13.AsyncTask

1.简介

AsyncTask,是除了Handler之外,Android提供给我们方便地在子线程中对UI进行操作的另一个工具。借助AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单地从子线程切换到主线程。当然,AsyncTask背后的实现原理也是基于异步消息处理机制的,只是Android帮我们做了很好的封装而已。

AsyncTask属于已经封装好的轻量级异步类,主要的功能仍然是多线程通信以及消息处理。其优点主要为:

  • 方便实现异步通信:不需要使用 “任务线程(如继承Thread类) + Handler”的复杂组合
  • 节省资源:采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销。

下面我们再来看看AsyncTask拥有的一些特性。

2.特性

菜鸟教程中对于AsyncTask的介绍中,写得较为详细,这里就直接贴出部分说明图,供读者参考:

首先,AsyncTask是一个抽象类,一般我们都会定义一个类继承AsyncTask然后重写相关方法,其构造方法的参数说明如图所示:
在这里插入图片描述
接下来,AsyncTask中相应方法的执行流程如下:
在这里插入图片描述
最后,再给出AsyncTask的使用注意事项,如下所示:
在这里插入图片描述
直接说明这些概念可能会让初次学习AsyncTask的读者觉得费解,接下来就会演示AsyncTask最经典的应用案例:进度条来说明AsyncTask的使用方法。

3.演示

3.1 集成

AsyncTaskHandler一样,默认就是集成在Android SDK(具体来说,在Android 1.5版本之后)中的,所以这一步可以省略,你可以在代码中直接使用AsyncTask来处理消息机制。

3.2 布局文件

为了实现进度条的功能,在布局文件activity_main.xml中,我们定义一个按钮控件,用来触发进度条的运动,然后再定义一个进度条控件(ProgressBar),最后再定义一个文本控件,记录进度条的进度值,代码如下:

<?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"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/pb_test"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:max="100"
        android:progress="0"/>

    <Button
        android:id="@+id/btn_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="启动进度条"/>

    <TextView
        android:id="@+id/tv_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="100sp"
        android:gravity="center"
        android:text="进度值"/>


</LinearLayout>

布局效果大致如下:
在这里插入图片描述

3.3 创建AsyncTask子类

前面说过,AsyncTask是一个抽象类。要想使用,就需要先创建一个集成它的子类。这里新建一个名为ProgressAsyncTask的AsyncTask子类,然后重写相应的方法,代码如下:

/**
 * AsyncTask<Params, Progress, Result>三个泛型参数
 * Params
 * 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。本例中第一个泛型参数指定为Void,表示在执行AsyncTask的时候不需要传入参数给后台任务。
 * Progress
 * 后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。本例中第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位。
 * Result
 * 当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。本例中第三个泛型参数指定为Boolean,则表示使用布尔型数据来反馈执行结果。
 */
public class ProgressAsyncTask extends AsyncTask<Void,Integer,Boolean> {

    // 定义ProgressBar
    private ProgressBar pb_test;

    // 定义文本控件
    private TextView tv_test;

    public ProgressAsyncTask(ProgressBar pb_test, TextView tv_test) {
        super();
        this.pb_test = pb_test;
        this.tv_test = tv_test;
    }

    /**
     * 这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,
     * 比如显示一个进度条对话框等。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。
     * 任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的
     * 第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不
     * 可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用
     * publishProgress(Progress...)方法来完成。
     */
    @Override
    protected Boolean doInBackground(Void... voids) {
        for (int i = 0; i < 100; i++) {
            try {
                // 模拟耗时操作
                Thread.sleep(100);
                publishProgress(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }

    /**
     * 当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,
     * 方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参
     * 数中的数值就可以对界面元素进行相应的更新。
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        int value = values[0];
        pb_test.setProgress(value);
        tv_test.setText(value + "");
    }

    /**
     * 当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据
     * 会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行
     * 的结果,以及关闭掉进度条对话框等。
     */
    @Override
    protected void onPostExecute(Boolean aBoolean) {
        tv_test.setText("进度条加载完毕!");
    }
}

我们首先来看一下AsyncTask的基本格式。由于AsyncTask是一个抽象类,所以如果我们想使用它,就必须要创建一个子类去继承它。在继承时我们需要为AsyncTask类指定3个泛型参数,这3个参数的用途如下:

  • Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
  • Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
  • Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。

本例中已给出了详尽的注释,读者也可参考注释来配合理解。


在AsyncTask中,比较常用的重写方法如图所示:
在这里插入图片描述
在本例中,我们仅重写了AsyncTask中的4个方法,这4个方法相对来说更加重要,它们的作用如下所示:

  • onPreExecute()
    这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
  • doInBackground(Params…),该方法强制要求重写
    这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress…)方法来完成。
  • onProgressUpdate(Progress…)
    当在后台任务中调用了publishProgress(Progress…)方法后,onProgressUpdate (Progress…)方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
  • onPostExecute(Result)
    当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

这里为了方便演示,使用了Thread.sleep(100)模拟耗时操作。

简单来说,使用AsyncTask的步骤就是,在doInBackground()方法中执行具体的耗时任务, 在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。

3.4 获取AsyncTask对象并执行任务

当我们创建好AsyncTask的实例后,接下来就是获取其对象,然后调用AsyncTask中的execute()来执行这个任务。MainActivity的代码如下:

package com.androidframelearn.event_asynctask;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // 定义ProgressBar
    private ProgressBar pb_test;

    // 定义文本控件
    private TextView tv_test;

    // 定义按钮
    private Button btn_test;

    // 定义ProgressAsyncTask
    private ProgressAsyncTask mProgressAsyncTask;

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

        // 初始化UI
        initUI();

        // 注册点击事件
        btn_test.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mProgressAsyncTask = new ProgressAsyncTask(pb_test, tv_test);
                mProgressAsyncTask.execute();
            }
        });
    }

    /**
     * 初始化UI
     */
    private void initUI() {
        pb_test = findViewById(R.id.pb_test);
        tv_test = findViewById(R.id.tv_test);
        btn_test = findViewById(R.id.btn_test);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 关闭AsyncTask,防止内存泄漏
        mProgressAsyncTask.cancel(true);
    }
}

完成以上两步后,进度条的业务应该就已经实现了。可以运行一下项目,然后查看效果,如图所示:
在这里插入图片描述
在这里插入图片描述

3.5 注意事项

使用AsyncTask时,有一些问题需要注意:

  1. 生命周期:AsyncTask不与任何组件绑定生命周期,使用时应该如同本例一样在Activity或者FragmentonDestroy()中调用cancel(boolean)来关闭AsyncTask;
  2. 内存泄漏:若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用,而导致Activity无法被回收,最终引起内存泄露,使用时应将AsyncTask声明为Activity的静态内部类;
  3. 线程执行结果:当Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作,使用时需要在Activity恢复时的对应方法重启任务线程。

4.源码地址

AFL——Android框架学习

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赈川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值