Android的AsyncTask知识点总结

前言

1. AsyncTask简介

AsyncTask是Android帮我们封装好的异步处理工具,其本质上是线程池和Handler的封装。
而且不意外的是,线程池用来完成任务,Handler用来通信与更新UI。

2. AsyncTask的基本介绍

AsyncTask在其内部有几个特别重要的东西,首先是三个泛型参数,其次是四个内部方法。

2.1 泛型参数

  • Params:我们要执行异步任务之前需要传的参数。
  • Progress:在异步任务执行期间负责同步更新任务进度的参数。
  • Result:任务执行结束后所返回的结果。

这些参数一般定义这个类的时候就会用到,而且由于是泛型,我们得根据具体需求来决定使用哪些类。

public MyAsyncTask extends AsyncTask<Params, Progress, Result> {
	......
}

2.2 四个方法

  • onPreExecute():预先执行内容,在正式开始异步任务之前,如果有什么想要先完成的就写在这里,这个方法不能传参数。一般情况下做UI的初始化操作(比如弹出对话框之类的)。这个方法会在主线程中进行,可以更新UI。
  • protected Result doInBackground(Params… params):这个就是正式执行的异步任务。传入的参数由于是可变长参数,所以可以同时传多个参数。这是一个有返回值的方法,返回值的类型就是我们定义的Result类型。这个方法在子线程中进行,所以这个方法中不能更新UI
  • onProgressUpdate(Progress… progress):用于同步更新任务的方法,一般来说,如果在异步任务的半途中有什么要通知给主线程的内容,就会调用这个方法。这个方法在主线程中进行,可以更新UI。
  • onPostExecute(Result result):在异步任务完成之后,就会调用这个方法。传入的参数就是doInBackground方法中return的值。这个方法在主线程中进行,可以更新UI。

3. AsyncTask的基本用法

不做太过复杂的任务,就以数数从1数到100为例,写一个AsyncTask的例子。

  1. New一个新的Android项目,并且有一个EmptyActivity。

  2. 在activity_main.xml中设置一个进度条Progress Bar,一个TextView。

  3. 然后在Activity中绑定控件。

    TextView textView;
    ProgressBar progressBar;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text_view);
        progressBar = findViewById(R.id.progress_horizontal);
    
    }
    
  4. 为了方便区分AsyncTask三个参数的区别,对数字做一个简单封装。

    public class Num {
        int i;
    
        public Num(int i){
            this.i = i;
        }
    
        public void setI(int i) {
            this.i = i;
        }
    
        public int getI() {
            return i;
        }
    }
    
  5. 然后定义一个AsyncTask,Param是传入的参数填Num类,Progress取Integer,最后的Result取Boolean,写在MainActivity内部。

  6. 然后再加上四个方法。

    public class MainActivity extends AppCompatActivity {
    	.......
    	
    	class MyAsyncTask extends AsyncTask<Num, Integer, Boolean>{
    
            @Override
            protected void onPreExecute() {
                super.onPreExecute();
            }
    
            @Override
            protected void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);
            }
    
            @Override
            protected Boolean doInBackground(Num... nums) {
                return true;
            }
    
            @Override
            protected void onPostExecute(Boolean aBoolean) {
                super.onPostExecute(aBoolean);
            }
        }
    }
    
  7. onPreExecute()中做初始化的方法,这边就将textView和ProgressBar初始化一下,因为内部类可以访问外部类,所以可以直接调用。

    @Override
    protected void onPreExecute() {
        progressBar.setMax(100);
        progressBar.setProgress(0, true);
        textView.setText( "0 / 100");
    }
    
  8. 接下来在doInBackground完成具体数数的逻辑。

    @Override
    protected Boolean doInBackground(Num... nums) {
    	//做个简单的判断
        if (nums.length < 1)
                return false;
                
        while (nums[0].getI() < 100){
            nums[0].setI(nums[0].getI() + 1);
    
            //注意,这里要用publishProgress,不能直接调用onProgressUpdate
            //publishProgress后,就会执行onProgressUpdate,参数就是现在传入的参数
            publishProgress(nums[0].getI());
    
    		//每完成一次计数后稍微等一下,不然一下就结束了
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
    
  9. 然后在onProgressUpdate中更新进度。

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        if (values.length < 1)
            return;
    
        progressBar.setProgress(values[0], true);
        textView.setText(values[0] + " / 100");
    }
    
  10. 最后在onPostExecute中,用Toast对任务结果做个总结。

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        Toast.makeText(MainActivity.this, aBoolean? "complete" : "failed", Toast.LENGTH_SHORT).show();
        super.onPostExecute(aBoolean);
    }
    
  11. 最后在onCreate()中调用,为了方便就不做什么点击按钮后开始任务的东西了。然后为了防止内存泄漏,用上WeakPreference。

    TextView textView;
    ProgressBar progressBar;
    MyAsyncTask myAsyncTask;
    ReferenceQueue<MyAsyncTask> queue = new ReferenceQueue<>();
    WeakReference<MyAsyncTask> weakAsyncTask;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text_view);
        progressBar = findViewById(R.id.progress_horizontal);
    
        myAsyncTask = new MyAsyncTask();
        weakAsyncTask = new WeakReference<>(myAsyncTask, queue);
        myAsyncTask = null;
    
        weakAsyncTask.get().execute(new Num(0));
    
    }
    
  12. 外部想要调用AsyncTask的结果,需要使用get()这个方法。不过这个方法在出结果之前会造成堵塞。

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                //仔细观察第一个log和第三个log之间的时间差,就能体会到堵塞的概念了
                Log.i(TAG, "run: " + SystemClock.uptimeMillis());
                Log.i(TAG, "run: " + weakAsyncTask.get().get());
                Log.i(TAG, "run: " + SystemClock.uptimeMillis());
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
    

