Android 消息机制——Handler详解与常见问题分析

Android 消息机制详解

    • 什么是Handler
    • 使用流程
      • 使用方式一
      • 使用方式二
    • 框架简单分析
    • 消息处理流程的源码分析
      • 创建Looper对象和MessageQueue对象
      • 创建Handler对象
      • 发送消息
      • 处理消息
        • 开启消息循环
        • Handler中消费消息 (调用接口或重写方法)
    • 消息阻塞与唤醒的源码分析
      • 消息阻塞和延时
      • 消息的排序和唤醒
      • 简单理解阻塞和唤醒
      • 从阻塞到唤醒消息的切换
      • Native消息机制
    • 常见问题分析
      • 为什么不能在子线程中更新UI,根本原因是什么?
      • 为什么主线程用Looper死循环不会引发ANR异常?
      • 为什么Handler构造方法里面的Looper不是直接new?
      • MessageQueue为什么要放在Looper私有构造方法初始化?
      • Handler.post()的逻辑在哪个线程执行的,是由Looper所在线程还是Handler所在线程决定的?
      • MessageQueue.next()会因为发现了延迟消息,而进行阻塞。那么为什么后面加入的非延迟消息没有被阻塞呢?
      • Handler的dispatchMessage()分发消息的处理流程?
      • Handler为什么会导致内存泄漏, 如何处理

什么是Handler

Handler的主要作用是在线程之间传递消息。
每个Handler会和每个线程以及线程对应的消息队列相绑定。之后消息就可通过Handler在线程之间传递。

使用流程

使用方式一

// 步骤1:在主线程中 创建Handler类对象
private Handler mhandler = new  Handler(){
    // 通过复写handlerMessage()从而确定更新UI的操作
    @Override
    public void handleMessage(Message msg) {
    	...// 需执行的UI操作
    }
};

// 步骤2:创建消息对象
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放

// 步骤3:在工作线程中 通过Handler发送消息到消息队列中
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
mHandler.sendMessage(msg);

// 多线程可采用AsyncTask、继承Thread类、实现Runnable

使用方式二

// 步骤1:在主线程中创建Handler实例
private Handler mhandler = new mHandler();

// 步骤2:在工作线程中 发送消息到消息队列中 & 指定操作UI内容
// 需传入1个Runnable对象
mHandler.post(new Runnable() {
	@Override
    public void run() {
	    ... // 需执行的UI操作 
    }
});

// 多线程可采用AsyncTask、继承Thread类、实现Runnable

框架简单分析

Android的消息机制主要是指Handler的运行机制

handler工作时核心组件间的关系如下图:

Android 消息机制——Handler详解与常见问题分析_第1张图片

以上模型的解释:

  1. 以Handler的sendMessage方法为例,当发送一个消息后,会将此消息加入消息队列MessageQueue中。

  2. Looper负责去遍历消息队列并且将队列中的消息分发给对应的Handler进行处理。

  3. 在Handler的handleMessage方法中处理该消息,这就完成了一个消息的发送和处理过程。

    这里从图中可以看到参与消息处理有四个主要对象,它们分别是 Handler, Message, MessageQueue,Looper。
    Android 消息机制——Handler详解与常见问题分析_第2张图片

消息处理流程的源码分析

创建Looper对象和MessageQueue对象

我们都知道,ActivityThread就是Android的主线程或UI线程,ActivityThread的main方法是整个APP的入口
当App启动时 创建全局唯一Looper对象和全局唯一MessageQueue消息对象

Android 消息机制——Handler详解与常见问题分析_第3张图片

ActivityThread.java

public static void main(String[] args) {
	...
    Looper.prepareMainLooper();	// 进行准备工作
	...
    Looper.loop();	// 消息处理
}

Looper.java

