线程在Android中的地位,相信每一位开发者都理解。基于Android的特性,UI线程即主线程主要用于界面的更新,子线程用于进行耗时任务。通过本篇文章,将学习主线程以及子线程的概念,android中的线程形态,包括我们熟悉的AsyncTask、HandlerThread、IntentService,最后,认识线程池在Android中的应用,以及主要的线程池分类。
主线程和子线程
主线程是指进程所拥有的线程,java中,默认情况下一个进程中只有一个线程,即主线程。主线程在任何时候都必须保持高度的流畅性,以应对用户与UI界面的交互。为了主线程的保持较高的响应速度,子线程就派上用场了。通常将比较耗时的操作放在子线程中执行。除了主线程之外的所有线程都叫子线程。Android沿用了java的线程机制,将线程分为UI线程和子线程,UI线程即主线程。UI线程运行四大组件以及它们与用户的交互,子线程执行比较耗时的操作,类似文件读写,网络请求等。
Android中的线程形态
我们知道,我们在实际的开发中,创建一个线程有多种方式,下面将从AsyncTask、HandlerThread、IntentService三者来对线程形态进行介绍。三者的底层实现都采用的是线程,但具有不同的表现形态,在使用中也各有优缺点。
1、AsyncTask
AsyncTask是一个轻量级的异步类,它可以在线程池中执行后台任务,并把执行任务进度以及结果返回给主线程,并更新UI。从实现上来说,AsyncTask内部封装了Thread和handler,可以很方便的执行后台任务和在主线程中更新UI。但是,AsyncTask不适合进行比较耗时的后台任务,下面先介绍AsyncTask的基本用法:
- 创建AsyncTask
class myAsyncTask extends AsyncTask<String,Integer,Integer>{
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.d("myAsyncTask","do onPreExecute");
}
@Override
protected Integer doInBackground(String... files) {
Log.d("myAsyncTask","do doInBackground");
Integer cont = 0;
for (String file : files){
cont ++;
Log.d("myAsyncTask","do file:" + file);
publishProgress(cont);
}
return cont;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
for (Integer integer : values){
Log.d("myAsyncTask","do onProgressUpdate:" + integer.intValue());
}
}
@Override
protected void onCancelled() {
super.onCancelled();
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
Log.d("myAsyncTask","do onProgressUpdate:" + integer);
}
}
- 开启AsyncTask
String[] files = {"file1","file2","file3"};
new myAsyncTask().execute(files);
从上面的代码可以知道,首先我们创建一个内部类,继承AsyncTask,并重写其相关方法。然后创建AsyncTask的对象实例,通过execute方法开启线程。AsyncTask类有三个参数Params,Progess,Result,三个参数都为泛型。Params是我们传入线程的参数,比如网络url,文件路径等,可以包含多个;Progess为线程在执行过程中返回的进度;Result为线程执行完后返回的结果。
- onPreExecute:在主线程中执行,在异步线程开始之前被调用,一般做一些准备的工作。
- doInBackground:在线程池中执行,此方法用于执行异步任务。通过publishProgress方法更新任务进度,publishProgress方法会调用onProgressUpdate方法。任务执行完后,会返回结果给onPostExecute。
- onProgressUpdate:主线程中执行,在后台任务进度改变时被调用。
- onPostExecute:在主线程中执行,异步线程任务执行完成后该方法被调用,参数为后台任务的返回值,即doInBackground的返回值。
- onCancelled:主线程中执行,当后台任务被取消时,该方法被调用,onPostExecute方法将不再被调用。
AsyncTask方便了我们应用,但在使用中有一些限制:
- 必须在主线程中创建和加载;
- execute方法必须在UI线程调用;
- 不要在程序中直接调用onPreExecute、doInBackground、onProgressUpdate、onPostExecute方法;
- 一个AsyncTask对象只能调用一次,即只能执行一次execute方法,否则会报异常;
- AsyncTask采用一个线程来串行执行任务,可以通过其executeOnExecutor方法来并行的执行任务。
上面介绍了AsyncTask的用法以及其在使用中应该注意的情况,下面通过分析AsyncTask的实现原理,就会比较理解AsyncTask的相关限制。
我们从execute方法开始分析:
@MainThread
566 public final AsyncTask<Params, Progress, Result> execute(Params... params) {
567 return executeOnExecutor(sDefaultExecutor, params);
568 }
execute方法会调用executeOnExecutor方法:
603 @MainThread
604 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
605 Params... params) {
606 if (mStatus != Status.PENDING) {
607 switch (mStatus) {
608 case RUNNING:
609 throw new IllegalStateException("Cannot execute task:"
610 + " the task is already running.");
611 case FINISHED:
612 throw new IllegalStateException("Cannot execute task:"
613 + " the task has already been executed "
614 + "(a task can be executed only once)");
615 }
616 }
617
618 mStatus = Status.RUNNING;
619
620 onPreExecute();
621
622 mWorker.mParams = params;
623 exec.execute(mFuture);
624
625 return this;
626 }
上面execute方法中的sDefaultExecutor是一个串行的线程池,一个进程中所有的AsyncTask都在该线程池中排队执行。在executeOnExecutor方法中,首先会对当前状态进行判断,处于runing、finshed状态都会抛出异常,只有在pending状态才向下执行。onPreExecute方法会被最先调用,然后开始后台任务。
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static class SerialExecutor implements Executor {
236 final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
237 Runnable mActive;
238
239 public synchronized void execute(final Runnable r) {
240 mTasks.offer(new Runnable() {
241 public void run() {
242 try {
243 r.run();
244 } finally {
245 scheduleNext();
246 }
247 }
248 });
249 if (mActive == null) {
250 scheduleNext();
251 }
252 }
253
254 protected synchronized void scheduleNext() {
255 if ((mActive = mTasks.poll()) != null) {
256 THREAD_POOL_EXECUTOR.execute(mActive);
257 }
258 }
259 }
系统会将Params参数封装成FutureTask对象,FutureTask是一个并发对象,在这里充当Runnable对象。FutureTask会被交给SerialExecutor 的execute方法处理,将FutureTask插入到任务队列mTasks中。执行完任务后,会调用scheduleNext来开启下一个AsyncTask,直到所有任务被执行完为止。从这里可以看出,AsyncTask是串行执行的。
AsyncTask中有两个线程池(SerialExecutor 、THREAD_POOL_EXECUTOR)和一个handler(InternalHandler)。SerialExecutor 线程池用于任务的排队,THREAD_POOL_EXECUTOR则执行真正的后台任务。InternalHandler用于将执行环境切换到主线程。再AsyncTask的构造方法在中,有如下代码:
mWorker = new WorkerRunnable<Params, Result>() {
299 public Result call() throws Exception {
300 mTaskInvoked.set(true);
301 Result result = null;
302 try {
303 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
304 //noinspection unchecked
305 result = doInBackground(mParams);
306 Binder.flushPendingCommands();
307 } catch (Throwable tr) {
308 mCancelled.set(true);
309 throw tr;
310 } finally {
311 postResult(result);
312 }
313 return result;
314 }
315 };
FutureTask会调用mWorker 的run方法,因此mWork最终将在线程池中执行。再mWork的的call方法中,首先将mTaskInvoked设置true,表示任务已经被调用,然后执行doInBackground方法,最终将结果返回给postResult方法。
private Result postResult(Result result) {
342 @SuppressWarnings("unchecked")
343 Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
344 new AsyncTaskResult<Result>(this, result));
345 message.sendToTarget();
346 return result;
347 }
在postResult方法中,通过getHandler方法获取sHandler 对象并发送一条MESSAGE_POST_RESULT消息。
private static Handler getHandler() {
281 synchronized (AsyncTask.class) {
282 if (sHandler == null) {
283 sHandler = new InternalHandler();
284 }
285 return sHandler;
286 }
287 }
该handler定义如下:
private static class InternalHandler extends Handler {
673 public InternalHandler() {
674 super(Looper.getMainLooper());
675 }
676
677 @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
678 @Override
679 public void handleMessage(Message msg) {
680 AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
681 switch (msg.what) {
682 case MESSAGE_POST_RESULT:
683 // There is only one result
684 result.mTask.finish(result.mData[0]);
685 break;
686 case MESSAGE_POST_PROGRESS:
687 result.mTask.onProgressUpdate(result.mData);
688 break;
689 }
690 }
691 }
可以看到,该handler是一个静态对象,为了将执行环境切换到主线程,因此sHandler 必须在主线程创建。由于静态成员在类加载的时候会被初始化,因此这里变相的要求AsyncTask必须在主线程创建。收到MESSAGE_POST_RESULT消息后,会调用finish方法。
private void finish(Result result) {
664 if (isCancelled()) {
665 onCancelled(result);
666 } else {
667 onPostExecute(result);
668 }
669 mStatus = Status.FINISHED;
670 }
在finish方法中,如果isCancelled方法被调用,即任务被取消,则调用onCancelled(result)方法,反之调用onPostExecute方法,参数为doInBackground方法的返回结果。
下面通过一个例子,验证AsyncTask的串行执行。
点击按钮时,同时开启五个任务,每个任务休眠5秒模拟耗时,最后打印任务执行完成的时间点。
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new TestTask("task1").execute();
new TestTask("task2").execute();
new TestTask("task3").execute();
new TestTask("task4").execute();
new TestTask("task5").execute();
}
});
class TestTask extends AsyncTask<String,Integer,String>{
private String mName = "TestTask";
public TestTask(String mName) {
this.mName = mName;
}
@Override
protected String doInBackground(String... strings) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return mName;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Log.d("TestTask",s + " finshed at time:" + time.format(new Date()));
}
}
结果:
D/TestTask: task1 finshed at time:2018-01-31 02:37:37
D/TestTask: task2 finshed at time:2018-01-31 02:37:42
D/TestTask: task3 finshed at time:2018-01-31 02:37:47
D/TestTask: task4 finshed at time:2018-01-31 02:37:52
D/TestTask: task5 finshed at time:2018-01-31 02:37:57
通过结果,我们可以再次验证AsyncTask在线程池中是串行执行的,当然,这是在Android3.0之后,之前将并行执行。为了能在3.0后并发执行,系统提供了executeOnExecutor方法。
new TestTask("task1").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
new TestTask("task2").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
new TestTask("task3").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
new TestTask("task4").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
new TestTask("task5").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
结果:
D/TestTask: task2 finshed at time:2018-01-31 02:59:26
D/TestTask: task1 finshed at time:2018-01-31 02:59:26
D/TestTask: task3 finshed at time:2018-01-31 02:59:31
D/TestTask: task4 finshed at time:2018-01-31 02:59:31
D/TestTask: task5 finshed at time:2018-01-31 02:59:36
上面的代码运行在API 26上,发现通过executeOnExecutor方法开启并发任务时,最多允许两个AsyncTask任务并发进行。当我们调用execute方法时,系统会通过SerialExecutor 线程池对任务进行排队,最后调用executeOnExecutor串行执行;当我们直接调用executeOnExecutor方法是,跳过了中间的排队过程,直接通过THREAD_POOL_EXECUTOR线程池开启并发任务。
对于AsyncTask实现原理的分析就到这里了,总结一下:在AsyncTask中,一个两个线程池和一个Handler。SerialExecutor 线程池用于任务的排队,THREAD_POOL_EXECUTOR线程池用于执行具体的任务,当任务完成后,通过InternalHandler发送结果消息,并将执行环境切换到主线程。InternalHandler为静态对象,当加载类时,会自动初始化,这样就变相的要求AsyncTask在主线程创建。
2、HandlerThread
HandlerThread继承Thread,可以使用handler的线程。在其run方法中,通过Looper.prepare()创建消息循环队列,Looper.loop()开启消息循环。有了looper对象,这样就可以在HandlerThread中创建Handler对象。与常规Thread不同的是,一般的Thread通常执行一些耗时任务,而HandlerThread内部封装了消息循环,外界需要通过handler通知HandlerThread执行具体的任务。HandlerThread的run方法是一个死循环,因此当不再需要的时候,调用其 quit()或 quitSafely()方法跳出循环,终止线程的执行。
@Override
52 public void run() {
53 mTid = Process.myTid();
54 Looper.prepare();
55 synchronized (this) {
56 mLooper = Looper.myLooper();
57 notifyAll();
58 }
59 Process.setThreadPriority(mPriority);
60 onLooperPrepared();
61 Looper.loop();
62 mTid = -1;
63 }
3、IntentService
IntentService是一种特殊的service,并且是一个抽象类,因此必须为其创建子类才能实现它。IntentService用于执行后台任务,当任务执行完成后会自动停止。由于其属于Server,所以优先级会高于普通的线程,比较适合开启优先级较高的后台任务,不容易因系统内存不足而被杀死。实现上,IntentService封装了HandlerThread和Handler,二者在onCreate方法中被创建:
@Override
104 public void onCreate() {
105 // TODO: It would be nice to have an option to hold a partial wakelock
106 // during processing, and to have a static startService(Context, Intent)
107 // method that would launch the service & hand off a wakelock.
108
109 super.onCreate();
110 HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
111 thread.start();
112
113 mServiceLooper = thread.getLooper();
114 mServiceHandler = new ServiceHandler(mServiceLooper);
115 }
第一次启动IntentServer时,onCreate方法会被调用,首先创建HandlerThread 线程对象并开启线程。然后获取该线程的looper,并创建对应的mServiceHandler ,这样,由mServiceHandler 发送的消息都将被HandlerThread 线程处理执行。每次启动IntentServer,它的onStartCommand会被调用,用于处理每个后台任务的Intent。
@Override
132 public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
133 onStart(intent, startId);
134 return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
135 }
onStartCommand会调用onStart方法:
@Override
118 public void onStart(@Nullable Intent intent, int startId) {
119 Message msg = mServiceHandler.obtainMessage();
120 msg.arg1 = startId;
121 msg.obj = intent;
122 mServiceHandler.sendMessage(msg);
123 }
可以看出,在onStart方法中,通过mServiceHandler发送了Intent对象消息,因为mServiceHandler是在HandlerThread 中创建,所以消息会在HandlerThread 线程中被处理。mServiceHandler的handleMessage方法中,会将收到的intent对象交给 onHandleIntent方法处理。值得注意的是,这里的intent对象和启动server时startServer(intent)里的intent对象是一致的,这样在onHandleIntent方法中通过intent的不同参数开启不同的任务。
private final class ServiceHandler extends Handler {
62 public ServiceHandler(Looper looper) {
63 super(looper);
64 }
65
66 @Override
67 public void handleMessage(Message msg) {
68 onHandleIntent((Intent)msg.obj);
69 stopSelf(msg.arg1);
70 }
71 }
其中,onHandleIntent方法是一个抽象方法,需要在子类中实现。当onHandleIntent方法执行完后,系统会调用stopSelf(msg.arg1)来尝试停止服务。之所以用stopSelf(msg.arg1)而不是用stopSelf()方法是因为当前可能还有其他消息未处理,而stopSelf()方法会立即停止服务,stopSelf(msg.arg1)方法会等待所有消息处理完再停止服务。
由于每执行一次任务就必须启动一次IntentServer,而IntentServer内部是通过消息机制向HandlerThread 请求执行任务的,Handler中的looper是顺序处理消息的,因此,IntentServer也将顺序执行后台任务。
下面通过一个举例,来阐述IntentServer的用法。
public static class ServerTask extends IntentService{
public ServerTask() {
super("test");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String tag = intent.getStringExtra("action");
Log.d("ServerTask","action: " + tag);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("ServerTask","onDestroy--------");
}
}
Intent intent = new Intent(this,ServerTask.class);
intent.putExtra("action","action_tag 1#");
startService(intent);
intent.putExtra("action","action_tag 2#");
startService(intent);
intent.putExtra("action","action_tag 3#");
this.startService(intent);
在onHandleIntent方法中获取intent的参数值,可以对不同的intent进行过滤,执行不同操作。最后在onDestroy方法中进行日志打印,判断service自动结束的时间点。
结果打印:
D/ServerTask: action: action_tag 1#
D/ServerTask: action: action_tag 2#
D/ServerTask: action: action_tag 3#
D/ServerTask: onDestroy--------
从日志可以看出,任务执行的顺序是串行的,当所有的任务执行完成后,onDestroy方法被调用,服务停止。
线程池
提到线程池,首先说一下线程池的优点:
- 重用线程池中的线程,避免创建或销毁线程带来的性能开销;
- 能有效控制线程池的最大并发数,避免大量线程间相互抢占资源导致的阻塞现象;
- 能够对线程进行简单的管理,并提供定时执行或指定间隔循环执行。
Android中的线程池来至与java中的Executor,Executor是一个接口,真正的线程池实现为ThreadPoolExecutor。ThreadPoolExecutor通过不同的参数来创建不同功能的线程池。
ThreadPoolExecutor的构造方法中,通过不同的参数配置线程池,下面通过ThreadPoolExecutor中的不同参数,来理解线程池:
public ThreadPoolExecutor(int corePoolSize,
1182 int maximumPoolSize,
1183 long keepAliveTime,
1184 TimeUnit unit,
1185 BlockingQueue<Runnable> workQueue) {
1186 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
1187 Executors.defaultThreadFactory(), defaultHandler);
1188 }
corePoolSize
线程池的核心线程数,默认情况下,核心线程会一直存活,即使处于闲置状态。当ThreadPoolExecutor的allowCoreThreadTimeout属性设置为true时,则会有超时策略,时间由keepAliveTime决定,当超过该时间时,核心线程将被终止。
maximumPoolSize
线程池能容纳最多的线程数,当达到该上限后,后续的任务将会被阻塞。
keepAliveTime
非核心线程闲置状态超时时间,超过该时间,非核心线程将会被终止。当allowCoreThreadTimeout属性设置为true时,同样作用于核心线程。
unit
用于指定keepAliveTime时间单位,为一个枚举,包括毫秒、秒、分钟等
workQueue
线程池中的任务队列,通过线程池的execute方法提交的runnable对象会存储在该队列中。
ThreadFactory
线程工厂,为线程池提供创建新线程的接口。其只有一个方法,Thread newThread(Runnable r)。
除了上面的参数外,线程池还有一个特殊的参数Rejected-ExecutionHandler,当线程池无法执行任务时,有可能是线程任务已满或者是无法成功执行任务。这时候ThreadPoolExecutor会调用该handler的rejectedExeuction方法来通知调用者,默认情况下会抛出一个异常。
ThreadPoolExecutor执行任务时,遵循下面的规则:
- 如果线程池中的线程数量未达到核心线程数时,会开启一个新的核心线程来执行任务;
- 如果线程池中线程数量达到或超过核心线程数,则会被放入等待队列中排队执行;
- 上述二中,如果无法插入排队队列,那么一般情况是队列已满,此时如果线程数量未达到线程池规定的最大值,那么将开启非核心线程来执行任务;
- 上述三中,如果线程数量已经达到线程池规定的最大线程数,那么将拒绝执行该任务。
上面介绍了线程池,那么接下来介绍几种比较常用的线程池。
- FixedThreadPool
这是一种线程数量固定的线程池,当线程处于空闲状态时,并不会被回收,除非线程池被销毁。当所有线程都处于活跃状态时,新任务将等待,知道有空闲任务出来。由于该种线程池只有核心线程并且核心线程不会被回收,意味着它能够加速外界的请求。 - CachedThreadPool
这是一种线程数量不固定的线程池,并且只有非核心线程。最大线程数为integer.MAX_VALUE,由于integer.MAX_VALUE是一个很大的数,也就是说线程数可以任意大。当线程池中线程处于活跃状态时,会创建新的线程执行任务,否则将利用空闲线程,其中空闲线程的超时时间为一分钟。 - ScheduleThreadPool
核心线程是固定的,而非核心线程是没有限制的,并且非核心线程在闲置时会立即被回收。 - SingleThreadExecutor
该线程池内只有一个核心线程,并且确保所有任务都在同一个任务中执行,这样多任务间就不需要解决同步的问题。