最终代码长这样(不包括Num类):

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    TextView textView;
    ProgressBar progressBar;
    MyAsyncTask myAsyncTask;
    ReferenceQueue<MyAsyncTask> queue = new ReferenceQueue<>();
    WeakReference<MyAsyncTask> weakAsyncTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text_view);
        progressBar = findViewById(R.id.progress_horizontal);

        myAsyncTask = new MyAsyncTask();
        weakAsyncTask = new WeakReference<>(myAsyncTask, queue);
        myAsyncTask = null;

        weakAsyncTask.get().execute(new Num(0));

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //仔细观察第一个log和第三个log之间的时间差,就能体会到堵塞的概念了
                    Log.i(TAG, "run: " + SystemClock.uptimeMillis());
                    Log.i(TAG, "run: " + weakAsyncTask.get().get());
                    Log.i(TAG, "run: " + SystemClock.uptimeMillis());
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }

    @SuppressLint("NewApi")
    class MyAsyncTask extends AsyncTask<Num, Integer, Boolean>{

        @Override
        protected void onPreExecute() {
            progressBar.setMax(100);
            progressBar.setProgress(0, true);
            textView.setText( "0 / 100");

        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            if (values.length < 1)
                return;

            progressBar.setProgress(values[0], true);
            textView.setText(values[0] + " / 100");
        }

        @Override
        protected Boolean doInBackground(Num... nums) {
            if (nums.length < 1)
                    return false;
            while (nums[0].getI() < 100){
                nums[0].setI(nums[0].getI() + 1);

                //注意,这里要用publishProgress,不能直接调用onProgressUpdate
                //publishProgress后,就会执行onProgressUpdate,参数就是现在传入的参数
                publishProgress(nums[0].getI());

                //每完成一次计数后稍微等一下,不然一下就结束了
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }

        @Override
        protected void onPostExecute(Boolean aBoolean) {
            Toast.makeText(MainActivity.this, aBoolean? "complete" : "failed", Toast.LENGTH_SHORT).show();
            super.onPostExecute(aBoolean);
        }
    }
}

4. 源码解析

有关AsyncTask中的Handler到底做了什么

AsyncTask中,对线程池和Handler进行了封装,我原本以为是那种,getHandler().sendMessage()这样的感觉,但是谁想并不是。
先看内部有关Handler的对象有哪些。

 /**
  * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
  *
  * @hide
  */
public abstract class AsyncTask<Params, Progress, Result> {

	private static InternalHandler sHandler;

	private final Handler mHandler;
}

有两个对象,现在不知道InternalHandler是啥,就先看mHandler这个类。
结果mHandler就两个调用,一个赋值一个return
结果mHandler就两个调用,一个赋值一个return。
赋值的这行在构造函数中,也就是说new AsyncTask的时候就会实例化。

