haohao

Android 多线程实现方式

Markdown

只为初心

一个优秀的程序员,必须具备两种能力:

  • 学习能力
  • 时间管理能力

Android 多线程实现方式

通常来说,一个应用至少有一个进程,而一个进程至少有一个线程。
线程是 CPU 调度的基本单位,进程是系统资源分配的基本单位。
进程独享内存资源,一个进程可以看作一个 JVM ,一个进程崩溃后,在保护模式下一般不会对其它进程产生影响。
同一个进程中的线程共享内存资源,一个线程死掉就导致整个进程死掉。
行了,进程和线程就扯嫩忙多。
Android 提供了四种常用的多线程实现方式:

  • AsyncTask
  • 异步消息机制
  • IntentService
  • ThreadPoolExcutor

AsyncTask

我们的老朋友 AsyncTask 类,它是封装好的线程池,操作 UI 线程极其方便。

瞅一眼,AsyncTask 的三个泛型参数:

public abstract class AsyncTask<Params, Progress, Result>

  • params传入参数类型,即 doInBackground() 方法中的参数类型;
  • Progress ,异步任务执行过程中返回的任务执行进度类型,即 publishProgress() 和onProgressUpdate() 方法中传入的参数类型;
  • Result ,异步任务执行完返回的结果类型,即 doInBackground() 方法中返回值的类型。

四个回调方法:

  • onPreExecute(),在主线程执行,做一些准备工作。
  • doInBackground(),在线程池中执行,该方法是抽象方法,在此方法中可以调用 publishProgress() 更新任务进度。
  • onProgressUpdate(),在主线程中执行,在 publishProgress() 调用之后被回调,展示任务进度。
  • onPostExecute(),在主线程中执行,异步任务结束后,回调此方法,处理返回结果。

注意

  • 当 AsyncTask 任务被取消时,回调 onCanceled(obj) ,此时 onPostExecute(),不会被调用,AsyncTask 中的 cancel() 方法并不是真正去取消任务,只是设置这个任务为取消状态,需要在 doInBackground() 中通过 isCancelled() 判断终止任务。
  • AsyncTask 必须在主线程中创建实例,execute() 方法也必须在主线程中调用。
  • 每个 AsyncTask 实例只能执行一次 execute() ,多次执行会报错,如需执行多次,则需创建多个实例。
  • Android 3.0 之后, AsyncTask 对象默认执行多任务是串行执行,即 mAsyncTask.execute() ,并发执行的话需要使用 executeOnExecutor() 。
  • AsyncTask 用的是线程池机制和异步消息机制(基于 ThreadPoolExecutor 和 Handler )。Android 2.3 以前,AsyncTask 线程池容量是 128 ,全局线程池只有 5 个工作线程,如果运用 AsyncTask 对象来执行多个并发异步任务,那么同一时间最多只能有 5 个线程同时运行,其他线程将被阻塞。Android 3.0 之后 Google 又进行了调整,新增接口 executeOnExecutor() ,允许自定义线程池(那么核心线程数以及线程容量也可自定义),并提供了 SERIAL_EXECUTOR 和 THREAD_POOL_EXECUTOR 预定义线程池。后来 Google 又做了一些调整(任何事物都不完美),将线程池的容量与 CPU 的核心数联系起来,如目前 SDK 25 版本中,预定义的核心线程数量最少有 2 个,最多 4 个,线程池容量范围 5 ~ 9 。改动如下:
1
2
3
4
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;

异步消息机制

异步消息机制的三大主角: Handler ,Message 和 Looper 。
Looper 负责创建 MessageQueue 消息对列,然后进入一个无限 for 循环中,不断地从消息队列中取消息,如果消息队列为空,当前线程阻塞,Handler 负责向消息队列中发送消息。

Looper

Looper 有两个重要的方法: prepare() 和 loop()。

  • prepare() , Looper 与当前线程绑定,一个线程只能有一个 Looper 实例和一个 MessageQueue 实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true)); 保证 Looper 对象在当前线程唯一
}
// Looper 的构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
  • loop ,进入一个无限 for 循环体中,不断地从消息队列中取消息,然后交给消息的 target 属性的 dispatchMessage 方法去处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// 无限循环体,有没有想过在 UI 线程里,有这样一个死循环,为什么界面没卡死??
// 答案最后揭晓。
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycle();
}
}

Handler

Handler 负责向消息队列中发送消息。
在 Activity 中我们直接可以 new Handler ,那是因为在 Activity 的启动代码中,已经在当前 UI 线程中调用了 Looper.prepare() 和 Looper.loop() 方法。

在子线程中 new Handler 必须要在当前线程(子线程)中创建好 Looper 对象和消息队列,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
//在子线程中
Looper.prepare();
handler = new Handler() {
public void handleMessage(Message msg) {
//处理消息
};
};
Looper.loop();

之后,你拿着这个 Handler 对象就可以在其他线程中,往这个子线程的消息队列中发消息了。

