一、概述
Handler 作为 Android 消息机制的重要一员,给我们开发带来了极大的便利。
可以说有异步线程与主线程通信的地方,就会有出现Handler的身影。
二、基本使用方式
val handler = android.os.Handler {msg ->
//接受并处理消息逻辑handleMessage(Message msg)
Log.i("test", "$msg")
true
}
//发送消息
handler.sendMessage(0x01)
handler.post {
// 处理runnable
}
复制代码
实例化Handler,并重写 handleMessage
方法 ,然后在需要的地方调用 send
以及 post
系列方法就可以了。非常简单易用,同事支持延时消息的发送。
三、原理解析
3.1 Handler和Looper之间的关联
在实例化 Handler 的时候 Handler 会去检查当前线程的 Looper 是否存在,如果不存在则会抛出异常,所以在创建 Handler 之前一定先创建 Looper 。
检测了mLooper是否为null,代码如下:
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
复制代码
报错信息RuntimeException:that has not called Looper.prepare()
。表明当前的Looper并没有创建。而一般我们在UI主线程中不需要创建Looper对象也能正常运行,因为UI主线程已经为我们创建好了 Looper。
在非主线程中,Looper是需要自己手动创建,代码如下:
class HandlerThread : Thread() {
var mHandler: Handler? = null
override fun run() {
super.run()
Looper.prepare()
mHandler = Handler { msg ->
Log.i("test", "$msg")
true
}
Looper.loop()
}
}
复制代码
Looper.prepare()
方法来创建 Looper ,并借助 ThreadLocal 来实现和当前线程的绑定功能,并且Looper内部还会持有一个 MessageQueue。
源代码:
public static void prepare() {
prepare(true);
}
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));
}
复制代码
Looper.loop()
方法开始不断尝试从 MessageQueue 中获取 Message , 并分发给对应的 Handler。所以说 Handler 和线程的关联是靠 Looper 来实现的。
3.2 Message 的存储与管理
Handler 提供了一系列方法让我们来发送消息,send()系列和post()系列 。
不管我们调用哪个系列的方法,最终都会调用MessageQueue.enqueueMessage(Message,long)
这个方法。
其实消息管理者 MessageQueue就是个队列,负责消息的入队和出队。。
以 sendEmptyMessage(int)
方法调用次序为例:
//Handler
sendEmptyMessage(int)
-> sendEmptyMessageDelayed(int,int)
-> sendMessageAtTime(Message,long)
-> enqueueMessage(MessageQueue,Message,long)
-> //MessageQueue
-> queue.enqueueMessage(Message, long)
复制代码
3.3 Message 的分发与处理
前面说到了 Looper.loop()
负责对消息的的循环处理。
//Looper
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;
//死循环
for (;;) {
// 不断从 MessageQueue 获取消息
Message msg = queue.next(); // might block
//退出 Looper
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//...
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
...
}
//回收消息
msg.recycleUnchecked();
}
}
复制代码
loop()
里调用了 MessageQueue.next()
,核心代码如下:
//MessageQueue
Message next() {
//...死循环
for (;;) {
//...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//...
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
//...
}
}
复制代码
还调用 msg.target.dispatchMessage(msg)
,msg.target 就是发送该消息的 Handler,这样就回调到了 Handler 那边去了:
//Handler
public void dispatchMessage(Message msg) {
//msg.callback 是 Runnable ,如果是post方法则会走if条件
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
//实现了callback接口进行调用
if (mCallback.handleMessage(msg)) {
return;
}
}
//回调到 Handler 的 handleMessage 方法
handleMessage(msg);
}
}
复制代码
Looper.loop() 是个死循环,会不断调用 MessageQueue.next() 获取 Message ,并调用 msg.target.dispatchMessage(msg)
回到了 Handler 来分发消息,以此来完成消息的回调。
注意:loop()方法并不会卡死主线程。
那么线程怎么切换的呢? 我们将所涉及的方法调用栈画出来,如下:
Thread.foo(){
Looper.loop()
-> MessageQueue.next()
-> Message.target.dispatchMessage()
-> Handler.handleMessage()
}
复制代码
Handler.handleMessage() 所在的线程最终由调用 Looper.loop() 的线程所决定。
平时我们用的时候从异步线程发送消息到 Handler,这个 Handler 的 handleMessage()
方法是在主线程调用的,所以消息就从异步线程切换到了主线程。
3.4 关于Message的复用和缓存池
有时候为了提高效率,我们通常通过Handler.obtainMessage()
来获取一个Message android/os/Handler.java源代码如下:
public final Message obtainMessage(){
return Message.obtain(this);
}
复制代码
android/os/Message.java源代码如下:
由于Message.obtian()
的调用存在多个线程之间同时访问静态变量的数据共享问题,所以需要对方法块进行同步操作。从下面代码中可以看到对单链表的操作,如果存在缓存Message的话,就取出Header Message,并将Header指向Header.next。
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
复制代码
Message除了可以作为发送信息的载体之外,内部还维持了一个最大容量为50的单链表结构作为缓存池。因为这些都是静态变量,所以Message缓存池的概念在整个程序里面只有一个。
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
复制代码
那么sPool是在什么时候添加到Message的呢?
在Looper.loop()中,如果Message被处理之后,会调用下面的方法,完成数据的初始化和添加进缓存池的操作:
public static void loop() {
...
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
@UnsupportedAppUsage
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
复制代码
3.5 Handler、Looper、MessageQueue小结
Handler是通过Looper和MessageQueue的协助,三者通力合作,分工明确。
- Looper :负责关联线程以及消息的分发。在创建的线程中,从MessageQueue获取到发送的Message,分发给 Handler ;
- MessageQueue :是个队列,负责消息的存储与管理。负责管理由Handler发送过来的 Message ;
- Handler : 负责发送并处理消息。面向开发者,提供API,并隐藏背后实现的细节。
总结: Handler 发送的消息由 MessageQueue 存储管理,并由 Loopler 负责回调消息到 handleMessage()。 线程的转换由 Looper 完成,handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。
四、Handler 的延伸及问题解决
4.1 Handler 引起的内存泄露原因以及最佳解决方案
Handler 允许发送延时消息,但如果在延时期间用户关闭了 Activity,那么该 Activity 会出现内存泄露的情况。
产生内存泄露根本原因:Message会持有Handler,依据Java 的特性,内部类会持有外部类,使得 Activity 也会被 Handler 持有,这样最终就导致Message没有被处理 Activity 泄露。
解决方法是:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息。
示例代码如下:
//创建抽象类,持有Activity的弱引用
abstract class SafeActivityHandler private constructor(): Handler() {
private var refActivity: WeakReference<Activity>? = null
constructor(activity: Activity) : this() {
this.refActivity = WeakReference(activity)
}
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
val activity = refActivity?.get()
if (activity != null && !activity.isFinishing) {
handleMsg(msg) //正常处理消息
} else {
removeCallbacksAndMessages(null) //销毁消息,防止泄漏
}
}
abstract fun handleMsg(msg: Message)
}
// Activity静态内部类,去除和外部类关联
class AppHandler(activity: Activity) : SafeActivityHandler(activity) {
override fun handleMsg(msg: Message) {
if (msg.what == 0x01) {
Log.i("123", "处理消息")
}
}
}
//在 `Activity.onDestroy()`前移除消息,加一层保障
override fun onDestroy() {
appHandler.removeCallbacksAndMessages(null) //销毁消息,防止泄漏
super.onDestroy()
}
// 测试 Handler发送消息
appHandler = AppHandler(mActivity)
appHandler.sendEmptyMessageDelayed(0x01,5000)
复制代码
这样双重保障,就能完全避免内存泄露了。
4.2 为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?
通常我们认为 ActivityThread 就是主线程。事实上它并不是一个线程,而是主线程操作的管理者,所以吧,我觉得把 ActivityThread 认为就是主线程无可厚非,另外主线程也可以说成 UI 线程。
在 ActivityThread.main() 方法中有如下代码:
//android.app.ActivityThread
public static void main(String[] args) {
//...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
复制代码
复制代码
Looper.prepareMainLooper(); 代码如下:
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
复制代码
可以看到在 ActivityThread 调用了 Looper.prepareMainLooper() 方法创建了 主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接使用 Handler 了。
4.3 Handler 里藏着的 Callback 能干什么?
在 Handler 的构造方法中有要求传入 Callback ,那它是什么,又能做什么呢?
来看看 Handler.dispatchMessage(msg)
方法:
public void dispatchMessage(Message msg) {
//这里的 callback 是 Runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
复制代码
复制代码
可以看到 Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true) ,那么 Handler 的 handleMessage(msg)
方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。
我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息!
场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH
,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。
4.4 创建 Message 实例的最佳方式
由于 Handler 极为常用,所以为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗。
- 通过 Message 的静态方法
Message.obtain();
复用 - 通过 Handler 的公有方法
handler.obtainMessage()
复用
4.5 子线程里弹 Toast 的正确姿势
正确示例代码如下(同理Dialog也是):
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(HandlerActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
复制代码