谈谈关于 AsyncTask 、HandlerThread 和 IntentService 的源码

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40697071/article/details/88714469

一、开篇语

因为最近在进行 Android 方面的一些知识点的整理,发现有一些比较细节的问题都是以前没有注意到的,而且之前对于 AsyncTask、HandlerThread 和 IntentService 的理解也不是很透彻,所以正好借着这次机会进行一下整理,捋清思路,增强自己对它们的更深一层的理解。

这篇博文的话,我们会主要从这三个类的概念、使用的方式、源码和一些需要注意细节进行讲解,因为可能大家对它们也不是很陌生了,所以可能博文中对于源码的分析,细节点的分析会更多一些,对于使用的方式,其实网上的文章就已经很多了。

原创文章不易,请尊重劳动成果和知识产权,谢谢。 

 

二、基本介绍

1)AsyncTask

AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. 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 package such as ExecutorThreadPoolExecutor and FutureTask.

An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread. An asynchronous task is defined by 3 generic types, called ParamsProgress and Result, and 4 steps, called onPreExecutedoInBackgroundonProgressUpdate and onPostExecute.

上面就是官方文档中,对于 AsyncTask 类的一个描述,大概的意思就是说 AsyncTask 能够让你在不需要操作线程和 handler 的基础上更方便的在后台的工作线程中来执行耗时任务并在 UI 线程中进行 UI 的更新操作,需要注意的就是 AsyncTask 是一个抽象类。

对于这样的叙述应该大家都不会感到陌生,因为对于 Android 来说,存在这样的问题就是当我们在主线程(UI线程)中执行任务的时候,是不应该执行耗时型任务的,因为耗时型的任务可能会导致我们的程序出现 ANR 的问题,所以,我们通常的做法是需要来创建一个新的工作线程,然后将耗时的任务放到工作线程中去执行,但是这样的操作的时候新的问题又出现了,就是 Android 又不允许我们在非 UI线程中去访问 UI,也就是说我们在工作线程中是没有办法去操作 UI的,但是我们正常的项目中当执行一个耗时任务,比如下载任务的时候,开始前提示开始下载,下载中实时更新下载的进度,下载完成后提示下载已经完成,在这个过程中操作 UI是必要的,那我们怎么处理这种情况呢,首选的方式肯定是使用 handler ,更准确的说也就是 Android 的线程间通信机制(这里就不展开讲了,有兴趣的自己去查相关的资料就可以了),也就是当我们需要更改 UI时,将信息通过 handler 来传递给主线程,在主线程中再来进行 UI的更新操作。

这样看来,其实整个的一个流程还是比较麻烦的,而且这样做带来的还有一个比较大的问题就是关于线程的创建和销毁,我们都知道线程其实也是一种系统资源,所以当我们每执行一个后台任务就创建一个线程,使用完了马上再进行销毁,这样的方式其实是很消耗资源。所以这个时候,我们就可以使用 AsyncTask 来完成这个工作,对于 AsyncTask 来说,其内部维护了两个线程池,一个是 serialExecutor ,另一个是 THREAD_POOL_EXECUTOR,对于这两个线程池的具体作用我们放到下面的源码分析中再来细讲,在这里我们只需要通过使用 AsyncTask 来执行异步的后台任务的实质就是通过线程池来实现的,这样的作法可以有效的避免线程不断的创建和销毁时带来的系统开销。

2)HandlerThread

Thread that has a Looper. The Looper can then be used to create Handlers.

Note that just like with a regular ThreadThread.start() must still be called.

接下来我们来看 HandlerThread,官方文档对于这个类的描述就比较简洁了,就是一个带有 Looper 的 Thread,这是因为对于 HandlerThread 其实就是继承自 Thread 的一个线程,只不过在这个类中默认帮我们对 Looper 的创建和对消息的一些处理都进行了相关的封装,可以用来执行多个耗时操作,而不需要多次开启线程,对于使用者来说只需要创建一个 HandlerTread 的实例类,并传入一个自定义的 Handler 与 HandlerTread 中的 Looper 进行绑定就可以了,对于具体的工作线程的逻辑我们直接写到自定义的 Handler 的 handleMessage 方法中就可以了。通过使用 HandlerTread 我们同样也可以避免对线程的反复创建和销毁,因为对于 HandlerThread 来说,它会帮我们维护着一条工作线程来执行后台的耗时任务。

