多线程详解——AsyncTask工作原理(源码详解)

目录

一、Android中工作线程

二、AsyncTask的使用简介

1. API方法

2. 使用举例

串行

并行

3. AsyncTask生命周期问题

三、AsyncTask的缺陷和问题

四、 AsyncTask的工作原理

1. 构造方法

2. execute方法

3. executeOnExecutor方法

4. SerialExecutor类

5. THREAD_POOL_EXECUTOR线程池

6. InternalHandler对象


一、Android中工作线程

‌Android中的工作线程主要包括以下几种‌:

  1. Thread类‌:直接创建一个新的Thread对象,并在其run方法中执行后台任务。这种方式灵活但需要手动管理线程的生命周期‌12。

  2. AsyncTask‌:这是一个轻量级的异步任务类,适用于简单的后台任务。AsyncTask在Android 11(API 30)之后被标记为过时,建议使用更现代的异步处理机制‌1。

  3. HandlerThread‌:这是一个带有消息队列的线程,适用于需要处理消息传递的后台任务。HandlerThread可以与Handler配合使用,方便在子线程中处理消息并更新UI‌12。

  4. IntentService‌:这是一个服务类,内部使用HandlerThread来执行任务。IntentService会自动管理线程的生命周期,适合执行长时间运行的任务,且不会被系统轻易杀死‌13。

工作线程的特点和用途‌:

  • 非UI线程‌:工作线程不直接参与用户界面的更新和绘制,专门用于执行后台任务。
  • 后台任务‌:执行耗时的后台任务,避免阻塞主线程,保持应用流畅。
  • 并发执行‌:可以同时运行多个工作线程,提高应用的响应速度和性能‌

二、AsyncTask的使用简介

1. API方法

  AsyncTask是对Handler与线程池的封装。使用它的方便之处在于能够更新用户界面,当然这里更新用户界面的操作还是在主线程中完成的,但是由于AsyncTask内部包含一个Handler,所以可以发送消息给主线程让它更新UI。另外,AsyncTask内还包含了一个线程池。使用线程池的主要原因是避免不必要的创建及销毁线程的开销。设想下面这样一个场景:有100个只需要0.001ms就能执行完毕的任务,如果创建100个线程来执行这些任务,执行完任务的线程就进行销毁。那么创建与销毁进程的开销就很可能成为了影响性能的瓶颈。通过使用线程池,我们可以实现维护固定数量的线程,不管有多少任务,我们都始终让线程池中的线程轮番上阵,这样就避免了不必要的开销。‘

    在这里简单介绍下AsyncTask的使用方法,为后文对它的工作原理的研究做铺垫,关于AsyncTask的详细介绍大家可以参考官方文档或是相关博文。

    AsyncTask是一个抽象类,我们在使用时需要定义一个它的派生类并重写相关方法。AsyncTask类的声明如下:

public abstract class AsyncTask<Params, Progress, Result> 

    我们可以看到,AsyncTask是一个泛型类,它的三个类型参数的含义如下:

  • Params:doInBackground方法的参数类型;
  • Progress:AsyncTask所执行的后台任务的进度类型;
  • Result:后台任务的返回结果类型。

    我们再来看一下AsyncTask类主要为我们提供了哪些方法:

onPreExecute() //此方法会在后台任务执行前被调用,用于进行一些准备工作
doInBackground(Params... params) //此方法中定义要执行的后台任务,在这个方法中可以调用publishProgress来更新任务进度(publishProgress内部会调用onProgressUpdate方法)
onProgressUpdate(Progress... values) //由publishProgress内部调用,表示任务进度更新
onPostExecute(Result result) //后台任务执行完毕后,此方法会被调用,参数即为后台任务的返回结果
onCancelled() //此方法会在后台任务被取消时被调用

    以上方法中,除了 doInBackground 运行在子线程中,其他的都是运行在主线程的。

2. 使用举例

串行

当想要串行执行时,直接行execute()方法

并行

如果需要并行执行时,执行executeOnExecutor(Executor)。

package com.example.workthread

import android.app.Activity
import android.os.AsyncTask
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import java.util.concurrent.Executor
import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit


class AsyncTaskActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        var myAsyncTask = MyAsyncTask()
        //默认使用线程池THREAD_POOL_EXECUTOR
        //myAsyncTask.execute()
        //创建自定义线程池
        val executor: Executor = ThreadPoolExecutor(
            10, 50, 10,
            TimeUnit.SECONDS, LinkedBlockingDeque<Runnable>(100)
        )
        myAsyncTask.executeOnExecutor(executor)
    }

    inner class MyAsyncTask : AsyncTask<String, Int, Long>() {

        override fun onPostExecute(result: Long?) {
            super.onPostExecute(result)
            //运行在主线程
            Log.d("tag", "result:" + result)
            Log.d("tag", "onPostExecute#thread: ${Thread.currentThread()}")
            this@AsyncTaskActivity.findViewById<TextView>(R.id.showtext).text = result.toString()
        }

        override fun doInBackground(vararg params: String?): Long {
            //运行在子线程
            Log.d("tag", "doInBackground#thread: ${Thread.currentThread()}")
            return 1000
        }
    }
}

