教你轻松玩转Android线程

Posted 燃云汽车

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了教你轻松玩转Android线程相关的知识,希望对你有一定的参考价值。

上期回顾


上一篇向大家介绍了进程和线程的基本概念,以及在android开发中如何正确地使用线程,创建线程。一经发表,好评如潮:

上汽智行小编欢迎大家来评论区吐槽   风太大了我听不清     skr skr skr skr skr skr skr skr skr skr....... 这个时候只要微笑就可以了 前方高能预警,请慎重 前方高能预警,请慎重

咳咳,我们先回顾一下上篇内容:


我们讲解了创建线程的几种方式。

  • New Thread 方式(匿名,继承Thread,Thread+Runnable)

  • ASyncTask

  • Handle+Runnable


同时我们也介绍了工作线程与UI线程之前通信的方式:

  • runOnUiThread

  • view.post

  • view.postDelay

  • Handler+Message


我们在上篇结尾的时候发现其实runOnUiThread、view.post、view.postDelay底层均使用的是Handler来完成线程之间的通信切换。


本篇文章,我们首先来看一下另外一种创建线程的方式,IntentService,再来学习线程池的概念。


IntentService


IntentService,相应大家对Service应该有所了解,Service是四大组件之一,经常负责做一些背后的工作,IntentService是Service的子类,在具有了Service的特性之后,也加入了自己的特性,我们来看一下官方解释:

1* IntentService is a base class for {@link Service}s that handle asynchronous
2
3* requests (expressed as {@link Intent}s) on demand.  Clients send requests
4
5* through {@link android.content.Context#startService(Intent)} calls; the
6
7* service is started as needed, handles each Intent in turn using a worker
8
9* thread, and stops itself when it runs out of work.


IntentService是一种主要用于处理异步请求的Service。通用调用startService来请求,对应的服务即会启动,利用工作线程依次处理请求,并在工作结束时销毁。


IntentService的正解使用姿势:

 1final int KEY = 1;
2
3class WorkService extends IntentService{
4
5    public WorkService(String name) {
6
7        super(name);
8
9    }
10
11    @Override
12
13    protected void onHandleIntent(@Nullable Intent intent) {
14
15        int key = intent.getIntExtra("key",0);
16
17        if(key == KEY){
18
19            //do something
20
21        }
22
23    }
24
25}
26
27Intent intent = new Intent(this,WorkService.class);
28
29intent.putExtra("key",KEY);
30
31startService(intent);


我们可以看到IntentService的使用非常简单,通过intent来传递条件,再由IntentService中的onHandleIntent来进行解析,并做相关操作,而onHandleIntent是在工作线程中进行的。我们来看一下源码中IntentService是如何进行线程切换的。

 1private final class ServiceHandler extends Handler {
2
3    public ServiceHandler(Looper looper) {
4
5        super(looper);
6
7    }
8
9    @Override
10
11    public void handleMessage(Message msg) {
12
13        onHandleIntent((Intent)msg.obj);
14
15        stopSelf(msg.arg1);
16
17    }
18
19}


可以看到,我们在子类中进行的操作是在一个叫ServiceHandler中做的,接着往下看:

 1@Override
2
3public void onCreate() {
4
5    // TODO: It would be nice to have an option to hold a partial wakelock
6
7    // during processing, and to have a static startService(Context, Intent)
8
9    // method that would launch the service & hand off a wakelock.
10
11    super.onCreate();
12
13    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
14
15    thread.start();
16
17    mServiceLooper = thread.getLooper();
18
19    mServiceHandler = new ServiceHandler(mServiceLooper);
20
21}


在IntentService的onCreate方法中,先声明了一个HandlerThread的工作线程,同时将该工作线程绑定的Looper传入到了ServiceHandler中,即我们在onHandleIntent中的耗时操作,会在这个HandlerThread中进行操作。


看,我们的Handler机制是不是特别强大,其实我们看了这么创建线程的方式,绝大部分是通过Handler的方式进行线程切换的,其实掌握了Handler的原理,再去看一些Google封装好的类似于IntentService,runOnUiThread之类,就会非常简单明了。


好,线程的创建基本都到这里,接下来就到我们本篇的重点——线程池。


线程池


之前我们一直在讲线程,我们的Thread,ASyncTask,包括Handler都是再讲单个线程的操作,我们可以将我们需要执行的代码交给线程去执行,执行完之后再给UI线程反馈,或者也不给反馈了。那如果我们有大量重复的耗时操作呢?如果这些大量重复的耗时操作需要我们在很短的时候内去完成呢?这里就有了多线程并发的概念。


提到多线程并发就不得不提到线程池的概念了,Executors是Android为我们提供的创建线程池的工厂类(工厂类是什么?自行百度或google)。


怎样简单地了解线程池的概念呢?我们来算个简单的数学题目。有100个人要从A点到B点,一辆荣威i5除了司机可以做4个人,一辆荣威i5从A点到B点需要5分钟,请问,把这100个人全部从A点送到B点,需要多长时间(我们假设同一辆荣威i5到B点可以立即回到A点)?答案很简单对不对,25*5 = 125分钟。如果我们要求这100个人全部到达B点的时间必须要在30分钟内,好吧,一辆荣威i5是不够了,我们得多加几辆,但是这个协调人谁来当呢,100个人只管坐车,他们肯定不会管,那么这时候,租车公司的概念就要有了吧?我们可以找租车公司,让他们多给我派几辆荣威i5过来,可以保证我们的100人可以在30分钟内到达B点。经过计算,我们至少需要5辆车才可以让100人在30分钟内全部抵达B点。


我们来看看这个例子里面的人物与线程池的对应关系,100个人对应100个待执行的任务,一辆荣威i5对应一个执行线程,一个车队对应一个线程池,这样理解起来是不是简单多了,我们的任务不需要管谁来接我们,线程怎么分配,执行完我们又怎么样,我们只需要执行我们自己的代码就好,其他的事就交给线程池就好了。


带着这么清晰明了的关系,我们来看我们的Executors给我们准备哪几种创建车队(线程池)的方法呢?我们先来看以下一段代码:


源码:

 1public ThreadPoolExecutor(int corePoolSize,
2
3                          int maximumPoolSize,
4
5                          long keepAliveTime,
6
7                          TimeUnit unit,
8
9                          BlockingQueue<Runnable> workQueue,
10
11                          ThreadFactory threadFactory,
12
13                          RejectedExecutionHandler handler
{
14
15    if (corePoolSize < 0 ||
16
17        maximumPoolSize <= 0 ||
18
19        maximumPoolSize < corePoolSize ||
20
21        keepAliveTime < 0)
22
23        throw new IllegalArgumentException();
24
25    if (workQueue == null || threadFactory == null || handler == null)
26
27        throw new NullPointerException();
28
29    this.corePoolSize = corePoolSize;
30
31    this.maximumPoolSize = maximumPoolSize;
32
33    this.workQueue = workQueue;
34
35    this.keepAliveTime = unit.toNanos(keepAliveTime);
36
37    this.threadFactory = threadFactory;
38
39    this.handler = handler;
40
41}


正确使用姿势:

 1BlockingQueue<Runnable> mQueue = new LinkedBlockingQueue<Runnable>(128);
2
3RejectedExecutionHandler handler = new RejectedExecutionHandler() {
4
5    @Override
6
7    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
8
9        LogUtils.d("task has been rejected");
10
11    }
12
13};
14
15ExecutorService executorService = new ThreadPoolExecutor(1,
16
17        10,200,TimeUnit.MILLISECONDS,mQueue,Executors.defaultThreadFactory(),handler);
18
19executorService.execute(myRunnable);


这就是创建车队的终极方法,对,你懂的,不管你叫了多少个车队,其实都是一家公司的。我们来看看创建车队需哪些参数:

  • corePoolSize 线程池中的核心线程数,一般来说核心线程在线程池中是一直存活的,哪怕是IDLE状态,但是如果设置了allowCoreThreadTimeOut为true,那核心线程也会被回收

  • maximumPoolSize 线程池中最大线程数,包括核心线程数和非核心线程数,如果线程池已达到最大线程数的话,新的任务会被阻塞

  • keepAliveTime 超时时间,处于IDLE状态的非核心线程如果IDLE时间超过这个时间就会被回收,如果allowCoreThreadTimeOut为true,那核心线程也会在超时之后被回收

  • TimeUnit 超时时间的时间单位

  • workQueue 线程池的任务队列,交给线程池的任务会首先进到队列中去,再看线程的状态来分配

  • threadFactory 线程工厂,为线程池提供新线程,可以使用Executors的defaultThreadFactory来获取实例,也可以自行继承ThreadFactory。

  • handler 当新的任务无法执行时,回调handler的rejectedExecution来通知调用者


以上就是最原始的创建线程池的方法,我们可以根据自己的业务场景来自定义线程池,但Google对开发者这么友好,怎么可能就丢了这么个方法给开发者使用呢?所以这时候再来看之前Executors给我们提供了哪几个创建线程池的方法。


   1、newFixedThreadPool


源码:

1public static ExecutorService newFixedThreadPool(int nThreads) {
2
3    return new ThreadPoolExecutor(nThreads, nThreads,
4
5                                  0L, TimeUnit.MILLISECONDS,
6
7                                  new LinkedBlockingQueue<Runnable>());
8
9}


正确使用姿势:

1ExecutorService executorService = Executors.newFixedThreadPool(5);
2
3executorService.execute(myRunnable);


newFixedThreadPool也是调用的new ThreadPoolExecutor,只不过有些参数帮我们默认填写了,我们了解了ThreadPoolExecutor的各个参数用法,这时候再来看newFixedThreadPool,是不是觉得超简单?newFixedThreadPool只接收了一个mThreads参数,对应到ThreadPoolExecutor的corePoolSize参数,也就是说newFixedThreadPool创建了一个有固定数量核心线程的线程池。


   2. newSingleThreadExecutor


源码:

 1public static ExecutorService newSingleThreadExecutor() {
2
3    return new FinalizableDelegatedExecutorService
4
5        (new ThreadPoolExecutor(11,
6
7                                0L, TimeUnit.MILLISECONDS,
8
9                                new LinkedBlockingQueue<Runnable>()));
10
11}


正确使用姿势:

1ExecutorService executorService = Executors.newSingleThreadExecutor();
2
3executorService.execute(myRunnable);


newSingleThreadExecutor创建了一个只有一个核心线程的线程池,适用于顺序任务或者单个任务的执行。


   3. newCachedThreadPool


源码:

1public static ExecutorService newCachedThreadPool() {
2
3    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
4
5                                  60L, TimeUnit.SECONDS,
6
7                                  new SynchronousQueue<Runnable>());
8
9}