public static void prepareMainLooper() {
    prepare(false);
    ...
}
private static void prepare(boolean quitAllowed) {
	// 创建全局唯一的Looper对象
    sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

ThreadLocal.java

public void set(T value) {	// 将线程与looper绑定
	Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
    	map.set(this, value);
    else
    	createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

创建Handler对象

Activity中创建Handler

Android 消息机制——Handler详解与常见问题分析_第4张图片

Handler.java

// 重写handleMessage()调用此方法
public Handler(Callback callback) {
    this(callback, false);	// 即调用 Handler(Callback callback, boolean async)
}
public Handler(Callback callback, boolean async) {
	...
    mLooper = Looper.myLooper();	// myLooper()中只有一行代码: sThreadLocal.get();
    mQueue = mLooper.mQueue;
    ...
}

Looper.mylooper() 实质就是取当前线程的looper,这个Looper是在prepare()的时候创建并通过threadLocal存储起来的
Looper.java中有如下代码:

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

另外, 我们在开发中还会用到Looper.getMainLooper(), 它是用来取主线程的looper

/**
 * Returns the application's main looper, which lives in the main thread of the application.
 */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

发送消息

Android 消息机制——Handler详解与常见问题分析_第5张图片

// 我们调用 sendMessage(msg) 时候, 会调用sendMessage(msg, 0)
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    ...
    return enqueueMessage(queue, msg, uptimeMillis);
}
// 把消息放入队列, 并进行排序
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    ...
    return queue.enqueueMessage(msg, uptimeMillis);	// 消息排序
}

处理消息

Android 消息机制——Handler详解与常见问题分析_第6张图片

  • 开启消息循环

    Looper.java

public static void loop() {
    final Looper me = myLooper();	// sThreadLocal.get(); 获取Looper对象
    final MessageQueue queue = me.mQueue;	// 获取消息队列
    ...
	for (;;) {
        Messagemsg = queue.next(); // might block
        ...
        msg.target.dispatchMessage(msg);
        ...
    }
}
  • Handler中消费消息 (调用接口或重写方法)

    Handler.java

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

消息阻塞与唤醒的源码分析

消息阻塞和延时

Looper 的阻塞主要是靠 MessageQueue 来实现的,在MessageQuese的next() 进行阻塞,在 MessageQueue的enqueueMessage() 进行唤醒。主要依赖 native 层的 Looper 依靠 epoll 机制进行的。

MessageQueue.java

Message next() {
	...
	int nextPollTimeoutMillis = 0;
	for (;;) {
	    if (nextPollTimeoutMillis != 0) {
	        Binder.flushPendingCommands();
	    }
		// 阻塞和延时在这里进行
	    nativePollOnce(ptr, nextPollTimeoutMillis);
	    ...
     }
}

从源码可知, 阻塞和延时,主要是 nativePollOnce(ptr, nextPollTimeoutMillis) 调用naive方法操作管道,

具体由 nextPollTimeoutMillis 的值决定是否需要阻塞, 为0的时候表示不阻塞,为-1的时候表示一直阻塞直到被唤醒,其他时间表示延时。

消息的排序和唤醒

在MessageQueue的enqueueMessage()进行排序和唤醒

MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
    ...
	msg.when = when;
   	Message p = mMessages;
    boolean needWake;
    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 {
        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;
    }
    // ----- 此处进行消息唤醒 -----
    // We can assume mPtr != 0 because mQuitting is false.
    if (needWake) {
        nativeWake(mPtr);	
    }
}

对message对象池的重新排序,遵循规则(when从小到大)。

此处for死循环退出情况分两种

  1. p==null表示对象池中已经运行到了最后一个,无需再循环。
  2. 碰到下一个消息when小于前一个,立马推出循环(不管对象池中所有message是否遍历完),进行从新排序。

简单理解阻塞和唤醒

就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。

这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。

​所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

从阻塞到唤醒消息的切换

如下图:

Android 消息机制——Handler详解与常见问题分析_第7张图片

Native消息机制

​ 参考文档

