本篇博文涉及到的一些Android中常用的线程相关知识,源码也比较简单,作为重温笔记。
Android中的线程、线程池,,如下图所示:
AsyncTask
上述方法的执行顺序是:1 -> 2 -> 3 -> 4
对于AsyncTask有如下几点可以从源码中得到验证
- AsyncTask的对象必须在主线程中创建(非必须,后面会讲)
- execute方法必须在UI线程调用(非必须,后面会讲)
- 不要在程序中直接调用onPreExecute、onPostExecute、doInBackground和onProgressUpdate方法
- 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常
- AsyncTask默认是串行执行任务的,但是我们仍然可以通过AsyncTask的executeOnExecutor方法来并行地执行任务
- AsyncTask 调用 cancel() 任务是否立即停止执行?onPostExecute() 还会被调用吗?onCancelled() 什么时候被调用?
AsyncTask的一般用法如下:
// 除了doInBackground是必须重写的方法,其他的都是可选的实现方法
public class MyAsyncTask extends AsyncTask<String, Integer, Integer> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Integer doInBackground(String... strings) {
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
}
@Override
protected void onCancelled(Integer integer) {
super.onCancelled(integer);
}
}
===============================================================
MyAsyncTask myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();// 同步执行
myAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");// 并发执行
myAsyncTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, "");// 同步执行
源码解读:
如下两点是非必须的:
- AsyncTask的对象必须在主线程中创建
- execute方法必须在UI线程调用
之前看过一些网上的资料还有一些书籍,都说如上的1,2条。但是并未详细的解释为什么要这样。阅读了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 // 1
*/
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 // 2
*/
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
......
}
如上源码中的1和2处,@hide意味着这两个构造方法是系统使用的而不是给开发者准备的,开发者只能使用第一个无参的构造方法。无参的构造方法会调用上述源码最后一个构造方法,保证了我们可以拿到主线程的Looper。
我们知道UI线程的Looper是在ActivityThread的main方法中创建的
------ ActivityThread.java
public static void main(String[] args) {
......
Looper.prepareMainLooper();
......
Looper.loop();
.....
}
而Looper的prepareMainLooper方法是单例模式的
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
/**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
所以,即便我们在非UI线程创建AsyncTask对象,AsyncTask依然具备切换到UI线程进行UI更新的操作的能力。所以强制要求第一点(AsyncTask的对象必须在主线程中创建)是错误的。
下面看下第二点(execute方法必须在UI线程调用)为什么也是非必须的?
上面说了,无论在UI还是工作线程,AsyncTask都具备切换到UI线程进行UI更新的能力,这可以保证onPostExecute方法一定是运行的UI线程中的(执行onPostExecute,此时已经切换到了UI线程),
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]);// 执行onPostExecute方法
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
但是onPreExecute是没有这种保证的,因为此时在工作者线程调用execute方法的话,onPreExecute是运行在非UI线程的。
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
.......
onPreExecute();// 在哪个线程调用execute方法,则运行在哪个线程中
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
调用AsyncTask的execute方法,会执行上述代码。当然,如果在onPreExecute方法中不做更新UI的操作或者通过其他方法比如runOnUiThread等方法切换到UI线程进行UI的更新也就不会产生问题了。
既然这样,那为啥AsyncTask的源码注释要求我们要在UI线程创建AsyncTask对象以及执行execute方法呢?
为了版本的兼容。新版代码虽然没这个问题了,但我们写代码时无法预知用户手机的版本,为兼容旧版本,故新版源码构造方法及其他相关方法的注释仍写着必须在UI线程创建。
- 不要在程序中直接调用onPreExecute、onPostExecute、doInBackground和onProgressUpdate方法
- 这个很容易理解,他们都是AsyncTask内部的protected方法,各个方法之间有先后的执行顺序的,强制手动调用这些方法可能会出现一些问题
- 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常
@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) {
// 1
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;
}
调用AsyncTask的execute方法,会执行executeOnExecutor方法,上述1处的源码我们可以发现,每个AsyncTask对象都是有状态的,有如下两个结论:
- 处于运行状态的AsyncTask不能再次执行
- 一个AsyncTask只能被执行一次
这也就解释了本条要得出的结论。
- AsyncTask默认是串行执行任务的,但是我们仍然可以通过AsyncTask的executeOnExecutor方法来并行地执行任务
public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; @MainThread public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } // 借助ArrayDeque和线程池,顺序执行的Executor 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 来执行任务的,而SerialExecutor 借助ArrayDeque和线程池,保证了任务是按照顺序一个一个执行的。
那是不是AsyncTask是不是只支持同步执行任务呢?并不是,取决于我们使用的线程池。
myAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");// 并发执行
myAsyncTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, "");// 同步执行
Android 1.6之前,AsyncTask是串行执行; Android 1.6 - 3.0,AsyncTask是并行执行的;Android 3.0 - 及以后版本,默认是串行执行的。
- AsyncTask 调用 cancel() 任务是否立即停止执行?onPostExecute() 还会被调用吗?onCancelled() 什么时候被调用?
任务不会立即停止的,我们调用 cancel 的时候,只是将 AsyncTask 设置为 canceled(可取消)状态,我们从以下代码可以看出,AsyncTask 设置为已取消的状态,那么之后 onProgressUpdate 和 onPostExecute 都不会被调用,而是调用了 onCancelled() 方法。onCancelled() 方法是在异步任务结束的时候才调用的。时机是和 onPostExecute 方法一样的,只是这两个方法是互斥的,不能同时出现。
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
// 线程执行完之后才会被调用
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
HandlerThread
HandlerThread的原理比较简单,一句话就是:具有Looper的Thread。
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
要想使用HandlerThread只需要调用其start方法即可,这样就可以拿到HandlerThread的Looper,进而具备切换到HandlerThread工作线程执行任务的能力。
IntentService
IntentService的代码量也很少,其原理也比较容易理解,简单来说具备了三个特点:
- 相比普通Thread,IntentService继承Service具有较高的优先级,不容易被杀死
- 相比普通Service,HandlerThread可以执行耗时任务
- IntentService执行完任务,自动结束
第一点很容易理解,不再详述。
- 相比普通Service,HandlerThread可以执行耗时任务
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
在IntentService的onCreate方法中,其内部的ServiceHandler的Looper来源于HandlerThread,这样IntentService就具备了在单独的线程执行耗时任务的能力。具体执行流程如下:
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
可以看到,IntentService在onStart方法接收到Intent之后,会向ServiceHandler发送一个消息,这个消息会携带启动Id和Intent。
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);
}
}
ServiceHandler接收到消息之后,会在handleMessage中处理消息。主要工作如下:
- 回调onHandleIntent的用户自定义处理消息逻辑;
- 结束Service - 第三点在这里得到验证
注意:stopSelf(id)并不是立即停止Service,而是等到所有的消息都处理完了再停止Service。比如多次启动同一个IntentService。
Intent intent = new Intent(this,MyIntentService.class);
intent.putExtra("task_action","action1");
startService(intent);
intent.putExtra("task_action","action2");
startService(intent);
intent.putExtra("task_action","action3");
startService(intent);
Execuotr
Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一些列参数来配置线程池,通过不同的参数可以创建不同的线程池,Android的线程池主要分为四类,这四类线程池可以通过Executors所提供的工厂方法来得到。由于Android中的线程池都是直接或者间接通过配置ThreadPoolExecutor来实现的,因此先学习下ThreadPoolExecutor。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
- corePoolSize
线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略,这个时间间隔由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长时,核心线程就会被中止。
- maximumPoolSize
线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务就会被阻塞。
- keepAliveTime
非核心线程闲置时的超时时长,超过这个时长,非核心线程将被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,keepAliveTime同样是作用于核心线程。
- workQueue
线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中
- threadFactory
线程工厂,为线程池提供创建新线程的功能。
任务提交的大概逻辑如下:
When a new task is submitted in method {@link #execute(Runnable)},and fewer than corePoolSize threads are running, a new thread is created to handle the request, even if other worker threads are idle. If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full.
The use of this queue interacts with pool sizing: * * If fewer than corePoolSize threads are running, the Executor * always prefers adding a new thread * rather than queuing. * * If corePoolSize or more threads are running, the Executor * always prefers queuing a request rather than adding a new * thread. * * If a request cannot be queued, a new thread is created unless * this would exceed maximumPoolSize, in which case, the task will be * rejected.
1)当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程;
2)当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行;
3)当workQueue已满,且maximumPoolSize > corePoolSize时,新提交任务会创建新线程执行任务;
4)当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理;
5)当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程;
线程池的分类
通过Executors,可以生成四类典型的线程池
- FixedThreadPool
线程数量固定,且都为核心线程。
当线程处于空闲状态也不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。
- 只有核心线程且没有超时机制
- CachedThreadPool
线程数量不固定的线程池,且均为非核心线程,最大线程数为Integer.MAX_VALUE。
当线程池中的线程都处于活动状态,线程池会创建新的线程来处理新的任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程都有超时机制,这个超时的时长为60s,超过60s闲置线程就会被回收。
与FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这将导致任何任务都会被立即执行。
CachedThreadPool适合执行大量的耗时较少的任务,当整个线程池都处于空闲状态,线程池中的线程都会超时而被停止,这个时候CachedThreadPool之中实际上是没有任何线程的,它几乎不占用任何系统资源。
- 只有非核心线程,有超时机制,超时时间为60s.
- ScheduledThreadPool
核心线程的数量固定,非核心线程的数量没有限制,并且当非核心线程闲置时会被立即回收。
- SingleThreadPool
只有一个核心线程,确保所有的任务都在一个线程中按顺序执行,任务之间无同步问题。