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

简易的 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;
    }
}

MyLooperMyHandlerThread 工作的核心,持有着任务队列。在 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 的官方实现由 HandlerLooperMessageQueue 协同工作。

  • 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 后,会交给 HandlerdispatchMessage 方法来调度:

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 的创建时机在于 HandlerThreadrun 方法中,在该方法中会调用 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),线程很快就会被结束,因此,内存泄露问题也只是一小会,我们是完全不用担心的。

总结

ThreadExecutorAsyncTaskHandlerThreadIntentService 都可用于后台执行任务。

我们一般不会直接使用 Thread,因为它太难管理了,我们更多会使用 Executor,因为它简单易用,能很好地管理线程池。

AsyncTask 已被弃用,因为它内部是单线程池,任务一多容易导致阻塞,同时它感知不到 Activity 的生命周期,在界面销毁后调用 onPostExecute 时容易导致崩溃或内存泄露。

HandlerThread 常用于需要串行执行的任务,或者需要在特定线程上执行的任务。

IntentService 适合需要上下文(发送通知)的单次任务,在任务完成后会自动停止,Service 开销很大,所以不适合执行轻量任务。

相关推荐
sweetying4 小时前
30了,人生按部就班
android·程序员
用户2018792831675 小时前
Binder驱动缓冲区的工作机制答疑
android
真夜5 小时前
关于rngh手势与Slider组件手势与事件冲突解决问题记录
android·javascript·app
用户2018792831675 小时前
浅析Binder通信的三种调用方式
android
用户095 小时前
深入了解 Android 16KB内存页面
android·kotlin
火车叼位6 小时前
Android Studio与命令行Gradle表现不一致问题分析
android
前行的小黑炭8 小时前
【Android】 Context使用不当,存在内存泄漏,语言不生效等等
android·kotlin·app
前行的小黑炭9 小时前
【Android】CoordinatorLayout详解;实现一个交互动画的效果(上滑隐藏,下滑出现);附例子
android·kotlin·app
用户20187928316721 小时前
Android黑夜白天模式切换原理分析
android