概述
首先来简单了解一下线程,在Android中线程分为UI线程与子线程,所谓UI线程也就是主线程,UI线程主要用于处理界面相关的事,子线程则是用于执行耗时任务。
在Android中,扮演线程角色的除Thread外,还有AsyncTask、IntentService、HandlerThread。这几种不同的线程形式拥有不同的特性和使用场景,下面一一介绍一下:
- AsyncTask封装了线程池和Handler,它的主要作用是为了方便开发者在子线程通知主线程更新UI
- HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler
- IntentService是一个服务,系统对其进行了封装使其可以方便执行后台任务,IntentService内部采用HandlerThread来执行任务,当任务执行完毕后IntentService会自动退出
当我们在一个进程中频繁创建和销毁线程,这会带来巨大的系统开销,显然不是一个高效的做法,正确的做法是采用线程池,一个线程池中会缓存一定数量的线程,这样可以节省大量开销。
三种线程形式
AsyncTask
AsyncTask是一种轻量级异步任务类,它可以在线程池中执行后台任务,然后将执行的进度和最终结果传递给主线程并在主线程中更新UI。从实现上来说,AsyncTask封装了Thread和Handler,通过AsyncTask可以更加方便执行后台任务以及在主线程中访问UI,但AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的后台任务来说民间一使用线程池。
AsyncTask是一个抽象类,我们要使用它得创建一个子类去继承它,在继承AsyncTask我们可以为AsyncTask类指定三个参数:
- Params:在执行AsyncTask时需要传入的参数,这些参数参数可用于后台任务中使用
- Progress:后台任务执行时,如果需要在界面上展示当前进度,使用该泛型作为进度单位
- Result:当任务执行完毕,如果需要对结果进行返回,则使用这里的泛型作为返回值类型
如下简单示例:
class DownLoadTask extends AsyncTask<Void,Integer,Boolean>{}
第一个参数Void表示不需要传递参数给后台任务使用,第二个参数Integer表示使用整数作为进度参数单位,第三个Boolean参数表示使用布尔数据来反馈执行结果
接着还要去重写AsyncTask中四个重要的方法:
- onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作
- doInBackground(Params…)
这个方法中的所有代码都会在子线程中运行,我们在这里进行耗时任务的处理,可以通过return将执行结果返回,如果AsyncTask类第三个参数是Void,就不需要return了。这个方法中不能进行UI操作,如果需要更新UI元素,调用publishProgress(Progress…)方法完成
- onProgressUpdate(Progress…)
调用了publishProgress(Progress…)方法之后,onProgressUpdate(Progress…)方法很快就会被调用,在这个参数可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新
- onPostExecute(Result)
当后台任务执行完并通过return进行结果返回时,这个方法就会很快被调用,返回的数据作为参数传递到此方法中,可以根据这个参数进行一些UI操作
例如我们要实现一个后台下载功能的自定义AsyncTask类DownloadTask:
class DownloadTask extends AsyncTask<Void,Integer,Boolean>{
@Override
protected void onPreExecute(){
progressDialog.show();//显示进度条
}
@Override
protected Boolean doInBackground(Void...params){
try{
while(true){
int downloadPercent=download();//虚构方法,表示进行下载任务
publishProgress(downloadPercent);
if(downloadPercent>=100){//进度到达100,结束
break;
}
}
}catch(Exception e){
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer...values){
progressDialog.setMessage("下载进度");//更新下载进度
}
@Override
protected void onPostExecute(Boolean result){
progressDialog.dismiss();//关闭进度对话框
if(result){
//提示下载成功
}else{
//提示下载失败
}
}
}
这样我们就完成了一个具体的AsyncTask任务类,相信也能大概理解使用AsyncTask的诀窍了,就是在donInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。
想使用这个任务类,只需:
new DownloadTask.execute();
AsyncTask在使用过程中还存在一些条件限制,如下:
- AsyncTask必须在主线程中加载,也就是说第一次访问AsyncTask必须发生在主线程
- AsyncTask的对象必须在主线程中创建
- execute方法必须在UI线程调用
- 不要在程序中直接调用onPreExecute、onPostExecute、donInBackground和onProgessUpdate方法
- 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会运行异常
HandlerThread
HandlerThread继承了Handler,它是一种可以使用Handler的Thread,它的实现非常简单,我们可以打开HandlerThread的源码看看,这里我们就看它的run方法:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();//创建Looper对象
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
可以看到这里我们通过Looper.prepare()
来创建消息队列,通过Looper.loop()
来开启消息循环,这样我们就可以在实际使用时在HandlerThread中使用Handler了。
从上面我们可以看出HandlerThread和Thread的不同之处:
- Thread主要是在run方法中去执行耗时任务
- HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread来执行一个具体的任务
由于HandlerThread的run方法是一个无限循环,因此明确不需要使用HandlerThread时,可以通过它的quit或quitSafely方法来终止线程的执行。
IntentService
上面介绍到了HandlerThread,HandlerThread一个使用场景就是IntentService。IntentService是一种特殊的Service,它继承了Service并且它是一个抽象类,所以必须创建它的子类才可以使用IntentService。
IntentService可用于执行后台的耗时任务,当任务执行后它会自动停止。因为IntentService是服务,所以它的优先级比单纯的线程高很多,所以IntentService比较适合执行一些高优先级的后台任务。
在IntentService的实现上,它封装了HandlerThread和Handler,我们可以看以下IntentService的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 + "]");//HandlerThread对象
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);//Handler对象
}
可以看到在onCreate方法中会创建一个HandlerThread,上面说到在HandlerThread中会创建出一个Looper对象,所以在这我们就可以获取这个Looper对象并使用这个Looper对象创建出一个Handler对象mServiceHandler
,这样我们通过mServiceHandler发送的消息都会在HandlerThread中执行。
在每次启动IntentService时,它的onStartCommand方法就会调用一次,IntentService在onStartCommand方法中去处理每个后台任务的Intent,我们可以看看onStartCommand方法的源码:
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
在onStartCommand方法中调用了onStart方法,打开onStart方法看看:
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
可以看到这里仅是通过mServiceHandler发送了一条消息,这个消息会在HandlerThread中被处理。mServiceHandler在接收到消息后,会将这个消息交给onHandleIntent方法处理,我们可以看看mServiceHandler对象的类ServiceHandler的实现:
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方法结束后会调用stopSelf(int startId)方法而不是stopSelf()方法来停止服务,这是因为stopSelf(int startId)方法会等待所有消息都处理完后再停止服务。
这里的onHandleIntent()方法是一个抽象方法,我们需要在子类中实现。另外我们需要知道的是,Handler中的Looper是顺序执行后台任务的,当多个后台任务存在时,这些后台任务也是按照外界发起的顺序排队执行的。
线程池
Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池实现为ThreadPoolExecutor。THreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池,从线程池的功能特性来说,可以将线程池分为四类,这四类线程池可以通过Executors所提供的工厂方法来得到(后面再详细介绍)
再介绍线程池一下的好处:
- 线程池中的线程可以重用,避免因为线程的频繁创建与销毁带来的性能开销
- 能有效控制线程池的最大并发数,避免大量的线程之间因为互相抢占系统资源而导致的阻塞现象
- 能够对线程进行简单管理,并提供定时执行以及指定间隔循环执行等功能
ThreadPoolExecutor
ThreadPoolExecutor是线程池的真正实现,它的构造方法提供了一系列的参数来配置线程池,这些参数直接影响线程池的功能特性,如下是ThreadPoolExecutor一个常用的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
- corePoolSize:线程池的核心线程数。默认情况下核心线程会一直存货,即使它们处于闲置状态
- maximumPoolSize:线程池能容纳的最大线程数,当活动线程到达这个数值后,后续的新任务会被阻塞
- keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程会被回收
- unit:指定keepAliveTime参数的时间单位 TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分钟)
- workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会被存储在这个参数中
- threadFactory:线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)
ThreadPoolExecutor执行任务时大致遵顼如下规则:
- 如果线程池中的线程数量为达到核心线程数,那么就直接启动一个核心线程来执行任务
- 如果线程池中的线程数量已达到或超过核心线程的数量,那么任务就会被插入到任务队列中排队等待执行
- 如果上面一条中无法将任务插入任务队列中了,这往往是由于任务队列已满,这时如果线程数量未达到线程池规定的最大值,那么就会立即启动一个非核心线程来执行任务
- 如果上面一条线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务
四类线程池
根据不同的功能特性,可以将线程池分为四类常见的线程池:
- FixedThreadPool
通过Executors的newFixedThreadPool方法来创建。它是一种线程数量固定的线程池,当线程处于空闲时,它们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到线程空闲出来。
由于FixedThreadPool只有核心线程并且这些核心线程不会被回收,这意味着它能更加快速响应外界的请求。
- CachedThreadPool
通过Executor的newCacheThreadPool方法来创建。它是一种线程数量不定的线程池,它只有非核心线程,并且最大线程数为Integer.MAX_VALUE,实际上就相当于线程数量可以任意大。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用闲置的线程来处理。
CacheThreadPool适合执行大量的耗时较少的任务。
- ScheduledThreadPool
通过Executors的newScheduledThreadPool方法来创建。它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收。
ScheduledThreadPool适用于执行定时任务和具有固定周期的重复任务。
- SingleThreadExecutor
通过Executors的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,它确保所有任务都在同一个线程中按顺序执行。
SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中执行,这使得这些任务之间不需要处理线程同步的问题。