常见问题分析

  • 为什么不能在子线程中更新UI,根本原因是什么?

Android 消息机制——Handler详解与常见问题分析_第8张图片

mThread是UI线程,这里会检查当前线程是不是UI线程。那么为什么onCreate里面没有进行这个检查呢。

这个问题原因出现在Activity的生命周期中,在onCreate方法中,UI处于创建过程,对用户来说界面还不可视,直到onStart方法后界面可视了,再到onResume方法后界面可以交互。

从某种程度来讲,在onCreate方法中不能算是更新UI,只能说是配置UI,或者是设置UI的属性。这个时候不会调用到ViewRootImpl.checkThread(),因为ViewRootImpl没被创建。而在onResume方法后,ViewRootImpl才被创建。这个时候去交互界面才算是更新UI。

setContentView只是建立了View树,并没有进行渲染工作(其实真正的渲染工作是在onResume之后)。

也正是建立了View树,因此我们可以通过findViewById() 来获取到View对象,但是由于并没有进行渲染视图的工作,也就是没有执行ViewRootImpl.performTransversal。同样View中也不会执行onMeasure() ,如果在onResume() 方法里直接获取View.getHeight() /View.getWidth() 得到的结果总是0。

  • 为什么主线程用Looper死循环不会引发ANR异常?

简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,
此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制。

  • 为什么Handler构造方法里面的Looper不是直接new?

如果在Handler构造方法里面new Looper,怕是无法保证保证Looper唯一,只有用Looper.prepare()才能保证唯一性,具体见prepare() 方法。

  • MessageQueue为什么要放在Looper私有构造方法初始化?

因为一个线程只绑定一个Looper,所以在Looper构造方法里面初始化就可以保证mQueue也是唯一的

即Thread对应一个Looper 对应一个 mQueue。

  • Handler.post()的逻辑在哪个线程执行的,是由Looper所在线程还是Handler所在线程决定的?

由Looper所在线程决定的。

逻辑是在Looper.loop()方法中,从MsgQueue中拿出msg,并且执行其逻辑,这是在Looper中执行的,因此有Looper所在线程决定。

  • MessageQueue.next()会因为发现了延迟消息,而进行阻塞。那么为什么后面加入的非延迟消息没有被阻塞呢?

见:排序和唤醒

  • Handler的dispatchMessage()分发消息的处理流程?

    Android 消息机制——Handler详解与常见问题分析_第9张图片
    msg.callback 在mHandler1.post() 中使用
    mCallback在new Handler是通过接口回调
    在这里插入图片描述
    post()和sendMessage()都是发送消息,加入消息队列得方式也是一样,区别在于处理消息得方式。通过跟踪源码,容易区分。

  • Handler为什么会导致内存泄漏, 如何处理

    非静态内部类持有外部类的引用(通常为Activity)就可能会引起内存泄漏。

  1. 把内部类声明成static:
    • 静态内部类不持有外部类的引用
    • 静态内部类不依赖外部类。静态内部类是可以独立存在的,即使外部类消亡了,内部类还是可以存在的。 编译之后的文件名为: 外部类$内部类
  2. 在外部类销毁的时候调用 moveCallbackAndMessage 来移除回调和消息
  3. 可以使用弱引用来解决
// 使用弱引用来引用外部类的实例
private static class MyHandler extends Handler{
   //持有弱引用HandlerActivity,GC回收时会被回收掉.
   private final WeakReference<HandlerActivity> mActivty;
   public MyHandler(HandlerActivity activity){
       mActivty =new WeakReference<HandlerActivity>(activity);
   }
   @Override
   public void handleMessage(Message msg) {
       HandlerActivity activity=mActivty.get();
       super.handleMessage(msg);
       if(activity!=null){
           //执行业务逻辑
       }
   }
} 

你可能感兴趣的:(Android基础,android,android消息机制,安卓消息机制,消息机制,handler)