HandlerThread

HandlerThread 可以看作在子线程中创建一个异步消息处理机制的简化版,HandlerThread 对象自动帮我们在工作线程里创建 Looper 对象和消息队列。

使用方法:

1
2
3
4
5
6
7
8
9
10
mHandlerThread = new HandlerThread("MyHandlerThread");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
//处理消息
}
};

之后你就可以使用 Handler 对象往工作线程中的消息队列中发消息了。

看一下源码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
}

注意:handler 在 UI 线程中初始化的,looper 在一个子线程中执行,我们必须等 mLooper 创建完成之后,才能调用 getLooper ,源码中是通过 wait 和 notify 解决两个线程的同步问题。

IntentService

IntentService 可以看成是 Service 和 HandlerThread 的合体。它继承自 Service ,并可以处理异步请求,其内部有一个 WorkerThread 来处理异步任务,当任务执行完毕后,IntentService 自动停止。

如果多次启动 IntentService 呢? 看到 HandlerThread ,你就应该想到多次启动 IntentService ,就是将多个异步任务放到任务队列里面,然后在 onHandlerIntent 回调方法中串行执行,执行完毕后自动结束。

下面对源码进行简单的解析,IntentService 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//onHandleIntent 方法在工作线程中执行,执行完调用 stopSelf() 结束服务。
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public IntentService(String name) {
super();
mName = name;
}
/**
* enabled == true 时,如果任务没有执行完,当前进程就死掉了,那么系统就会令当前进程重启。
* 任务会被重新执行。
*/
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
super.onCreate();
// 上面已经讲过,HandlerThread 对象 start 之后,会在工作线程里创建消息队列 和 Looper 对象。
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
// 获得 Looper 对象初始化 Handler 对象。
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
// IntentService 每次启动都会往工作线程消息队列中添加消息,不会创建新的线程。
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
// 官方建议 IntentService onStartCommand 方法不应该被重写,注意该方法会调用 onStart 。
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
//服务停止会清除消息队列中的消息,除了当前执行的任务外,后续的任务不会被执行。
mServiceLooper.quit();
}
/**
* 不建议通过 bind 启动 IntentService ,如果通过 bind 启动 IntentService ,那么 onHandlerIntent 方法不会被回调。Activity 与 IntentService 之间的通信一般采用广播的方式。
*/
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
/**
* 子类必须要实现,执行具体的异步任务逻辑,由 IntentService 自动回调。
*/
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}

IntentService 源码很容易理解,你也可以就自己的应用场景封装自己的 IntentService 。

场景

  • 正常情况下,启动 IntentService ,任务完成,服务停止;
  • 异步任务完成前,停止 IntentService ,服务停止,但任务还会执行完成,完成后,工作线程结束;
  • 多次启动 IntentService ,任务会被一次串行执行,执行结束后,服务停止;
  • 多次启动 IntentService ,在所有任务执行结束之前,停止 IntentService ,服务停止,除了当前执行的任务外,后续的任务不会被执行;

ThreadPoolExcutor

Markdown

图片来自 Jakob Jenkov 博客

ThreadPool

用来管理一组工作线程,任务队列( BlockingQueue )中持有的任务等待着被线程池中的空闲线程执行。

常用构造方法:

1
2
3
4
5
6
7
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
);
  1. corePoolSize 核心线程池容量,即线程池中所维持线程的最低数量。corePoolSize 初始值为 0 ,当有新任务加入到任务队列中,新的线程将被创建,这个时候即使线程池中存在空闲线程,只要当前线程数小于 corePoolSize ,那么新的线程依然被创建。
  2. maximumPoolSize 线程池中所维持线程的最大数量。
  3. keepAliveTime 空闲线程在没有新任务到来时的存活时间。
  4. unit 参数 keepAliveTime 的时间单位。
  5. workQueue 任务队列,必须是 BlockingQueue 。

简单使用

创建 ThreadFactory ,当然也可以自定义。
1
2
3
4
5
6
7
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
创建 ThreadPoolExecutor 。
1
2
3
4
5
6
7
8
9
10
11
// 根据 CPU 核心数确定线程池容量。
public static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
mThreadPoolExecutor = new ThreadPoolExecutor(
NUMBER_OF_CORES * 2,
NUMBER_OF_CORES * 2 + 1,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
backgroundPriorityThreadFactory
);
执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
mThreadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
//do something
}
});
Future future = mThreadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
//do something
}
});
//任务可取消
future.cancel(true);
Future<Integer> futureInt = mThreadPoolExecutor.submit(new Callable<Integer>() {
@override
public Integer call() throws Exception {
return 0;
}
});
//获取执行结果
futureInt.get();
FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>(){
@override
public Integer call() throws Exception {
return 0;
}
});
mThreadPoolExecutor.submit(task);
task.get();


联系我

Wechat ID

公众号

生活不止于眼前的苟且, 还有诗和远方的田野