正确使用姿势:

1ExecutorService executorService = Executors.newCachedThreadPool();
2
3executorService.execute(myRunnable);


newCachedThreadPool创建了一个没有核心线程,但是最大线程数没有限制,且有60s超时时间的线程池,适用于执行大量耗时较少的任务,当线程处于IDLE状态达到60s后会被系统回收,当所有线程都被回收后,线程池占用的系统资源将会极少。


   4. newScheduledThreadPool


源码:

1public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
2
3    return new ScheduledThreadPoolExecutor(corePoolSize);
4
5}


正确使用姿势:

1ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
2
3scheduledExecutorService.schedule(myRunnable,0,TimeUnit.MILLISECONDS);
4
5scheduledExecutorService.scheduleAtFixedRate(myRunnable,0,10,TimeUnit.MILLISECONDS);
6
7scheduledExecutorService.scheduleWithFixedDelay(myRunnable,0,10,TimeUnit.MILLISECONDS);


细心的小伙伴已经发现了,为什么这次返回的是ScheduledExecutorService了,而且使用姿势也变了。我们来看看ScheduledExecutorService是什么?

1* An {@link ExecutorService} that can schedule commands to run after a given
2
3* delay, or to execute periodically.


ScheduledExecutorService就是ExecutorService的子类,但是它有一些独特的特性是ExecutorService没有的,它可以执行定时任务或者具有固定周期的任务。


最后一个知识点,既然有创建线程池,那么肯定有关闭线程池吧?请看官方提供的正确关闭线程池的例子:

 1void shutdownAndAwaitTermination(ExecutorService pool) {
2
3   pool.shutdown(); // Disable new tasks from being submitted
4
5   try {
6
7     // Wait a while for existing tasks to terminate
8
9     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
10
11       pool.shutdownNow(); // Cancel currently executing tasks
12
13       // Wait a while for tasks to respond to being cancelled
14
15       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
16
17           System.err.println("Pool did not terminate");
18
19     }
20
21   } catch (InterruptedException ie) {
22
23     // (Re-)Cancel if current thread also interrupted
24
25     pool.shutdownNow();
26
27     // Preserve interrupt status
28
29     Thread.currentThread().interrupt();
30
31   }
32
33}


ExecutorService提供了两个关闭方法:

  • shutdown  锁定线程池,这时候不允许新任务进来了,待当前线程池中的任务执行完毕后关闭线程池

  • shutdownNow  直接关闭线程池


官方的代码意思就是说,我先锁定线程池,再给你60s的时间来执行当前的任务,如果60s到了我就直接关闭线程池了,当然,我们也可以直接调用shutdownNow来关闭线程池。


好了,终于告一段落了,本期主要跟大家一起学习了Android中线程的概念,和几种创建方式,希望大家共勉。相信能看到这里的也只有高能程序猿了。

声明:本文内容及图片由BC-AUTO转载至网络,来源于上汽智行。如涉及版权问题,请联系管理员删除。


以上是关于教你轻松玩转Android线程的主要内容,如果未能解决你的问题,请参考以下文章

Pandas时间序列详解 | 轻松玩转Pandas

短信验证码接收网页版教你分分钟玩转互联网(注册账号轻松解决)

Android组件化一文教你玩转APT

腾讯工程师教你玩转 RocksDB

教你玩转Spring Cloud Alibaba,150页全解中文文档,限时分享

两句命令教你玩转《黑客帝国》中的“代码雨”