Handler实现机制浅析

Handler是Android中的消息处理机制,是一种线程间通信的解决方案,同时你也可以理解为它天然的为我们在主线程创建一个队列,队列中的消息顺序就是我们设置的延迟的时间。

简单使用

一般是在主线程中实现一个Handler,然后在子线程中使用它。

class HandlerActivity: AppCompatActivity() {
 
    private val mHandler = MyHandler()
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 在子线程中通过自定义的 Handler 发消息
        thread {
            mHandler.sendEmptyMessageDelayed(1, 1000)
        }
    }
 
    // 自定义一个 Handler
    class MyHandler: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
        }
    }
}

或者有时候需要在子线程中创建运行在主线程中的Handler

class HandlerActivity: AppCompatActivity() {
    private var mHandler: Handler? = null
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        thread {
            //获得main looper 运行在主线程
            mHandler = MyHandler(Looper.getMainLooper())
            mHandler!!.sendEmptyMessageDelayed(1, 1000)
        }
    }
     // 自定义一个 Handler
    class MyHandler(): Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
        }
    }
}

在第二个用法中出现了一个Looper.getMainLooper(),使用它作为参数,即使MyHandler是在子线程中定义的,但是它的handleMessage方法依然运行在主线程。我们看一下Handler的构造方法之一:

public Handler(@NonNull Looper looper) {
       this(looper, null, false);
}

handleMessage方法具体运行在哪个线程是和这个Looper息息相关的。

概述

image

这就是整个Handler在Java层的流程示意图。可以看到,在Handler调用sendMessage方法以后,Message对象会被添加到MessageQueue中去。而这个MessageQueue就是被包裹在了Looper中。那么Looper对象是干什么的呢?它和Handler是什么关系呢?我们来看一下他们具体的职责吧~

  • Handler

消息机制中作为一个对外暴露的工具,其内部持有了一个 Looper负责Message的发送及处理。

Handler.post(Runnable r)/Handler.sendMessage(Message msg) :向消息队列发送各种消息事件;
Handler.handleMessage():处理相应的消息事件

  • Looper

作为消息循环的核心,其内部包含了一个消息队列 MessageQueue ,用于记录所有待处理的消息;通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者,可以看成是消息泵。注意,线程切换就是在这一步完成的。

  • MessageQueue

则作为一个消息队列,则包含了一系列链接在一起的 Message ;不要被这个Queue的名字给迷惑了,就以为它是一个队列,但其实内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。

  • Message

则是消息体,内部又包含了一个目标处理器 target ,这个 target 正是最终处理它的 Handler。

Handler

从我们大家最熟悉的sendMessage方法说起。sendMessage方法见名思意,就是发送一个信息,可是要发送到哪里去呢,这是代码:

# Handler.java
public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}


public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
       // focus -1 
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
 
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

不管我们是通过post/send发送消息,最终都会调用到enqueueMessage方法,在这个方法中做了两件事:
1.给Message赋值
2.将Message按照时间顺序放入队列

最后的入队列是在MessageQueue中完成的,已经不再是Handler的方法了,也就是说,调用走到了这里。事件的流向已经不归Handler管了。Handler只负责把Message发送出去,然后等待时机处理这条Message,至于Message存取的过程跟Handler没有关系。

注意focus1处的代码,Message将当前的Handler对象赋值给了target字段,所以说:

Handler在发送Message(消息)时,每个发出去的Message都持有把它发出去的Handler的引用。

MessageQueue

MessageQueue是一个由单链表构成的优先级队列(取的都是头部,所以说是队列)。前面提到Handler发送消息入队是由MessageQueue来完成的,那么MessageQueue是在哪里定义好的呢?

答案是在Handler的构造函数中:

public Handler(@Nullable Callback callback, boolean async) {
    // ...
    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;
    // ...
}                                                     
 

可以看到,Handler中持有的MessageQueue是从Looper中拿到的。关于Looper稍后再讲。

上面说到,最后发送消息都调用的是MessageQueuequeue.enqueueMessage(msg, uptimeMillis)方法。现在我们已经拿到了queue,进去看看这个方法它做了什么。

