线程在Android中是一个很重要的概念,从用途上来说,线程分为主线程和子线程,主线程主要处理的是和界面相关的事情,而子线程则往往用于耗时的操作。由于Android的特性,如果在主线程中执行耗时操作就容易导致程序无法及时响应。除了Thread本身外,Android中可以扮演线程角色的还有很多很多,比如:
AsyncTask,IntentService和HandlerThread。不同形式的线程虽然都是线程,但是它们仍然具有不同的特性和使用场景。
主线程是进程所拥有的线程,在java中默认情况下一个进程只有一个线程,这个线程就是主线程。主线程主要处理界面交互相关的逻辑,因为用户随时会和界面发生交互,因此主线程在任何时候都必须具有较高的响应速度,否则就会产生一种界面卡顿的感觉。为了保持较高的响应速度,这就要求主线程中不能执行耗时的操作,这个时候子线程就派上用场了。
11.2.1 AsyncTask
AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和执行的结果传递给主线程并在主线程中更新UI。从实现来说,AsyncTask封装了Thread和Handler,通过AsyncTask可以更加方便地执行后台任务以及在主线程中访问UI。
AsyncTask是一个抽象的泛型类,它提供了params、progress和result这三个泛型参数。其中params表示的是参数的类型,progress表示后台任务执行进度的类型,而result则表示后台任务的返回结果的类型。
AsyncTask提供了4个核心的方法,它们的含义就不讲了。这里只是简单的提一下。
(1)onPreExecute:在主线程中执行,在异步任务执行之前,此方法会被调用
(2)doInBackground(Params…params):在线程池中执行,此方法用于执行异步任务,params参数表示的是异步任务的输入参数。在此方法中可以通过publishProgress方法来更新任务进度,publishProgress方法会回调onProgressUpdate方法。另外此方法需要返回计算结果给onPostExecute
(3)onProgressUpdate(Progress…values):在主线程中执行,当后天任务的执行进度发生改变时此方法会被调用
(4)onPostExecute(Result result):在异步任务执行完毕之后会被调用
AsyncTask在使用的过程中还是有一些限制的,主要有以下几点:
(1)AsyncTask的类必须在主线程中加载,这就意味着第一次访问AsyncTask必须发生在主线程,当然这个过程在Android4.1及以上版本中已经被系统自动完成了。在ActivityThread的main方法中,它会调用AsyncTask的init方法,这就满足了AsyncTask的类必须在主线程加载的条件了。
(2)AsyncTask的对象必须在主线程中创建
(3)execute方法必须在UI线程调用
(4)不要在程序中直接调用4个核心方法
(5)一个AsyncTask对象只能执行一次,即只能调用一次execute方法
(6)AsyncTask默认是调用execute方法来串行执行任务的,但是我们仍然可以调用executeOnExecutor方法来并行的执行
11.2.2 AsyncTask的工作原理
它的实现原理是这样的:
在主线程执行了execute方法。这个方法会调用onPreExecute方法,然后会将params参数进行封装成FutureTask【继承自Runnable】交给SerialExecutor【用于任务的排队】的execute方法去处理,execute方法会把FutureTask对象添加到任务队列中等待THREAD_POOL_EXECUTOR去执行任务,一旦得到执行官就会调用doInBackGround方法,
1)在这个用户复写的方法中如果调用了publishProcess方法,则会通过AsyncTask的内部类InternalHandler实例sHandler发送一条MESSAGE_POST_PROGRESS消息,更新进度,sHandler处理消息时onProgressUpdate(Progress… values)方法将被调用;
2)执行中如果遇到异常,则发送一条MESSAGE_POST_CANCEL的消息,取消任务,sHandler处理消息时onCancelled()方法将被调用;
3 )如果执行成功,则发送一条MESSAGE_POST_RESULT的消息,显示结果,sHandler处理消息时onPostExecute(Result result)方法被调用;
AsyncTask中有两个线程池(serialExecutor和THREAD_POOL_EXECUTOR)和一个静态的Handler(InternalHandler),其中线程池serialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正的任务的执行,Handler(InternalHandler)用于将工作环境切换会主线程中。
继承自Handler的InternalHandler实例sHandler是一个静态的Handler对象,为了能够将执行环境切换会主线程的话,就必须要求sHandler在主线程中创建。又由于sHandler是静态的,所以会在加载类的时候进行初始化。综合来看,就会变相的要求AsyncTask类必须在主线程中加载。
11.2.3 HandlerThread
她继承了Thread,它是一种可以使用Handler的Thread【以为着本身包含了Looper】。它在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler了。
使用步骤
尽管HandlerThread的文档比较简单,但是它的使用并没有想象的那么easy。
下面看一下它的使用步骤:
(1)创建一个HandlerThread,即创建了一个包含Looper的线程。
HandlerThread handlerThread = new HandlerThread("leochin.com");
handlerThread.start(); //创建HandlerThread后一定要记得start()
(2)获取HandlerThread的Looper
Looper looper = handlerThread.getLooper();
(3)创建Handler,通过Looper初始化
Handler handler = new Handler(looper);
接下来你就可以通过handler给工作线程发消息了。最后注意一下由于HandlerThread的run方法是一个无限循环,因此当明确不需要再使用HandlerThread的时候,可以通过它的quit或者quitSafely方法来终止线程的执行,这是一个良好的编程习惯。
11.2.4 IntentService
是一种特殊的service,它继承了service并且它是一个抽象类,因此必须创建它的子类才能使用IntentService。IntentService可用于执行后台耗时的任务。
优点:
(1)当任务执行后它会自动停止,
(2)由于具有Service的特性,所以它相比于普通的Thread具有较高的优先级不容易被后台杀死,
IntentService封装了HandlerThread和Handler。这一点可以从onCreate方法中看出来:
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
可以明显的看到,在onCreate方法中创建了HandlerThread 对象,并且开启了这个线程。通过这个线程拿到Looper对象,再将这个Looper对象作为参数传到Handler中创建了一个mServiceHandler 对象。每次启动IntentService的时候,都会调用onStart方法,在这个方法中IntentService仅仅是通过mServiceHandler 发送了一个消息,这个消息会在HandlerThread中被处理。下面是onStart的源码:
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
上述的ServiceHandler是IntentService的一个内部类,它继承了Handler。如下所示:
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
上面在onStart方法中发送了消息,最后在mServiceHandler的handleMessage方法中调用了用户需要覆写的onHandlerIntent方法进行消息的处理。当最后一个消息处理完毕之后就调用stopSelf(msg.arg1)停止服务。
提到了线程池就有必要说一下线程池的好处:
(1)重用线程,避免频繁创建和销毁线程带来的性能开销;
(2)能够有效的控制线程池的最大并发数,避免大量的线程之间因为互相抢占资源导致的阻塞;
(3)能够对线程进行简单的管理;
Android中的线程池的概念来源于java中的Executor,Executor是一个接口,真正的线程池的实现是ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置。通过不同的配置就可以创建不同的线程池。从线程池的功能特性上来看,Android的线程池主要分为4类,这4类线程池可以通过Executors所提供的工厂方法来得到。我们可以先来看一下ThreadPoolExecutor
11.3.1 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);
}
(1)corePoolSize:线程池的核心线程数。默认情况下,核心线程会在线程池中一直存活,即使他们处于空闲的状态。但是如果将ThreadPoolExecutor中的allowCoreThreadTimeOut设置为true的话,那么闲置的核心线程在等待新任务到来时就会有超时策略。这个时间间隔由keepAliveTime指定。当等待时间超过keepAliveTime指定的时间就核心线程就会被终止。
(2)maximumPoolSize:线程池所能容纳的最大线程数,当活动线程数达到这个值后,后续的新任务就会被阻塞。
(3)keepAliveTime:闲置线程的超时时长。
(4)unit:用于指定keepAliveTime参数的时间单位。
(5)workQueue:线程池的任务队列,通过ThreadPoolExecutor的execute方法提交的Runnable对象都会存储其中的。
(6)threadFactory:线程工厂,为线程池创建新线程的功能。
ThreadPoolExecutor执行任务时大致是遵循如下规则的:
(1)如果线程池中的线程数量没有达到核心线程的数量的话,那么新能加入一个任务就会新创建一个核心线程;
(2)如果线程池中的线程数量达到或者超过核心线程的数量的话,那么就会将新来的任务添加到等待队列中;
(3)如果等待队列已满,但是线程数量没有达到最大线程数,就会启动非核心线程来执行任务;
(4)如果步骤3中的线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,并且会抛出异常;
ThreadPoolExecutor的参数配置在AsyncTask中有明显的体现,下面就是AsyncTask中的线程池的配置情况;
private static final String LOG_TAG = "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);
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sThreadFactory);
11.3.2 线程池的分类
1,FixThreadPool
它是一种线程数量固定的线程池,而且全是核心线程。线程处于空闲状态也不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态,新任务就会处于等待状态,直到有空闲的线程出来。由于它的显著特点,这就意味着它能够更加快的响应外界的请求。另外任务队列也是没有大小限制的。
2,CacheThreadPool
是一种线程数量不固定的线程池。它只有非核心线程,并且其最大线程数为Integer.MAX_VALUE。由于这是一个很大的数,所以可以认为最大线程数可以任意大。当线程池中有空闲线程的时候就会用空闲线程来执行新任务,但是空闲线程是有时间限制的【60s】,当没有空闲线程的话就会创建新的线程来执行新任务。还有值得注意的是,这个线程池的任务队列其实相当于空集合。也就是说新任务都会立即执行不用等待。这类线程池比较适合执行大量的耗时较少的任务。
3,SchemeThreadPool
它的核心线程数是固定的,但是非核心线程数是无限制的,并且当非核心线程闲置时就会被立刻回收。这类线程池用于执行定时任务和具有固定周期的重复任务。
4,SingleThreadPool
只有一个核心线程,任务都是在同一个线程中按照顺序执行的。它的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题。