简易的 Android 多线程模型
我们来模拟一个简易的 Android 多线程模型。
MyHandlerThread
java
/**
* 创建一个线程,并在其内部运行一个 MyLooper
*/
public class MyHandlerThread extends Thread {
private MyLooper mLooper;
private final Object mLock = new Object();
public MyHandlerThread() {
super();
}
public MyHandlerThread(String name) {
super(name);
}
@Override
public void run() {
// 创建自己的 Looper
MyLooper looper = new MyLooper();
synchronized (mLock) {
mLooper = looper;
mLock.notifyAll(); // 通知 mLooper 已创建完毕
}
// 在当前线程循环
mLooper.loop();
}
/**
* 获取此线程的 Looper
* 必须在线程启动后调用
*/
public MyLooper getLooper() {
synchronized (mLock) {
while (mLooper == null) {
try {
// 等待 mLooper 创建完毕
mLock.wait();
} catch (InterruptedException ignored) {
}
}
}
return mLooper;
}
/**
* 退出
*/
public boolean quit() {
// 获取 Looper
MyLooper looper = getLooper();
if (looper != null) {
looper.quit(); // 调用 Looper 的退出
return true;
}
return false;
}
}
MyHandlerThread
是一个线程,主要用于在运行时(run
方法中),创建自己的 MyLooper
对象,并调用该对象的 loop
方法进行循环。
MyLooper
java
/**
* 作用:循环、取任务、执行任务、退出
*/
public class MyLooper {
// 阻塞队列,队列为空时会自动阻塞当前线程
private final BlockingQueue<Runnable> mQueue;
private volatile boolean mIsRunning = true;
// 当前运行的线程
private Thread mThread;
public MyLooper() {
this.mQueue = new LinkedBlockingDeque<>();
}
/**
* 开始循环
* 在 MyHandlerThread 的 run() 方法中调用
*/
public void loop() {
// 记录当前线程
mThread = Thread.currentThread();
while (mIsRunning) {
try {
// 如果队列有任务,会立即取出并执行
// 如果队列为空,线程会在此处休眠
Runnable task = mQueue.take();
task.run();
} catch (InterruptedException e) {
// 中断
mIsRunning = false;
Thread.currentThread().interrupt();
}
}
System.out.println(mThread.getName() + " Looper loop finished.");
}
/**
* 退出循环
*/
public void quit() {
mIsRunning = false;
if (mThread != null) {
// 中断线程,让 take() 抛出 InterruptedException
mThread.interrupt();
}
}
/**
* 用于让 MyHandler 访问队列
*/
BlockingQueue<Runnable> getQueue() {
return mQueue;
}
}
MyLooper
是 MyHandlerThread
工作的核心,持有着任务队列。在 loop()
方法中,会不断从队列中取出任务并执行,提供 quit()
方法,让 loop()
循环停止。
MyHandler
java
/**
* 作用:将任务发送到指定的 Looper 队列
*/
public class MyHandler {
private final MyLooper mLooper;
private final BlockingQueue<Runnable> mQueue;
public MyHandler(MyLooper looper) {
if (looper == null) {
throw new NullPointerException("looper cannot be null");
}
this.mLooper = looper;
this.mQueue = mLooper.getQueue();
}
/**
* 发送一个任务到目标线程执行
*/
public void post(Runnable task) {
if (task == null) {
throw new NullPointerException("task cannot be null");
}
// offer() 是非阻塞添加任务到队列
mQueue.offer(task);
}
}
MyHandler
用于将任务添加到 MyLooper
的任务队列中。
使用示例
三者协同使用:
java
System.out.println("Main thread is: " + Thread.currentThread().getName());
// 创建并启动 MyHandlerThread
MyHandlerThread workerThread = new MyHandlerThread("MyWorkerThread");
workerThread.start();
// 获取该后台线程的 MyLooper
MyLooper backgroundLooper = workerThread.getLooper();
// 创建一个 MyHandler
MyHandler workerHandler = new MyHandler(backgroundLooper);
// 从主线程推送任务到后台线程
workerHandler.post(() -> {
System.out.println("Task - Running on: " + Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (Exception ignored) {
}
System.out.println("Task - Finished.");
});
// 让任务执行一段时间
try {
Thread.sleep(2000);
} catch (InterruptedException ignored) {
}
// 停止后台线程
workerThread.quit();
// 等待后台线程真正终止
try {
workerThread.join();
} catch (InterruptedException ignored) {
}
运行结果:
less
Main thread is: main
Task - Running on: MyWorkerThread
Task - Finished.
MyWorkerThread Looper loop finished.
Android 多线程模型
我们的模型只是一个 Android Handler 机制的大致框架,Android 的官方实现由 Handler
、Looper
和 MessageQueue
协同工作。
-
Handler
: 负责发送和处理消息。 -
MessageQueue
: 是一个消息队列(链表实现),存储着Message
对象。 -
Looper
: 负责从MessageQueue
循环取出消息,并交给对应的Handler
处理。
官方使用示例
我们来看看官方的 Handler 机制的使用示例。
java
// 创建与主线程 Looper 关联的 Handler
Handler handler = new Handler(Looper.getMainLooper()) { // Looper.myLooper()
@Override
public void handleMessage(@NonNull Message msg) {
Runnable callback = msg.getCallback();
if (callback != null) {
callback.run();
return;
}
// 处理消息
// msg.obj;
// msg.what;
// msg.arg1;
// msg.arg2;
System.out.println("Handle message on " + Thread.currentThread().getName());
}
};
// 在子线程中执行耗时操作,并在主线程中更新 UI
new Thread(new Runnable() {
@Override
public void run() {
// ... 执行耗时操作 ...
try {
Thread.sleep(2000);
} catch (InterruptedException ignored) {
}
System.out.println("Task running on " + Thread.currentThread().getName());
handler.post(new Runnable() {
@Override
public void run() {
// ... 回到主线程处理结果 ...
// 例如:更新 UI
System.out.println("Update UI on " + Thread.currentThread().getName());
}
});
// 传递消息
Message message = new Message();
message.what = 1;
message.obj = "Hello World";
handler.sendMessage(message);
}
}).start();
我们利用 Handler
可以在子线程中推送任务给主线程执行。
Handler
机制的核心是传递 Message
对象,我们可以通过 sendMessage()
传递一个 Message
对象,也可以通过 post()
传递一个 Runnable
任务。post
内部会把 Runnable
封装到 Message
对象的 callback
字段中,然后调用 sendMessage
将消息传入到消息队列中。
Looper
拿到 Message
后,会交给 Handler
的 dispatchMessage
方法来调度:
java
public void dispatchMessage(@NonNull Message msg) {
// 如果 Runable 任务不为空
if (msg.callback != null) {
// 直接执行
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 否则,调用我们重写的 handleMessage 方法处理消息
handleMessage(msg);
}
}
ThreadLocal
ThreadLocal
是线程的本地变量,也就是每一个线程的内存空间中都具有这个变量,数据在线程之间不共享。
例如这段代码:
java
ThreadLocal<String> threadLocal = new ThreadLocal<>();
new Thread(() -> {
threadLocal.set("Thread-1-local");
System.out.println(threadLocal.get() + " on " + Thread.currentThread().getName());
}, "Thread-1").start();
new Thread(() -> {
threadLocal.set("Thread-2-local");
System.out.println(threadLocal.get() + " on " + Thread.currentThread().getName());
}, "Thread-2").start();
不管线程 1 和线程 2 中的 set
方法调用顺序如何,运行结果一定是:
less
Thread-1-localon Thread-1
Thread-2-localon Thread-2
它在 Looper 中也有应用:
java
// Looper.java
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// ...
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
比如 Looper.myLooper()
获取的是属于当前线程的 Looper
对象,它是通过 ThreadLocal
来获取的。
Looper
的创建时机在于 HandlerThread
的 run
方法中,在该方法中会调用 Looper.prepare()
来初始化属于当前线程的 sThreadLocal
变量,也就是初始化 Looper
。
java
// Looper.java
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
总结:该机制的本质在于,能让指定的运行中的线程执行任务。例如,我们可以在子线程中某个时刻,推送一个任务给主线程去执行。
延伸:AsyncTask 的内存泄露问题
AsyncTask
是一个线程工具,可以让我们很方便地在主线程和子线程中执行代码。例如:
java
class MyAsyncTask extends AsyncTask {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Object doInBackground(Object[] objects) {
return null;
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
}
}
MyAsyncTask task = new MyAsyncTask();
task.execute(new Object());
但它有个问题:因为它是内部类,持有了外部类的对象引用,这样会导致内存泄露,我们需要加上 static
关键字来修饰它。
实际上这个说法是错误的,因为任何一个内部类都会持有外部类的对象引用。
之所以会导致内存泄露,是因为界面关闭后,AsyncTask
的子线程还在执行任务,而正在执行的线程不会被回收,从而 Activity 无法得到及时回收。
GC 回收的策略,被 GC Root 直接或间接引用的对象不会被回收,运行中的线程、静态对象都会被 GC Root 引用。
但后台线程往往执行的是一个短时任务(如果是非常耗时的任务,应该使用 Service
),线程很快就会被结束,因此,内存泄露问题也只是一小会,我们是完全不用担心的。
总结
、Thread
Executor
、 、AsyncTask
HandlerThread
、IntentService
都可用于后台执行任务。
我们一般不会直接使用 Thread
,因为它太难管理了,我们更多会使用 Executor
,因为它简单易用,能很好地管理线程池。
AsyncTask
已被弃用,因为它内部是单线程池,任务一多容易导致阻塞,同时它感知不到 Activity 的生命周期,在界面销毁后调用 onPostExecute
时容易导致崩溃或内存泄露。
HandlerThread
常用于需要串行执行的任务,或者需要在特定线程上执行的任务。
IntentService
适合需要上下文(发送通知)的单次任务,在任务完成后会自动停止,Service
开销很大,所以不适合执行轻量任务。