// MessageQueue.java
//省略部分代码
boolean enqueueMessage(Message msg, long when) {
 
    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }
 
        msg.markInUse();
        msg.when = when;
 
        //【1】拿到队列头部
        Message p = mMessages;
        boolean needWake;
 
        //【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            //【3】消息插到队列中间
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
 
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

从以上代码也可看出来:

在多个HandlerMessageQueue中添加数据(发送消息时各个Handler可能处于不同线程)时,其内部通过synchronized关键字保证线程安全。同时messagequeue.next()内部也会通过synchronized加锁,确保取的时候线程安全,同时插入也会加锁。

1.mMessages 是队列的第一消息,获取到它判断消息队列是不是空的,是则将当前的消息放到队列头部;

2.如果当前消息不需要延时,或当前消息的执行时间比头部消息早,也是放到队列头部。

3.如果不是以上情况,说明当前队列不为空,并且队列的头部消息执行时间比当前消息早,需要将它插入到队列的中间位置。

如何判断这个位置呢?依然是通过消息被执行的时间。
通过遍历整个队列,当队列中的某个消息的执行时间比当前消息晚时,将消息插到这个消息的前面。

可以看到,消息队列是一个根据消息【执行时间先后】连接起来的单向链表。想要获取可执行的消息,只需要遍历这个列表,对比当前时间与消息的执行时间,就知道消息是否需要执行了。

Looper

Handler中的MessageQueue对象其实就是Handler中的Looper它的MessageQueueHandlerMessageQueue中添加消息,其实就是往HandlerLooper所持有的MessageQueue中添加对象。
简单来说,LooperMessageQueue是一对一的关系,一个Looper持有一个MessageQueue对象。

回到之前Handler构造函数的代码:

//Handler.java
//省略部分代码
public Handler(@Nullable Callback callback, boolean async) {
    //敲黑板,划重点就是这一句!!!!
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
}

在这一句中Handler通过Looper.myLooper方法获取到了Looper对象,当然,也有可能没获取到。不过,你如果没获取到就要抛异常了。

# Looper.java
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}
TreadLocal
//sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal sThreadLocal = new ThreadLocal();

sThreadLocal是一个ThreadLocal类,并且它的泛型是Looper对象。ThreadLocal提供了线程的局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。简要言之:往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。

可以看到Looper对象是通过ThreadLocal获取的,那么ThreadLocal是何时放进去的呢?查看sThreadLoacl.set()方法的调用位置:

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

可以看得出,最后调用了是prepare(boolean quitAllowed)方法,而这个方法首先判断,如果sThreadLocal有值,就抛异常,没有值才会塞进去一个值。其实很好理解,就是说prepare方法必须调用但也只能调用一次,不调用没有值,抛异常,调用多次也还抛异常。

如果Looper为空就抛异常,现在我们知道了,什么时候Looper为空呢?没有调用prepare方法的时候会为null。

也就是说在构造Handler之前,必须得有Looper对象,换言之,在构造Handler之前,必须调用Looperprepare方法创建Looper

接下来再看看这行sThreadLocal.set(new Looper(quitAllowed));做了什么吧,它是如何塞进去的呢?

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

set方法首先获取到了当前的线程,然后获取一个map。这个map是以键值对形式存储内容的。如果获取的map为空,就创建一个map。如果不为空就塞进去值。要注意的是,这里面的key是当前的线程,这里面的value就是Looper。也就是说,线程和Looper是一一对应的。也就是很多人说的Looper和线程绑定了,其实就是以键值对形式存进了一个map中。

Looper中的MessageQueue是在Looper的构造函数中创建的:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}
Looper是和当前线程绑定的。并且每个线程中只有一个LooperMessageQueue

前面提到,创建Handler对象之前需要调用Looper.prepare()方法先创建一个Looper对象,但我们在activity页面中创建Handler的时候并没有这么做,那是因为主线程默认已经创建Looper了。因此我们先来看看主线程中是如何处理的。