3)IntentService

IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through android.content.Context#startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

This "work queue processor" pattern is commonly used to offload tasks from an application's main thread. The IntentService class exists to simplify this pattern and take care of the mechanics. To use it, extend IntentService and implement onHandleIntent(android.content.Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.

All requests are handled on a single worker thread -- they may take as long as necessary (and will not block the application's main loop), but only one request will be processed at a time.

根据文档,我们可以明确的一点就是,这个类的本质是一个 Service,也就是说它是继承自 Service 的,这也就注定它同其它的后台线程相比,具有更高的优先级,保活性会更强。同时根据文档我们还能得到一个信息是,他能够按顺序的处理多个耗时任务,并且能够在任务执行完成后自动进行线程的销毁。最后即它虽然能够处理多个任务,但都是通过同一个工作线程来完成的,就算我们连续插入多个任务,也不会进行多个工作线程的创建,这是不是很像上面的 HandlerThread 呢。

而对于它的使用就更加简单了,只需要让我们自定义的类继承 IntentService ,并实现它的 onHandleIntent 方法,在 onHandleIntent 方法中执行耗时操作即可。

 

三、源码解析

1)AsyncTask

public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return 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);
                }
            }
        };
    }

首先登场的肯定是 AsyncTask 的构造方法,我们可以对着代码来分析一下在它的这个构造方法中都做了哪些事。首先我们可以看到,它首先创建了 WorkerRunnable 实例对象,并实现了它的 call 方法,根据这个对象的名字我们也可以猜出来其大概的作用,没错他就是那个耗时操作的工作任务(注意这里严格来说,不是工作线程,Runnable 严格来说只是线程中的一个任务),同时我们可以看到,在 AsyncTask 执行过程中真正执行耗时操作的方法 doInBackground 也是在这个方法中出现的,在 doInBackground 方法的下面还有一个比较重要的方法,就是无论如何最后都会执行的 postResult ,根据方法名我们大概可以猜到,它的作用就是传递最后的结果。对于 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;
    }

可以看到在这个方法中,首先将 doInBackground 执行后的结果进行封装,然后是通过 Handler 来进行了一次线程间的消息发送,所以我们接下来去看看在这个 Handler 中做了什么。

 

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

首先我们能够看到这个 InternalHandler 是一个静态类,并且它是需要做 UI 更新工作的,所以它必须存在于主线程中,这也就说明了为什么 AsyncTask 必须实例在主线程中,execute 在主线程中。在这个 Handler 中首先是对消息类型进行了一次判断,如果消息的类型是 MESSAGE_POST_RESULT 则调用 finish 方法,如果消息类型是 MESSAGE_POST_PROGRESS 则直接调用 onProgressUpdate 方法,对于这个 onProgressUpdate 方法其会在 doInBackground 方法中调用 publishProgress 的时候会进行调用,主要的作用就是进行切回主线程进行 UI 的更新(因为 doInBackground 是在工作线程运行的,是没有办法访问 UI 的)。那么 finish 方法里又做了什么呢,我们接着跟进去。

 

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

finish 方法比较简单,主要做的工作是判断当前任务有没有被取消,为什么会在这里进行判断呢,因为我们知道 AsyncTask 中任务的取消机制是和线程中的中断机制是一致的,即没有办法在外界直接对其进行中断取消,一般的做法就是通过 intercept 和 cancel 方法来对其进行标记,然后在 AsyncTask 或者 线程的内部去进行判断,假如是发现被标记了,那么直接中断或取消。所以这个当判断发现假如被进行了取消标记,那么直接调用 onCancelled 方法来进行后续处理,否则的话默认就是任务已经执行完成,调用 onPostExecute 来执行任务结束后的操作,比如更新 UI什么的,注意这个 onPostExecute 也是在主线程中进行的。到这里我们基本已经说完了 doInBackground 后面的操作,然后让我们回过头来看它前面的操作。

 

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

