<<Android 开发艺术探索>> Chapter 11

Android的线程和线程池

主线程和子线程

  • Android中的主线程也叫UI线程,主要作用是运行四大组件以及处理他们和用户的交互。
  • Android中的子线程的作用是执行耗时任务, 比如网络请求,I/O操作等。

Android中的线程状态

除了传统的Thread外,还包含AsyncTaskHandlerThreadIntentService

1. AsyncTask

AsyncTask其实是封装了HandlerThread, 方便了我们的使用。

  1. AsynTask是一个抽象的泛型类,它提供了ParamsProgressResult这三个泛型参数,其中

    • Params表示参数类型,
    • Progress表示后台任务的执行进度的类型,
    • Result则表示后台返回结果
  2. AsyncTask提供了4个核心方法,含义如下

    • onPreExecute()在主线程中执行,在异步任务执行之前,此方法会被调用
    • doInBackground(Parms… params)在线程池中执行,此方法用于执行异步任务,params参数表示异步任务的输入参数
    • onProgressUpdate(Progress… values)在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用
    • onPostExecute(Result result)在主线程中执行,在异步任务执行之后,此方法会被调用,其中result参数是后台任务的返回值.

    执行的顺序是onPreExecute()先执行,接着是doInBackground(),最后才是onPostExecute()
    PS:(有种情况是AsyncTask()还提供了onCancelled()方法,它同样在主线程中执行,当异步任务取消时,onCancelled()方法会被调用,这个时候onPostExecute()则不会被调用)

  3. AsyncTask具体有几点限制

    • AsyncTask第一次访问必须在主线程中加载,在android4.1及以上版本中已经被系统自动完成,和在android5.0源码中可以看出,在ActivityThreadmain()方法,它会调用AsyncTaskinit()方法,满足了在主线程中加载的条件。
    • AsyncTask的对象必须在主线程中创建
    • execute必须在UI线程调用(因为excute()方法中会调用onPreExecute())
    • 一个AsyncTask对象只能执行一次,即只能调用一次execute方法
    • 在Android1.6以前,AsyncTask是串行执行的,Android1.6的时候开始采用并行执行的方式,但是从Android3.0开始,为了避免AsyncTask所带来的兵法错误,AsyncTask又采用一个线程来串行执行任务。尽管如此,我们还是通过executeOnExecutor()方法来并发的执行任务。
2. HandlerThread

HandlerThread继承了Thread,在其run方法中通过Looper.prepare()方法来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler了。在不需要使用HandlerThread时,可以通过他的quit或者quitSafely方法来终止线程的执行

// 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;
}

//使用方法
HandlerThread handlerThread = new HandlerThread("MyHandler");
handlerThread.start();
Handler customHandler = new Handler(handlerThread.getLooper()){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        // 处理Message
    }
};
3. IntentService
  • IntentService是服务的原因,这导致它的优先级比单纯的线程要高很多。其中它封装了HandlerThreadHandler.
  • 它第一次启动会在OnCreate方法中创建一个HandlerThread, 并在其中构造了一个mServiceHandler。由于mServiceHandler发送的消息都在HandlerThread中,所以IntentService也可以执行后台任务。之后,mServiceHandler收到消息 后,会将Intent对象传递给onHandleIntent方法去处理。一般来说停止一个该服务用stopSelf(int startId),是因为stopSelf(int startId)会等到所有的消息都处理完毕后才终止服务。
  • 它也是顺序执行后台任务。
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);
    }
}

@Override
public void onCreate() {
    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

@Override
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

Android中的线程池

使用线程池有以下好处:
  • 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销
  • 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占资源而导致的阻塞现象
  • 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能

Andorid中的线程池都是直接或者间接通过配置ThreadPoolExecutor来实现的

public ThreadPoolExecutor(int corePoolSize,
                        int maximumPoolSize,
                        long keepAliveTime,
                        TimeUnit unit,
                        BlockingQueue<Runnable> workQueue,
                        ThreadFactory threadFactory)
  • corePoolSize: 线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使他们处于闲置状态(如果将ThreadPoolExecutorallowCoreThreadTimeOut参数设为true,那么闲置的核心线程也会受keepAliveTime参数影响被回收)
  • maximumPoolSize:线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务将会被阻塞。
  • keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会回收。
  • unit:用于指定keepAliveTime参数的时间单位
  • workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable独享会存储在这个参数中
  • threadFactory:线程工厂,为线程池提供创建新线程的功能

ThreadPoolExecutor执行任务时大致遵循如下规则:

  1. 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
  2. 如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
  3. 如果在步骤2中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
  4. 如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandlerrejectedExecution方法来通知调用者。

AsyncTaskTHREAD_POOL_EXECUTOR线程池(执行任务的线程池)的配置:

  • corePoolSize = CPU核心数+1;
  • maximumPoolSize = 2倍的CPU核心数+1;
  • 核心线程无超时机制,非核心线程在闲置时间的超时时间为1s;
  • 任务队列的容量为128。
线程池的分类:
  • FixedThreadPool
    线程数量固定的线程池,它只有核心线程并且这些核心线程不会被回收,能够更快地响应外界的请求。
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
        nThread, nThread, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()
    );
}
  • CachedThreadPool
    线程数量不固定的线程池,它只有非核心线程,比较适合执行大量的耗时较少的任务。
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(
        0L, Interger.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()
    );
}
  • ScheduledThreadPool
    核心线程数量固定,非核心线程数量没有限制的线程池,主要用于执行定时任务和具有固定周期的重复任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Interger.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
  • SingleThreadExecutor
    只有一个核心线程的线程池,确保了所有的任务都在同一个线程中按顺序执行。
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(
        new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>())
    );
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容