查看ActivityThread类,这是整个app的入口,内部有一个main方法,它是程序的入口:

//ActivityThread.java  
public static void main(String[] args) {  
    ···  
    Looper.prepareMainLooper();  
    ··· 
     ActivityThread thread = new ActivityThread();  
    thread.attach(false, startSeq);  
    if (sMainThreadHandler == null) {  
        sMainThreadHandler = thread.getHandler();  
    }  
    if (false) {  
        Looper.myLooper().setMessageLogging(new  
                LogPrinter(Log.DEBUG, "ActivityThread"));  
    }  
    // End of event ActivityThreadMain.  
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
    Looper.loop();  
    throw new RuntimeException("Main thread loop unexpectedly exited");  
} 

可以看到在ActivityThread中的main方法中,我们先调用了Looper.prepareMainLooper()方法,然后获取当前线程的Handler,最后调用Looper.loop()。先来看一下Looper.prepareMainLooper()方法

//Looper.java    
/**  
* 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() {  
    // 设置不可以退出的Looper
     prepare(false);  
     synchronized (Looper.class) {  
         if (sMainLooper != null) {  
             throw new IllegalStateException("The main Looper has already been prepared.");  
         } 
          sMainLooper = myLooper();  
     }  
}  
//prepare  
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

Looper虽说要分发消息,但是它又不知道你什么时候会发送消息,只能开启一个死循环,不断的尝试从队列中拿数据。这个死循环在哪里开始的?Looper.loop()开启了一个死循环,然后不断的尝试去队列中拿消息。

// Looper.java
public static void loop() {
 
    //拿到当前线程的Looper
    final Looper me = myLooper();
    ...
    //拿到Looper的消息队列
    final MessageQueue queue = me.mQueue;
    ...
    //1 这里开启了死循环
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        msg.target.dispatchMessage(msg);
        ...
        // 回收复用
        msg.recycleUnchecked();
    }
}

在循环中Looper不停的取出消息,拿到Message对象以后,会去调用Message的target字段的dispatchMessage方法;target正是发送这条消息的Handler

public void dispatchMessage(@NonNull Message msg) {  
     if (msg.callback != null) {  
         handleCallback(msg);  
     } else {  
         //如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage  
         if (mCallback != null) { 
              if (mCallback.handleMessage(msg)) {  
                 return;  
             }  
         }  
         handleMessage(msg);  
     }  
 } 

如果Message这个对象有CallBack回调的话,这个CallBack实际上是个Runnable,就只执行这个回调,然后就结束了。

如果Message对象没有CallBack回调,进入else分支判断Handler的CallBack是否为空,不为空执行CallBack的handleMessage方法,然后return,构建Handler的CallBack代码如下:

Handler.Callback callback = new Handler.Callback() {  
    @Override  
    public boolean handleMessage(@NonNull Message msg) {  
        //retrun true,就不执行下面的逻辑了,可以用于做优先级的处理  
        return false;  
    }  
}; 

最后才调用到Handler的handleMessage()函数,也就是我们经常去重写的函数,在该方法中做消息的处理。

消息拦截

可以看到Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;
如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。我们可以利用CallBack这个拦截来拦截Handler的消息。

Handler是如何进行线程切换的

线程间是共享资源的,子线程通过handler.sendXXX,handler.postXXX等方法发送消息,然后通过Looper.loop()在消息队列中不断的循环检索消息,最后交给handle.dispatchMessage方法进行消息的分发处理。

子线程可以更新UI吗?

查看以下代码:

@Override  
  protected void onCreate(@Nullable Bundle savedInstanceState) {  
      super.onCreate(savedInstanceState);  
      setContentView(R.layout.activity_three);  
      new Thread(new Runnable() {  
          @Override  
          public void run() {  
              //创建Looper,MessageQueue  
              Looper.prepare();  
              new Handler().post(new Runnable() {  
                  @Override  
                  public void run() {  
                      Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();  
                  }  
              });  
              //开始处理消息  
              Looper.loop();  
          }  
      }).start();  
  } 

这里需要注意在所有事情处理完成后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于循环等待的状态,因此不需要的时候终止Looper,调用Looper.myLooper().quit()

以上代码是否能正确弹出Toast呢?答案是肯定的。

在ViewRootImpl中的checkThread方法会校验mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的构造器中,也就是说一个创建ViewRootImpl线程必须和调用checkThread所在的线程一致,UI的更新并非只能在主线程才能进行。

线程中更新UI的重点是创建它的ViewRootImpl和checkThread所在的线程是否一致。
系统为什么不建议在子线程中访问UI?

这是因为 Android 的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。

那么为什么系统不对UI控件的访问加上锁机制呢?

缺点有两个:

1.首先加上锁机制会让UI访问的逻辑变得复杂
2.锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。

所以最简单且高效的方法就是采用单线程模型来处理UI操作。

子线程如何通知主线程更新UI(都是通过Handle发送消息到主线程操作UI的)
  • 主线程中定义 Handler,子线程通过 mHandler 发送消息,主线程 Handler 的 handleMessage 更新UI。
  • 用 Activity 对象的 runOnUiThread 方法。
  • 创建 Handler,传入 getMainLooper。
  • View.post(Runnable r) 。
Looper死循环为什么不会导致应用卡死,会耗费大量资源吗?

应用被卡死本质上不是阻塞了主线程,而是阻塞了Looper的loop方法。导致loop方法无法处理其他事件,导致出现了ANR事件。

从前面的主线程、子线程的分析可以看出,Looper会在线程中不断的检索消息,如果是子线程的Looper死循环,一旦任务完成,用户应该手动退出,而不是让其一直休眠等待。

线程其实就是一段可执行的代码,当可执行的代码执行完成后,线程的生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder 线程也是采用死循环的方法,通过循环方式不同与 Binder 驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。Android是基于消息处理机制的,用户的行为都在这个Looper循环中,我们在休眠时点击屏幕,便唤醒主线程继续进行工作。

主线程的死循环一直运行是不是特别消耗 CPU 资源呢?其实不然,这里就涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

主线程的Looper何时退出

在App退出时,ActivityThread中的mH(Handler)收到消息后,执行退出。

//ActivityThread.java  
case EXIT_APPLICATION:  
    if (mInitialApplication != null) {  
        mInitialApplication.onTerminate();  
    }  
    Looper.myLooper().quit();  
    break; 
如何处理Handler使用不当造成的内存泄漏?
class HandlerActivity: AppCompatActivity() {
 
    private val mHandler = MyHandler()
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 在子线程中通过自定义的 Handler 发消息
        thread {
             mHandler.sendEmptyMessageDelayed(1, 1000)
        }
    }
 
    // 自定义一个 Handler
    class MyHandler: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
        }
    }
}

再发送延时消息之前,app推出了,那么handleMessage方法还会执行吗?答案是会的。

MyHandler 是 HandlerActivity 的内部类,会持有 HandlerActivity 的引用。在进入页面以后,发送了一个延时 1s 的消息,如果 HandlerActivity 在 1s 内退出了,由于 Handler 会被 Message 持有,保存在其 target 变量中,而 Message 又会被保存在消息队列中,这一系列关联,导致 HandlerActivity 在退出的时候,依然会被持有,因此不能被 GC 回收,这就是内存泄漏!当这个 1s 延时的消息被执行完以后,HandlerActivity 会被回收。

有延时消息,在界面关闭后及时移除Message/Runnable,调用handler.removeCallbacksAndMessages(null)

内部类导致的内存泄漏改为静态内部类,并对上下文或者Activity/Fragment使用弱引用。

正确创建Message实例

1.通过 Message 的静态方法 Message.obtain() 获取;
2.通过 Handler 的公有方法 handler.obtainMessage()

// Message.java
 
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();
} 

sPool是消息池,obtain会先从消息池中获取Message对象,避免通过new创建过多的对象。

参考:
Handler的初级、中级、高级问法,你都掌握了吗?
Handler原理

你可能感兴趣的:(Handler实现机制浅析)