3. AsyncTask生命周期问题

很多开发者会认为一个在Activity中创建的AsyncTask随着Activity的销毁而销毁。然而事实并非如此。AsynTask会一直执行,直到doInBackground()方法执行完毕,然后,如果cancel(boolean)被调用,那么onCancelled(Result result)方法会被执行;否则,执行onPostExecuteResult result)方法。如果我们的Activity销毁之前,没有取消AsyncTask,这有可能让我们的应用崩溃(crash)。因为它想要处理的view已经不存在了。所以,我们是必须确保在销毁活动之前取消任务。总之,我们使用AsyncTask需要确保AsyncTask正确的取消。

三、AsyncTask的缺陷和问题

    AsyncTask的优点在于执行完后台任务后可以很方便的更新UI,然而使用它存在着诸多的限制先抛开内存泄漏问题,使用AsyncTask主要存在以下局限性:

  • 在Android 4.1版本之前,AsyncTask类必须在主线程中加载,这意味着对AsyncTask类的第一次访问必须发生在主线程中;在Android 4.1以及以上版本则不存在这一限制,因为ActivityThread(代表了主线程)的main方法中会自动加载AsyncTask
  • AsyncTask对象必须在主线程中创建
  • AsyncTask对象的execute方法必须在主线程中调用
  • 一个AsyncTask对象只能调用一次execute方法
  • AsyncTask内存泄漏问题

    如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将续在内存里保留这个引用,导致Activity无法被回收,引起内存泄漏。

  • AsyncTask结果丢失问题

屏幕旋转或Activity在后台被系统杀掉等情况会导致Actvity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

 

接下来,我们从源码的角度去探究一下AsyncTask的工作原理,并尝试着搞清楚为什么会存在以上局限性。

四、 AsyncTask的工作原理

1. 构造方法

   首先,让我们来看一下AsyncTask类的构造器都做了些什么:

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

 在第2行到第12行,初始化了mWorker,它是一个派生自WorkRunnable类的对象。WorkRunnable是一个抽象类,它实现了Callable<Result>接口。我们再来看一下第4行开始的call方法的定义,首先将mTaskInvoked设为true表示当前任务已被调用过,然后在第6行设置线程的优先级。在第8行我们可以看到,调用了AsyncTask对象的doInBackground方法开始执行我们所定义的后台任务,并获取返回结果存入result中。最后将任务返回结果传递给postResult方法。关于postResult方法我们会在下文进行分析。由此我们可以知道,实际上AsyncTask的成员mWorker包含了AyncTask最终要执行的任务(即mWorker的call方法)。

    接下来让我们看看对mFuture的初始化。我们可以看到mFuture是一个FutureTask的直接子类(匿名内部类)的对象,在FutureTask的构造方法中我们传入了mWorker作为参数。我们使用的是FutureTask的这个构造方法:

public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

  也就是说,mFuture是一个封装了我们的后台任务的FutureTask对象,FutureTask类实现了FutureRunnable接口,通过这个接口可以方便的取消后台任务以及获取后台任务的执行结果。

    从上面的分析我们知道了,当mWorker中定义的call方法被执行时,doInBackground就会开始执行,我们定义的后台任务也就真正开始了。那么这个call方法什么时候会被调用呢?我们可以看到经过层层封装,实际上是mFuture对象封装了call方法,当mFuture对象被提交到AsyncTask包含的线程池执行时,call方法就会被调用,我们定义的后台任务也就开始执行了。下面我们来看一下mFuture是什么时候被提交到线程池执行的。

2. execute方法

 首先来看一下execute方法的源码:

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

  我们可以看到它接收的参数是Params类型的参数,这个参数会一路传递到doInBackground方法中。execute方法仅仅是调用了executeOnExecutor方法,并将executeOnExecutor方法的返回值作为自己的返回值。我们注意到,传入了sDefaultExecutor作为executeOnExecutor方法的参数,那么sDefaultExecutor是什么呢?简单的说,它是AsyncTask的默认执行器(线程池)。AsyncTask可以以串行(一个接一个的执行)或并行(一并执行)两种方式来执行后台任务,在Android3.0及以后的版本中,默认的执行方式是串行。这个sDefaultExecutor就代表了默认的串行执行器(线程池)。也就是说我们平常在AsyncTask对象上调用execute方法,使用的是串行方式来执行后台任务。

