主线程和子线程
主线程是指进程中拥有的线程,在Java中默认情况下一个进程只有一个线程,这个线程就是主线程。主线程主要处理界面交互相关的逻辑。因为用户随时会和界面发生交互,因此主线程在任何时候都必须有较高的响应速度,否则就会产生一种页面卡顿的感觉。为了保持较高的响应速度,这就要求主线程中不能执行耗时的任务,就需要子线程来完成这些任务。子线程也叫工作线程,除了主线程以外的线程都是子线程。
Android沿用了Java的线程模型,其中的线程也分为主线程和子线程,其中UI线程就是主线程。主线程的作用是运行四大组件以及处理他们和用户的交互,而子线程的作用则是耗时任务,比如网络请求,I/O操作等。从Android 3.0开始要求网络访问必须在子线程中进行,否则网络访问会失败并会抛出NetworkOnMainThreadException异常。这样做是为了避免主线程由于耗时操作从而出现ANR现象。
Android中的线程形态
在Android中我们开启子线程最简单方法就是new Thread,实现run方法,执行耗时操作。除了Thread之外,Android系统还封装了Thread具有特殊表现形式的线程。针对不用使用场景和功能分为:AsyncTask,HandlerThread,IntentService。这三者底层实现的都是线程,同时在使用上也各有优缺点。
AsyncTask
这部分请参考:AsyncTask异步任务机制源码分析和总结笔记
HandlerThread
HandlerThread继承了Thread,它是一种可以使用Handler的Thread,它的实现很简单,就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际使用中就允许在HandlerThread中创建Handler了。
@Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
HandlerThread的实现来看,他和普通的Thread有显著的不同之处,普通的Thread主要用于在run方法中执行一个耗时任务,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式;来通知HandlerThread执行一个具体的任务。由于HandlerThread的run方法是一个无限循环,因此当明确不需要再使用HandlerThread时,可以通过他的quit或者quitSafely方法来终止线程的执行,这是一个良好的编程习惯。
使用范例:
HandlerThread mHandlerThread = new HandlerThread("Test", 5); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); } private Runnable mRunnable = new Runnable() { @Override public void run() { while (mRunning) { Log.d("MainActivity", "test HandlerThread..."); try { Thread.sleep(200); } catch (Exception e) { e.printStackTrace(); } } } };
IntentService
Intent是一种特殊的Service,他继承了Service并且他是一个抽象类,因此必须创建他的子类才能使用IntentService。IntentService可用于执行后台耗时的任务,当任务执行完成后它会自动停止,同事由于IntentService是服务的原因,导致它的优先级比单纯的线程要高得多,所以IntentService比较适合执行一些高优先级的后台任务,以内他优先级高不容易被杀死。实际上,IntentService封装了Handler和HandlerThread。在onCreate方法中。
@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方法会被调用,onCreate方法会创建一个HandlerThread,然后使用它的Looper来构造一个Handler对象mServiceHanlder,这样通过mServiceHandler发送的消息最终都会在HandlerThread中执行。每次启动IntentService,它的onStartCommand方法就会被调用一次,IntentService在onStartCommand中处理每个后台任务的Intent。
/** * You should not override this method for your IntentService. Instead, * override {@link #onHandleIntent}, which the system calls when the IntentService * receives a start request. * @see android.app.Service#onStartCommand */ @Override public int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; } @Override public void onStart(Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); }
在onStartCommand方法中调用了onStart方法,在onStart方法中可以了看出,IntentService仅仅是通过mServiceHandler发送了一个消息,这个消息会在Handlerthread中被处理。mServiceHandler收到消息后,会将Intent对象传递给onHandlerIntent方法中去处理。注意这个Intent对象的内容和外界的startService(intent)中intent的内容是完全一致的。通过这个Intent对象即可解析出外界启动IntentService是所传递的参数,通过这些参数就可以区分具体的后台任务,这样在onHandlerIntent方法中就可以对不同的后台任务做出来了。
当onhandlerIntent方法执行结束后,Intent会通过stopSelf(int startId)方法来尝试停止服务,而这个时候之所以采用stopSelf(int startId)而不是stopSelf()来停止服务,那是因为stopSelf()会立刻停止服务,而这个时候可能还有其他的消息未处理,stopSelf(int startId)则会等待所有的消息都处理完毕后才停止服务。
一般来说,stopSelf(int startId)在尝试停止服务之前会判断最近启动的服务次数是否和startId相等,如果相等就立刻停止服务,不相等则不停止服务。这个策略可以从AMS的stopServiceToken方法的实现找到依据。
Android中的线程池
线程池在Android开发中使用的优势特别明显,主要可以概括为三点:
(1) 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销
(2) 能够有效控制线程池的最大并发数,避免大量的线程之间因相互抢占系统资源而导致的阻塞现象。
(3) 能够对线程进行简单的管理,并提供定时执行以及制定间隔循环执行等功能
Android中线程池的概念源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor提供了一系列参数来配置线程池,下面介绍ThreadPoolExecutor的构造方法中的各个参数的含义,这些参数将直接影响到线程池的功能特性,下面是ThreadPoolExecutor的一个比较常用的构造方法。
/** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters and default thread factory. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code handler} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); }
corePoolSize
线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即时他们处于闲置状态。如果将ThreadPoolExecutor的aollwThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略,这个时间间隔有keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。
maximumPoolSize
线程池所能容纳的最大线程数,当活动线程数达到这个最大值后,后续的新任务将会被阻塞。
keepAliveTime
非核心线程闲置是的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。
unit
用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒),TimeUnit.SECONDS(秒),以及TimeUnit.MINUTES(分钟)等
workQueue
线程池中的任务队列,通过线程池的executor方法提交的Runnable对象会存储在这个参数中。
threadFactory
线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,他只是一个方法:Thread newThread(Runnable r)
ThreadPoolExecutor执行任务是大致遵循如下规则:
(1) 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
(2) 如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
(3) 如果在步骤2中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
(4) 如果步骤3中线程数量已达到线程池规定的最大值,那么就拒绝执行此任务,
下面我们以AsyncTask配置为例来进行分析
public abstract class AsyncTask<Params, Progress, Result> { 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); /** * An {@link Executor} that can be used to execute tasks in parallel. */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
从上面的代码可以看出,Asynctask对ThreadPoolExecutor线程池的配置规格
1, 核心线程数等于CPU核心数+1;
2, 线程池的最大线程数等于CPU核心数的2倍+1;
3, 核心线程无超时机制,非核心线程在限制时的超时时间为1秒
4, 任务队列的容量为128;
线程池的分类
1, FixedThreadPool
线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭了。FixedThreadPool只有核心线程并且这些核心线程不会被回收,意味着它能更快速的响应外界的请求。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>()); }
2, CachedThreadPool
一种线程数量不定的线程池,它只有非核心线程,并且最大的线程数为Integer.MAX_VALUE。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
3, ScheduledThreadPool
核心线程数固定,而非核心线程数没有限制。并且当非核心线程闲置时会被立即回收。这类线程池主要用于执行定时任务和具有固定周期的重复任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){ return new ScheduledThreadPoolExecutor(corePoolSize); } /** * Creates a new {@code ScheduledThreadPoolExecutor} with the * given core pool size. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @throws IllegalArgumentException if {@code corePoolSize < 0} */ public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); }
4, SingleThreadPool
线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有外界任务到一个线程中,使得在这些任务之间不需要处理线程同步的问题(顺序执行)。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>())); }