public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
        ? getMainHandler()
        : new Handler(callbackLooper);

	......
}

简单分析一下,当callbackLooper不等于主线程的Looper的时候,就会new一个子线程的Handler。说是这样说,然而你看看AsyncTask的构造函数列表。

/**
  * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
  */
 public AsyncTask() {
     this((Looper) null);
 }

 /**
  * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
  *
  * @hide
  */
 public AsyncTask(@Nullable Handler handler) {
     this(handler != null ? handler.getLooper() : null);
 }

 /**
  * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
  *
  * @hide
  */
 public AsyncTask(@Nullable Looper callbackLooper) {
     ......
 }

三个构造函数,但是后面两个构造函数中有个@hide,这意味着你无法在正常的开发过程中用上它。也就是说,对于我们而言,只能用无参的构造函数(并且looper肯定是传入null)。
那么回到刚刚的分支条件,绝大部分情况就相当于mHandler = getMainHandler()了。

private static Handler getMainHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler(Looper.getMainLooper());
        }
        return sHandler;
    }
}

这里就是相当于mHandler = sHandler的一个过程,但是sHandler是一个InternalHandler,并不是普通的Handler,而是AsyncTask内部写的一个继承Handler的静态类。

private static class InternalHandler extends Handler {
    public InternalHandler(Looper looper) {
        super(looper);
    }

    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT:
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

InternalHandler内部已经实现了handleMessage的方法,并且其中就有一个我们很熟悉的onProgressUpdate。最后再看看Message是哪里发出的,追踪MESSAGE_POST_PROGRESS这个what值。

@WorkerThread
protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}

看上去是一行比较复杂的内容,我稍微用平时的语句翻译一下。

protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        Handler handler = getHandler(); //这个是返回mHandler
        Message message = handler.obtainMessage();
        message.what = MESSAGE_POST_PROGRESS;
        message.obj = new AsyncTaskResult<Progress>(this, values);
        handler.sendMessage(message);
    }
}

这个publishProgress方法就是我们在doInBackground中用的那个方法。那么也就是说publishProgress这个方法就是发送一个Message给Handler,Handler再来处理,调用onProgressUpdate方法。
隔壁的postResult也是相近内容。

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

得出结论:

  1. 在AsyncTask中,它内部的Handler是已经封装好功能和内容了,我们不用再次实现(也无法实现)。
  2. doInBackground方法中publishProgress和最后的retrun,其实本质都是发送Message给Handler,再由Handler处理。
  3. 根据2,可以知道onProgressUpdate方法在主线程执行的原因,是Handler中的handleMessage()方法的内容,而handler绝大部分情况是主线程的Handler。

有关get()方法造成堵塞的原因

这个get()方法是真的不能放在主线程,放进去就容易造成卡机,因为你不知道你的AsyncTask什么时候结束。

@UnsupportedAppUsage
private final FutureTask<Result> mFuture;

......

public final Result get() throws InterruptedException, ExecutionException {
    return mFuture.get();
}

观察一下,mFuture是FutureTask的类型,FutureTask简单来说就是一个有返回值(和Callable相关)的多线程任务,而它的返回值要等任务结束后才能返回。这么看来会堵塞也是正常的。

有关doInBackground为啥在子线程运行

这里就要先看我们平时是怎么开启任务的,我们开启任务一般是调用AsyncTask.execute(Param… params)这个方法。

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

......

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    if (mStatus != Status.PENDING) {
        switch (mStatus) {
            case RUNNING:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task is already running.");
            case FINISHED:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task has already been executed "
                        + "(a task can be executed only once)");
        }
    }

    mStatus = Status.RUNNING;

    onPreExecute();

    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}

也就是说,实际上是sDefaultExecutor这个参数,执行了execute()这个方法。当然这个sDefaultExecutor是一个Executors,一个多线程框架,我们平时的newCachedThreadPool()之类的也有用到这个框架。
这就是AsyncTask内部实现的线程池。

参考材料

第一行代码Android(第2版)
p347 - p349
你真的了解AsyncTask?- CSDN
https://blog.csdn.net/majihua817/article/details/51658465
Android面试系列文章2018之Android部分AsyncTask机制篇 - CSDN
https://blog.csdn.net/ClAndEllen/article/details/79346383

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值