3. executeOnExecutor方法

    我们再来看一下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;
    }

  从以上代码的第4行到第12行我们可以知道,当AsyncTask对象的当前状态为RUNNING或FINISHED时,调用execute方法会抛出异常,这意味着不能对正在执行任务的AsyncTask对象或是已经执行完任务的AsyncTask对象调用execute方法,这也就解释了我们上面提到的局限中的最后一条。

    接着我们看到第17行存在一个对onPreExecute方法的调用,这表示了在执行后台任务前确实会调用onPreExecute方法。

    在第19行,把我们传入的execute方法的params参数赋值给了mWorker的mParams成员变量;而后在第20行调用了exec的execute方法,并传入了mFuture作为参数。exec就是我们传进来的sDefaultExecutor。那么接下来我们看看sDefaultExecutor究竟是什么。在AsyncTask类的源码中,我们可以看到这句:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

     sDefaultExecutor被赋值为SERIAL_EXECUTOR,那么我们来看一下SERIAL_EXECUTOR:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

4. SerialExecutor类

    现在,我们知道了实际上sDefaultExecutor是一个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);
            }
        }
    }

我们来看一下execute方法的实现。mTasks代表了SerialExecutor这个串行线程池的任务缓存队列,在第6行,我们用offer方法向任务缓存队列中添加一个任务,任务的内容如第7行到第13行的run方法定义所示。我们可以看到,run方法中:第9行调用了mFuture(第5行的参数r就是我们传入的mFuture)的run方法,而mFuture的run方法内部会调用mWorker的call方法,然后就会调用doInBackground方法,我们的后台任务也就开始执行了。那么我们提交到任务缓存队列中的任务什么时候会被执行呢?我们接着往下看。

     首先我们看到第三行定义了一个Runnable变量mActive,它代表了当前正在执行的AsyncTask对象。第15行判断mActive是否为null,若为null,就调用scheduleNext方法。如第20行到24行所示,在scheduleNext方法中,若缓存队列非空,则调用THREAD_POOL_EXECUTOR.execute方法执行从缓存队列中取出的任务,这时我们的后台任务便开始你真正执行了。

5. THREAD_POOL_EXECUTOR线程池

     通过以上的分析,我们可以知道SerialExecutor所完成的工作主要是把任务加到任务缓存队列中,而真正执行任务的是THREAD_POOL_EXECUTOR。我们来看下THREAD_POOL_EXECUTOR是什么:

 public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

    从上面的代码我们可以知道,它是一个线程池对象。根据AsyncTask的源码,我们可以获取它的各项参数如下:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};

private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

 由以上代码我们可以知道:

  •  corePoolSize为CPU数加一;
  • maximumPoolSize为CPU数的二倍加一;
  • 存活时间为1秒;
  • 任务缓存队列为LinkedBlockingQueue。

6. InternalHandler对象

现在,我们已经了解到了从我们调用AsyncTask对象的execute方法开始知道后台任务执行完都发生了什么。现在让我们回过头来看一看之前提到的postResult方法的源码:

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

 在以上源码中,先调用了getHandler方法获取AsyncTask对象内部包含的sHandler,然后通过它发送了一个MESSAGE_POST_RESULT消息。我们来看看sHandler的相关代码:

private static final InternalHandler sHandler = new InternalHandler();

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

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

  从以上代码中我们可以看到,sHandler是一个静态的Handler对象。我们知道创建Handler对象时需要当前线程的Looper,所以我们为了以后能够通过sHandler将执行环境从后台线程切换到主线程(即在主线程中执行handleMessage方法),我们必须使用主线程的Looper,因此必须在主线程中创建sHandler。这也就解释了为什么必须在主线程中加载AsyncTask类,是为了完成sHandler这个静态成员的初始化工作。

     在以上代码第10行开始的handleMessage方法中,我们可以看到,当sHandler收到MESSAGE_POST_RESULT方法后,会调用finish方法,finish方法的源码如下:

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

  在第2行,会通过调用isCancelled方法判断AsyncTask任务是否被取消,若取消了则调用onCancelled方法,否则调用onPostExecute方法;在第7行,把mStatus设为FINISHED,表示当前AsyncTask对象已经执行完毕。

    经过了以上的分析,我们大概了解了AsyncTask的内部运行逻辑,知道了它默认使用串行方式执行任务。那么如何让它以并行的方式执行任务呢? 阅读了以上的代码后,我们不难得到结论,只需调用executeOnExecutor方法,并传入THREAD_POOL_EXECUTOR作为其线程池即可。

推荐文章

源码分析AsyncTask的工作原理 - 简书

[转]深入理解AsyncTask的工作原理 - Zjc0514 - 博客园

Android AsyncTask(1)-使用方法和线程池解析 - 简书

https://segmentfault.com/a/1190000016853779?utm_source=tag-newest

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

闲暇部落

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

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

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

打赏作者

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

抵扣说明:

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

余额充值