Handler深入学习

一、概述

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 ,减少内存消耗。

  1. 通过 Message 的静态方法 Message.obtain();复用
  2. 通过 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();
复制代码

猜你喜欢

转载自juejin.im/post/7106113956908892190