Android 线程与线程池

概述

首先来简单了解一下线程,在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:当任务执行完毕,如果需要对结果进行返回,则使用这里的泛型作为返回值类型

如下简单示例:

java 复制代码
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:

java 复制代码
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()方法中执行一些任务的收尾工作。

想使用这个任务类,只需:

java 复制代码
new DownloadTask.execute();

AsyncTask在使用过程中还存在一些条件限制,如下:

  • AsyncTask必须在主线程中加载,也就是说第一次访问AsyncTask必须发生在主线程
  • AsyncTask的对象必须在主线程中创建
  • execute方法必须在UI线程调用
  • 不要在程序中直接调用onPreExecute、onPostExecute、donInBackground和onProgessUpdate方法
  • 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会运行异常

HandlerThread

HandlerThread继承了Handler,它是一种可以使用Handler的Thread,它的实现非常简单,我们可以打开HandlerThread的源码看看,这里我们就看它的run方法:

java 复制代码
	@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方法:

java 复制代码
	@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方法的源码:

java 复制代码
	@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方法看看:

java 复制代码
	@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的实现:

java 复制代码
	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一个常用的构造方法:

java 复制代码
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的意义在于统一所有的外界任务到一个线程中执行,这使得这些任务之间不需要处理线程同步的问题。

相关推荐
路有瑶台3 分钟前
MySQL数据库学习(持续更新ing)
数据库·学习·mysql
曙曙学编程7 分钟前
初级数据结构——树
android·java·数据结构
zmd-zk1 小时前
flink学习(2)——wordcount案例
大数据·开发语言·学习·flink
Chef_Chen1 小时前
从0开始学习机器学习--Day33--机器学习阶段总结
人工智能·学习·机器学习
hopetomorrow2 小时前
学习路之压力测试--jmeter安装教程
学习·jmeter·压力测试
hopetomorrow2 小时前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
闲暇部落2 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
/**书香门第*/2 小时前
Cocos creator 3.8 支持的动画 7
学习·游戏·游戏引擎·游戏程序·cocos2d
美式小田3 小时前
单片机学习笔记 9. 8×8LED点阵屏
笔记·单片机·嵌入式硬件·学习
猫爪笔记3 小时前
前端:HTML (学习笔记)【2】
前端·笔记·学习·html