教你轻松玩转Android线程
Posted 燃云汽车
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了教你轻松玩转Android线程相关的知识,希望对你有一定的参考价值。
上期回顾
上一篇向大家介绍了进程和线程的基本概念,以及在android开发中如何正确地使用线程,创建线程。一经发表,好评如潮:
咳咳,我们先回顾一下上篇内容:
我们讲解了创建线程的几种方式。
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(1, 1,
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线程的主要内容,如果未能解决你的问题,请参考以下文章
短信验证码接收网页版教你分分钟玩转互联网(注册账号轻松解决)