我们知道当创建好了 AsyncTask 实例的时候,我们需要通过调用 execute 来启动线程执行任务,所以我们从这里跟入。首先发现这个 execute 方法只是简单的调用了 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;
    }

首先我们可以看到进入这个方法,前面的代码都是一些相关的判断和变量赋值等,比较重要的是一个 onPreExecute 方法,这个方法也是我们需要在子类中进行实现的,其主要的作用就是来处理一些工作线程开始工作之前的逻辑,并且其运行在 UI线程,也就是说它可以对 UI 进行一些修改,显示一些页面提示等。接下来我们发现它又调用了 exec.execute 方法,而这个 exec 类其实跟到最后就是那个我们上面所说过的 SerialExecutor 线程池,所以我们直接看它的这个 execute 方法。

 

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 进行了同步操作,那这么做的意义是什么呢?没错,就是用来保证任务的串行,即任务是依次串行执行的,每次只会执行一个任务。在 execute 方法中它做了两件事,首先是将我们在构造方法中创建的新的工作线程的任务塞到队列里面,同时调用 scheduleNext 方法中的 THREAD_POOL_EXECUTOR.execute 来对队头取出的任务进行执行,那么我们接着跟下去。

 

 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;

到这里,我们明白了 THREAD_POOL_THREAD 才是那个真正执行任务的线程池,它维护着几条工作线程和核心线程,然后将从队列中取出的队伍来进行执行,至于怎么执行就回到了 AsyncTask 构造方法中我们创建的那个 WorkerRunnable 实例中的 call 方法了,接下来就会调用 doInBackground 来对耗时任务进行执行了。

启示:

1. 首先对于 AsyncTask 的方法执行流程是:

onPreExecute -> doInBackground -> publishProgress -> onProgressUpdate -> onPostProgress

其中除了 doInBackground 其余的方法都是运行在主线程中的。

2. 每个 AsyncTask 都具有一个 SerialExecutor 用来维护任务的串行执行,一个 THREAD_POOL_EXECUTOR 来执行耗时任务,还有一个位于主线程的 InternalHandler 来进行任务执行中和任务执行结束后的 UI更新操作。

3. AsyncTask 的实例化必须在主线程中进行,其 execute 方法的调用必须在主线程中调用。

4. 不要手动调用 doInBackground 这一系列的方法。

 

2)HandlerThread

public HandlerThread(String name) {
    super(name);
    mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

public HandlerThread(String name, int priority) {
    super(name);
    mPriority = priority;
}

老样子首先我们来看它的构造方法,这个构造方法比较简单,就是设置一下线程的名称和优先级。

 

public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

接下来我们来看它比较重要的一个 run 方法的实现,我们都知道 HandlerThread 是继承自 Thread 的,也就说它的本质是是一个线程,所以只要当我们调用了 start 方法后,这个线程就开始执行其 run 方法中的代码。在 run 方法中,它首先调用了 Looper.prepare 方法来初始化线程 Looper,读过 Looper 源码的应该知道这个 prepare 方法主要做的事就是首先检查当前线程的内存(TLS)中是否已经存在了 Looper,如果存在的话就直接报错,没有的话就创建一个新的线程 Looper,然后我们发现它居然进行了 notifyAll 方法,那么它要唤醒的线程到底是谁呢?这个的话我们先留个疑问,接着往下看。下面的 onLooperPrepared 方法就是留给用户进行扩展的,没什么好说的,然后就是 Looper.loop 方法的调用,在 Looper 源码中指示这段代码调用的时候,looper 就会开启循环,来不断的从消息队列中读取消息并进行分发了。

 

public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}

