第11章 Android的线程和线程池
线程分为主线程和子线程,主线程负责处理UI界面操作,子线程负责执行耗时操作,由于Android的特性,主线程执行耗时过长就会ANR。除了Thread本身之外,AsyncTask、IntentService和HandlerThread也可以扮演线程的角色。 AsyncTask底层是线程池,其余两个直接使用了线程。
优缺点:AsyncTask封装了线程池和Handler,它主要是为了方便开发者在子线程中更新UI,HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler,IntentService是一个服务,系统对其进行了封装使其可以更方便的执行后台任务,IntentService内部采用了HandlerThread来执行任务,当任务完成后IntentService就会自动退出,IntentService的作用很像一个后台进程,是一种服务,因而不容易被系统杀死。
线程是操作系统调度最小的单元,线程又是一种受限的系统资源,即线程的创建和销毁都会有相应的开销,当系统中存在大量的线程时,系统会通过时间片轮转的方式来调度每个线程,因此线程不可能做到绝对并行,除非线程数量小于等于CPU的核心数,这不可能。正确的做法是采用线程池,一个线程池可以缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销,Android中的线程池来源于Java,主要是通过Executor来派生特定类型的线程池。
本章主要介绍:主线程和子线程、AsyncTask和它的工作原理、HandlerThread以及IntentService、ThreadPoolExecutor以及线程池的分类。
(一)主线程与子线程
主线程是指进程所拥有的线程,默认情况下一个Java进程只有一个主线程,主线程主要处理界面交互相关的逻辑,因为用户随时会和界面发生交互,因此主线程在任何时候都必须有较高的响应速度,这就要求主线程中不能执行耗时任务,除了主线程(工作线程)之外的线程叫子线程。
Android沿用了Java的线程模式,线程也分为主线程和子线程,其中主线程也叫UI线程,主线程的作用是运行四大组件以及处理他们和用户的交互,而子线程的作用则是执行耗时任务,比如网络请求,IO操作等,网络访问必须在子线程中,这是避免主线程由于耗时操作所阻塞出现ANR现象。
(二)Android中的线程形态
除了传统的Thread以外,还包含了AsyncTask,HandlerThread以及IntentService,三者实现也是线程,不同表现形式、各有优缺点,我们由此进行分析。
2.1.AsyncTask
2.1.1.简单说明
AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。封装了Thread和Handler,AsyncTask我们更加方便的执行后台任务以及主线程访问UI,但是AsyncTask并不适合进行特别耗时的后台任务,这里建议线程池。
2.1.2.核心方法和相关参数
public abstract class AsyncTask<Params, Progress, Result>
//Params表示参数的类型,Progress表示后台任务的执行进度,Result表示结果的类型,可以使用void
onPreExecute (界面的初始化操作) :在主线程执行,在异步任务执行之前被调用,一般用于一些准备工作
doInBackground (处理具体耗时任务) :在线程池中执行,此方法用于执行异步任务,params参数表示异步任务的输入参数,在此方法中可以通过publishProgress方法来更新任务的进度,publishProgress会调用onProgressUpdate方法,此外此方法需要返回计算结果给onPostExecute
onProgressUpdate (进行UI操作) :在主线程执行,当后台任务执行进度发生改变时,返回结果
onPostExecute (任务收尾工作) :在主线程中执行,在异步任务结束后,此方法被调用,其中result参数是后台任务的返回值。
2.1.3.代码示例
//该类主要用于模拟文件下载过程,其输入参数类型为URL,后台任务进程参数为Integer,后台任务的返回结果类型为Long。
public class DownloadFileTask extends AsyncTask<URL, Integer, Long> {
//1.onPreExecute在后台任务开始执行之前进行调用,用于界面的初始化操作,譬如显示一个进度条对话框等。
@Override
protected void onPreExecute() {
super.onPreExecute();
}
//2.该方法中的所有代码都会在子线程中运行,在这里处理所有耗时任务,在这里是不可以更新UI元素,如果需要反馈当前任务的执行进度,
// 可以调用publishProgress(Progress...)方法来完成
//参数不定。
//用来执行具体任务并通过publishProgress来更新进度。同时还需要判断下载任务是否被外界取消了。下载完成后返回结果:下载的总字节数。
//线程池中执行。
@Override
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalsize = 0;
for (int i = 0; i < count; i++) {
totalsize += Downloader.dowdloadFile(urls[i]);
publishProgress((int) (i / (float) count * 100));
//在主线程中执行,当异步任务被取消时,onCancelled将会被调用,onPostExecute将不会被调用。
if (isCancelled())
break;
}
return totalsize;
}
//3.后台调用了publishProgress(Progress...)方法后,onProgressUpdate将会被很快调用,其方法中携带的参数是从后台任务中传递过来的,
// 在这里对UI进行更新,利用参数中的数值可以对界面元素进行相应的更新。
//更新界面中的下载进度,在主线程中。当publishProgress被调用时,此方法会被调用。
@Override
protected void onProgressUpdate(Integer... values) {
setProgressPercent(values[0]);
}
//4.当后台任务执行完毕后并使用return语句返回后,该方法很快被调用。返回数据会作为参数传递到此方法中。可利用返回数据来进行UI操作。
//譬如:提醒任务执行结果,以及关闭进度条对话框等。
//运行于主线程,在界面上做一些提示,譬如弹出一个对话框告知用户下载已完成。
@Override
protected void onPostExecute(Long aLong) {
showDialog("Download: " + aLong + " bytes");
}
}
new DownloadTask().execute(url1,url2,url3);
2.1.4.注意事项:
1.AsyncTask的类必须在主线程加载,也就意味着第一次访问AsyncTask必须发生在主线程;
2.AsyncTask的对象必须在主线程创建
3.execute方法必须在UI线程调用
4.不要在程序中直接调用它的四个方法
5.一个AsyncTask对象只能执行一次
6.Android3.0后AsyncTask默认采用一个线程来串行执行任务,当然也可以通过AsyncTask的executeOnExecutor方法来并行的执行任务。
2.2.AsyncTask的工作原理
步骤一:execute方法开始,调用executeOnExecutor方法。从串行的线程池到onPreExecute方法最先执行,再到线程池开始执行。
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
//sDefaultExecutor实际上是一个串行的线程池,一个进程中所有的AsyncTask全部在这个串行的的线程池中排队执行
return executeOnExecutor(sDefaultExecutor, params);
}
@MainThread
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方法最先执行
onPreExecute();
mWorker.mParams = params;
//线程池开始执行
exec.execute(mFuture);
return this;
}
步骤二:线程池的执行过程。将Params参数封装为FutureTask对象;execute方法将FutureTask对象插入到任务列表的mTask中;若无正在活动AsyncTask,执行AsyncTask;串行执行每一个。
//1.系统会把AsyncTask的Params参数封装为FutureTask对象,FutureTask是一个并发类,在这里他充当了Runnable的作用,然后调用SerialExecutor的execute方法。
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
//分析AsyncTask排队执行的过程。
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
//2.execute会将FutureTask对象插入到任务列表的mTask中
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
//3.如果这个方法没有正在活动的AsyncTask任务,那么就会调用SerialExecutor的scheduleNext的方法来执行下一个AsyncTask任务
if (mActive == null) {
scheduleNext();
}
}
//4.当一个AsyncTask任务执行后,AsyncTask会继续执行其他任务直到任务都被执行完为止,从这一点可以看出,AsyncTask是串行
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
需要说明的是:AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正的执行任务,IntentalHandler用于将执行环境从线程池切换到主线程。
步骤三:FutureTask的run方法会调用mWorker的call方法,因此mWorker的call方法最终会将线程池中执行。设置标志mTaskInvoked表明任务执行与否,执行AsyncTask的doInBackground方法,接着将其返回值传递给postResult方法,postResult通过sHandler发送一个MESSAGE_POST_RESULT消息。
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
//将mTaskInvoked设置为true,表示当前任务已经被调用过了
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
//执行AsyncTask的doInBackground方法,接着将其返回值传递给postResult方法
return postResult(doInBackground(mParams));
}
};
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
步骤四:sHandler(IntentHandler)是一个静态的Handler对象,为了能够将执行环境切换到主线程,这就是要求sHandler这个对象必须在主线程创建,由于静态成员会在加载类的时候进行初始化,因此这就变相要求AsyncTask的类必须在主线程加载,否则无法工作,同时sHandler收到MESSAGE_POST_RESULT这个消息后会调用AsyncTask的finish方法。
private static class InternalHandler extends Handler {
@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;
}
}
}
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
场景:点击按钮同时启动五个AsyncTask任务,每一个会休眠3s来模拟耗时操作,同时把每个AsyncTask执行结束的时间打印。
btn_click.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new MyAsyncTask("AsynacTask#1").execute("");
new MyAsyncTask("AsynacTask#2").execute("");
new MyAsyncTask("AsynacTask#3").execute("");
new MyAsyncTask("AsynacTask#4").execute("");
new MyAsyncTask("AsynacTask#5").execute("");
new MyAsyncTask("AsynacTask#6").execute("");
}
});
public class MyAsyncTask extends AsyncTask<String,Integer,String> {
private String TAG = "MyAsyncTask";
private String mName = "AsyncTask";
public MyAsyncTask(String name){
super();
mName = name;
}
@Override
protected String doInBackground(String... params) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return mName;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Log.e(TAG, s+"execute finish at:"+df.format(new Date()) );
}
}
}
2.3.HandlerThread
HandlerThread 继承了Thread,是一种可以使用Handler的Thread,在run方法中通过Looper.prepare()来创建消息队列,并通过loop()来开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler了,HandlerThread的run方法如下:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
//创建消息队列
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
//开启消息循环
Looper.loop();
mTid = -1;
}
与Thread的不同之处:普通Thread主要用于在run方法中执行一个耗时操作,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务。其run方法无限循环的,故而可以通过quit方法或者quitSafely方法来终止线程进行。
2.4.IntentService
IntentService可用于执行后台耗时的任务,当任务执行后它会自动停止,IntentService是服务,所以优先级比单纯的线程要高很多,不容易杀死,IntentService适合做一些高优先级的后台任务。IntentService封装了HandlerThread和Handler,这一点可以从他的OnCreate看出:
步骤一:OnCreate会创建一个HandlerThread,然后使用它的Looper来构造一个Handler对象mServiceHandler,mServiceHandler发送的消息最终都会在HandlerThread中执行。
@Override
public void onCreate() {
//IntentService第一次启动的时候,onCreate会创建一个HandlerThread,然后使用它的Looper来构造一个Handler对象mServiceHandler,mServiceHandler发送的消息最终都会在HandlerThread中执行。
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
步骤二:onCreate调用onstartCommond,onstartCommond调用onStart,然后借助mServiceHandler发送一个消息。
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
//只是借助mServiceHandler发送一个消息,这个消息会在HandlerThread中处理,mServiceHandler收到消息后,会将Intent对象传递给onHandlerIntent方法去处理,
mServiceHandler.sendMessage(msg);
}
步骤三:使用mServiceHandler的onHandleIntent对不同的后台任务做处理;当onHandleIntent执行结束后,调用stopSelf(int id)尝试停止服务(等待所有消息处理完毕后才会终止服务)。
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);
}
}
需要说明的是:onHandleIntent方法是一个抽象方法,它需要我们在子类中实现,它的作用是从Intent参数中区分具体的任务并执行这些任务。
场景:举例说明IntentService的工作方式
代码:
public class LocalIntentService extends IntentService {
private static final String TAG = "LocalIntentService";
public LocalIntentService() {
super(TAG);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String action = intent.getStringExtra("task_action");
Log.d(TAG, "receive task:" + action);
SystemClock.sleep(3000);
if("com.hzk.action.hzk1".equals(action)){
Log.d(TAG, "handle task"+action);
}
}
@Override
public void onDestroy() {
Log.d(TAG, "Service onDestroy: ");
super.onDestroy();
}
}
btn_click.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent service = new Intent(MainActivity.this,LocalIntentService.class);
service.putExtra("task_action","com.hzk.action.hzk1");
startService(service);
service.putExtra("task_action","com.hzk.action.hzk2");
startService(service);
service.putExtra("task_action","com.hzk.action.hzk3");
startService(service);
}
});
<service android:name=".LocalIntentService"/>
//service你为什么不注册啊???
由日志可知,三个后台任务都是排队执行的,它们的执行顺序是他们发起请求对的顺序,hzk1,hzk2,hzk3。另外当第三个任务执行完毕后,LocalIntentService才会真正地停止。
(三)Android中的线程池
线程池的优点:1.重用线程池中的线程,避免因为线程创建和销毁所带来的性能开销;2.有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象;3.对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
Android中的线程池来源于JAVA的Executor,Executor是一个接口,真正的线程池实现为ThreadPoolExecutor,提供了一系列参数来创建不同的线性池。一般主要分为四种。
3.1.ThreadPoolExecutor
3.1.1.ThreadPoolExecutor的几个参数
ThreadPoolExecutor是线程池的真正实现,它的构造方法提供了一系列的参数来配置线程池,这些参数会直接影响线程池的功能特性。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
ThreadFactory threadFactory)
(1)corePoolSize :线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活;(2)maximumPoolSize :线程池所容纳最大的线程数,当活动线程数达到这个数值后,后续的新任务将会被阻塞;(3)keepAliveTime :非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收;(4)unit :指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit,毫秒,秒,分钟等;(5)workQueue :线程池的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中;(6)threadFactory :线程工厂,为线程池创建新线程,ThreadFactory是一个接口,他只有一个方法,Thread new Thread(Runnable r)。
3.1.2.ThreadPoolExecutor执行任务的规则:
1.如果线程池中的线程数量<核心线程的数量,直接启动一个核心线程来执行任务;
2.如果线程池中的线程数量>=核心线程的数量,任务会被插入到任务队列中排队等待执行;
3.如果在步骤2中无法将任务插入到任务队列中,由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,立刻启动一个非核心线程来执行任务;
4.步骤3中线程数量已经达到线程池规定的最大数量,那么就会拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。
3.1.3.AsyncTask的配置情况
//AsyncTask对THREAD_POOL_EXECUTOR这个线程池进行了配置
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//1.核心线程数等于CPU核心数 + 1
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
//2.线程池的最大线程数为CPU核心数的2倍+1
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//3.核心线程无超时机制,非核心线程的限制时超时时间为1s
private static final int KEEP_ALIVE = 1;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable, "AsyncTask #" + mCount.getAndIncrement());
}
};
//4.任务队列的容量为128
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingDeque<Runnable>(128);
public static final Executor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
3.2.线程池的分类
Android最常见的有四种不同功能特定的线程池;直接或者间接地配置ThreadPoolExecutor来实现功能特性,它们分别是FixedThreadPool、CachedThreadPool、ScheduleThreadPool和SingleTheardExecutor。
3.2.1.FixedThreadPool
FixedThreadPool:通过ExecutorService 的newFixedThreadPool创建,是一种线程数量固定的线程池。其只有核心线程并且这些核心线程不会被回收(除非线程池被关闭),这意味着它能够更快加速的相应外界的请求,FixedThreadPool中只有核心线程并且这些核心线程没有超时机制,另外任务大小也是没有限制的。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
3.2.2.CachedThreadPool
CachedThreadPool:通过Executors的newCachedThreadPooll来创建,这是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数为Integer.MAX_VALUE,最大线程数可以任意大,当线程池中的线程都处于活动状态的时候,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务,超时机制为60s:60s后闲置线程会被回收掉。适用于:比较适合执行大量的耗时较少的任务,当整个线程池属于闲置状态时,线程池中的线程都会超时而被停止,这个时候CachedThreadPool之中实际上是没有任何线程,不占用任何资源。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
3.2.3.ScheduleThreadPool
通过newScheduleThreadPool方法来创建,它的核心线程数量是固定的,而非核心线程是没有限制的,非核心线程闲置时会立即回收。ScheduleThreadPool适用于定时任务和具有固定周期的重复任务。
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
3.2.4.SingleTheardExecutor
通过newSingleTheardExecutor创建,内部只有一个核心线程,确保所有的任务都在同一个线程按顺序执行。无需处理线程同步问题。
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
3.2.5.如何使用?
btn_click02.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Runnable command = new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
}
};
//1.fixedThreadpool
ExecutorService fixedThreadpool = Executors.newFixedThreadPool(4);
fixedThreadpool.execute(command);
//2.CachedThreadPool
ExecutorService cachedThreadpool = Executors.newCachedThreadPool();
cachedThreadpool.execute(command);
ScheduledExecutorService scheduledpool = Executors.newScheduledThreadPool(4);
//2000ms后执行command
scheduledpool.schedule(command,2000, TimeUnit.MICROSECONDS);
//延迟10s后,每隔100ms执行一次command
scheduledpool.scheduleAtFixedRate(command,10,1000,TimeUnit.MICROSECONDS);
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
singleThreadPool.execute(command);
}
});