AsyncTask的基本用法和解析

AsyncTask简介

我们都知道ui线程用来处理界面交互逻辑,响应用户的操作,响应速度要快,不然的话就会给用户一种卡顿的感觉,假如在ui线程做一些耗时的操作,就有可能导致ANR给用户不好体验,那么这些耗时的操作(I/O操作,访问网络等)就需要放在子线程中进行,我之前都是用Handler+Thread的方式来处理耗时操作,今天来介绍一下AsyncTask的用法。

AsyncTask是android系统提供的轻量级的异步任务类,它可以在后台线程中执行任务并且将结果更新在ui线程中,它提供了ParamsProgressResult三个泛型参数以及onPreExecutedoInBackgroundonProgressUpdateonPostExecute四个核心方法。

ParamsProgressResult
doInBackground接受的参数类型执行的进度类型传递给任务处理完毕返回结果类型

方法描述线程
onPreExecute在执行异步任务之前调用,主要进行一些初始化工作,例如显示一个进度条。主线程
doInBackground接收Params参数,执行异步任务,运行在后台线程中,同时可以调用publishProgress来更新进度,执行的结果将返回给onPostExecute子线程
onProgressUpdate当publishProgress被调用时执行,可以用来更新ProgressBar主线程
onPostExecute异步任务结束后执行,取得doInBackground返回的result,更新ui主线程

注意:
当task调用cancel(boolean)方法来取消任务的时候,task当中的isCanelled方法会返回true,之后onCancelled方法会取代onPostExecute方法被调用,为了确保一个task尽快被取消,你需要定期在doInBackground方法中检查isCancelled方法的返回值。

AsyncTask使用方式

AsyncTask是一个抽象类,因为我们需要自己写一个实现类来继承AsyncTask。
官方示例:
模拟文件的下载

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) 
                break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

Task创建完成后,进行调用:

new DownloadFilesTask().execute(url1, url2, url3);

AsyncTask的使用限制

  1. AsyncTask必须在UI线程中加载,在android4.1版本之后自动完成了这个过程
  2. AsyncTask必须在UI线程中创建
  3. execute方法必须在UI线程中调用
  4. 不要手动调用onPreExecutedoInBackgroundonProgressUpdateonPostExecute四个核心方法
  5. 一个Task对象只能执行一次,不然会报异常
  6. 1.6版本之前AsyncTask是单一线程执行的,1.6之后改成线程池并行执行,3.0之后再次改成串行为了避免并发执行而导致的一些问题,不过我们可以通过

AsyncTask工作原理

我们选择AsyncTask的构造函数作为入口进行分析:
AsyncTask的构造函数:

 /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

在构造函数中初始化了mWorker和mFuture两个对象,没有进行其他的操作。接下来我们来看AsyncTask的执行方法AsyncTask.execute方法:

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

紧接着返回了executeOnExecutor方法:

 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;
    }

在这里我们会对task的状态进行判断,如果task正在运行或者已经运行过了,那么就会抛出异常,之前我们也有说过,AsyncTask只能运行一次。如果task没有运行,会将状态标记为RUNNING,并且调用onPreExecute()方法,可见onPreExecute确实是第一个执行,上面提到我们要在主线程中执行execute方法不然,onPreExecute()方法就无法在主线程中调用。

我们看exec.execute(mFuture)这行代码,exec其实就是sDefaultExecutor

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
-----------------------
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

这是一个Executor对象,我们会使用SerialExecutor实现,我们来看SerialExecutor的代码:

 private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }
        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

SerialExecutor当中维护了一个Runnable队列,并且execute还接收一个Runnable参数,这个Runnable参数由exec.execute(mFuture)方法可知是一个FutureTask对象。从SerialExecutor的实现可以知道,系统会把AsyncTask的Params参数传递给mWorker对象并封装到FutureTask对象中,接着由SerialExecutor的execute方法来处理这个FutureTask对象,将它插入到mTasks队列当中,假如目前没有活动的AsyncTask,那么就会调用SerialExecutor的scheduleNext方法来获取任务队列的队首元素(一个Runnable对象,由FutureTask充当)进行执行。并且当一个task执行后,继续执行下一个任务,一直到执行完所有任务,由此可见AsyncTask默认是串行执行的(源码版本:23)。

AsyncTask当中有两个线程池和一个Handler:

SerialExecutorfuze 负责任务排队,将任务添加到队列中
THREAD_POOL_EXECUTOR 负责执行任务
InternalHandler 负责将运行环境切换到主线程

上述SerialExecutor代码中,THREAD_POOL_EXECUTOR执行了mActive–>r.run也就是FutureTask对象的run方法被执行,我们来看这个run方法:

 public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

这里会调用callable的call方法,也就是WorkerRunnable对象实现的call方法,我们来看call方法的实现:

mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

也就是我们在初始化AsyncTask对象的时候,初始化的WorkerRunnable对象,在这里看见可以见doInBackground方法被调用,并且在返回语句调用了postResult方法:

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

根绝postResult的代码可见,这回到了我们熟悉的Handler模式,通过Handler发送了一个MESSAGE_POST_RESULT标识的消息。private static InternalHandler sHandler;这个Handler是一个静态对象,为了能够将运行环境切换到主线程要求Handler必须在主线程中创建,也就是这个AsyncTask必须在主线程中创建,另外静态对象在类加载的时候进行初始化也就说明了AsyncTask是在主线程进行初始化。

 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;
            }
        }

Handler接收到MESSAGE_POST_RESULT这个消息后会执行finish方法:

private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

由此可见,如果AsyncTask被取消了就执行onCancelled方法否则就会执行onPostExecute方法,AsyncTask的工作流程大体如此。被泛型搞的有点头晕脑胀的,之前我们说的一些规则限制也都在分析过程中一一解释了。通过源码的分析其实可以看见AsyncTask的底层也是采用了线程池+Handler来实现的。

对于AsyncTask的一些困惑

AsyncTask源码中有说:AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent pacakge such as Executor, ThreadPoolExecutor and FutureTask.我不太明白为什么它只能够执行一些短时间的操作,stackoverflow当中有一个很好的回答
AsyncTasks should ideally be used for short operations,答案中提到了两点

  1. AsyncTask没有和Activity的生命周期绑定在一起:当屏幕发生旋转的时候,activity被销毁,一个新的activity实例被创建,但是task仍然在执行,task执行完毕后没办法在新的activity实例中更新ui,这会导致java.lang.IllegalArgumentException异常
  2. 内存泄漏的问题:AsyncTask是以内部类的形式存在的,它会持有外部activity的引用,如果进行耗时操作,activity被销毁的时候,task依然运行这会导致系统无法回收activity占用的内存从而导致内存泄漏
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值