之后就是 HandlerThread 中另一个比较重要的方法了,getLooper 作用就是获取工作线程的 looper,同时我们发现,在这个方法中存在着一个 wait 方法来阻塞 looper 的获取,那么其实我们也就明白了,上面那个 notifyAll 就是对应于这个 wait 的,他俩的作用就是防止当工作线程的 looper 还没初始化好的时候,主线程就开始对其进行获取了,这样的话就会导致程序发生错误。所以这里使用了线程等待,只有当确认 looper 初始化好了之后,才允许外界对其进行获取和调用。

 

public boolean quit() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quit();
        return true;
    }
    return false;
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public boolean quitSafely() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quitSafely();
        return true;
    }
    return false;
}

最后就是关于两个退出线程的方法,一个是正常退出,另外一个是安全退出,但其实我们跟踪源码的时候会发现,最后调用的都是 MessageQueue.quit 方法来进行退出。

 

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

然后在这个方法中又会根据是否安全退出,来执行两个不同的方法。

 

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

不安全的时候遍历队列,删除所有的信息。

 

private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

安全退出时会多一个进行判断当前是否有消息正在被处理,如果有的话进行等待其完成后再退出。

 

3)IntentService

@Override
public void onCreate() {
    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

因为 IntentService 是基于 HandlerThread 实现的,所以源码中的逻辑会比较简单,首先是 onCreate 方法中,就是创建了一个 HandlerThread ,然后通过调用它的 start 方法来开启线程 looper,之后创建一个工作线程的 Handler,最后将刚刚创建的工作线程 Handler 直接与 HandlerThread 中的 Looper 进行绑定就可以了。

 

private final class ServiceHandler extends Handler {

    public ServiceHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent) msg.obj);
        stopSelf(msg.arg1);
    }
}

可以看到,工作线程 Handler 的 handleMessage 的执行逻辑是直接通过 onHandleIntent 去转到外面让用户实现了,所以这个类中的 onHandleIntent 是一个抽象方法。同时我们还注意到,它通过调用 stopSelf 来自动停止。

 

public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

public void onStart(Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

@Override
public void onDestroy() {
    mServiceLooper.quit();
}

接下来的一些代码就比较简单了,当我们每次调用 startService 的时候,都会调用 onStartCommand,然后在 onStartCommand 中有会调用 onStart 将传过来的 intent 进行包装成消息后发送给工作线程。最后的话就是在退出的时候清空消息队列就可以了。

启示:

1. 工作线程中的任务执行是顺序执行的,因为 looper 只会在 onCreate 中创建一次,因此消息队列也只有会有一个,这样的话当我们调用 startService 并将任务传进来的时候,其实调用 onStartCommand 也只是将任务消息塞到同一个消息队列中,

2. 如果服务停止的话,那么消息队列中的消息会被清空,后面的任务都不会执行。

3. 在 onStartCommand 中将消息是依次插入到消息队列中,然后处理时也是逐个依次调用 onHandleIntent 来对任务消息进行处理。

 

四、心得体会

这篇博文到这里就结束了,虽然内容不是很多,但也是自己经过一段时间的总结才得到的,通过不断地学习吧,感觉体会还是挺多的,其中最重要的一点就是对于很多事我们要不光要知其然更要知其所以然,应该还是要具有一种不断刨根问底的这样的一种精神的,对于任何技术,都要有一种追其根本的精神,一种不断学习不断研究的过程。通过源码的阅读,不仅会增加我们对其使用的熟练度,更重要的是当我们再使用这个技术的时候,能够知道其背后的道理,更好的去理解这个技术,所以我也是建议大家对于技术不要只是限于会用就可以了,更好的还是要通过去阅读源码,去思考这背后的一种思想,这样才能使自己得到更大的提升,收获到除这一项技术以外更重要的东西。

如内容存在错误请及时联系我,我一定马上改正,谢谢。

没有更多推荐了,返回首页