Android 多线程:理解 Handler 与 Looper 机制

372 阅读6分钟

简易的 Android 多线程模型

我们来模拟一个简易的 Android 多线程模型。

MyHandlerThread

/**
 * 创建一个线程,并在其内部运行一个 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

/**
 * 作用:循环、取任务、执行任务、退出
 */
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

/**
 * 作用:将任务发送到指定的 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 的任务队列中。

使用示例

三者协同使用:

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) {
}

运行结果:

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 机制的使用示例。

// 创建与主线程 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 方法来调度:

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 是线程的本地变量,也就是每一个线程的内存空间中都具有这个变量,数据在线程之间不共享。

例如这段代码:

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 方法调用顺序如何,运行结果一定是:

Thread-1-localon Thread-1
Thread-2-localon Thread-2

它在 Looper 中也有应用:

// 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。

// 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 是一个线程工具,可以让我们很方便地在主线程和子线程中执行代码。例如:

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 开销很大,所以不适合执行轻量任务。