欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android中的消息机制:Handler、Message

程序员文章站 2022-07-14 17:43:13
...

Hander、Message

Android 中的消息处理机制之一,也是我们经常使用到的一种;
其中牵扯到了 Handler、Message、MessageQueue、Looper等模块;

经典示例

public class HandlerActivity extends Activity {
    private Handler handler = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);

                // 接受到消息,并处理.
                switch (msg.what) {
                    case 1:
                        break;
                    default:
                        break;
                }

            }
        };

        // 创建子线程。
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 子线程
                handler.sendEmptyMessage(1);
            }
        }).start();

    }
}

上面的代码比较简单
1、声明 Handler 对象,并实例化,重载 handleMessage(Message msg) 方法
2、创建子线程,并在子线程中通过 handler 对象发送消息
即可以实现线程间的消息传递;
为什么这样就可以达到消息的传递能力呢?

handler 分析

我们先来看下Handler中的一些代码:

// 一些变量
final Looper mLooper;
final MessageQueue mQueue;

// 构造方法.
public Handler(Callback callback, boolean async) {
    // ...
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

上面是Handler的一些变量以及构造方法,可以看到,Handler 持有一个 Looper 对象与 消息队列对象(mQueue),其中
- mQueue 是消息存储队列,职责:将消息有序的排列管理
- mLooper 是消息队列的执行人,职责:从消息队列中获取消息

Looper类分析

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

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

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
如果大家看过源码的注释的话,可以看到:prepareMainLooper() 方法的执行是系统在启动应用前自动调用的;其在主线程(UI线程)执行,其中优先执行了 prepare(false) 方法,其目的是让当前线程持有一个 Looper 对象与 MessageQueue 对象,且主线程的 Looper与MesageQueue 不可退出;
经过上面的执行,将 Thread、Looper、MessageQueue相关联,但是并没有执行,需要继续执行 Looper.looper()方法
public static void loop() {
    // ...
    for (;;) {
        // ...    
    }
}

该方法是开始执行死循环,不端的从 MessageQueue中取出 Message 对象并交由其 Handler 对象来处理(这个后面再说),这样以事件驱动的应用程序就跑起来了;
其中数据的校验也控制了一个 Thread 只能有一个 Looper、一个 MeesageQueue;

子线程

上面的代码是在主线程中创建的 Handelr,那我们是否可以在子线程中进行同样的操作呢?

执行代码:

new Thread(new Runnable() {
    Handler childHandler;
    @Override
    public void run() {
        childHandler = new Handler();
    }
}).start();

执行之后大家会发现,异常了: RuntimeException("Can't create handler inside thread that has not called Looper.prepare()"
异常信息很明确:请先执行 Looper.prepare() 方法,实际上就是应为我们创建的线程没有Looper、MessageQueue等相关对象;上面说了,主线程(UI线程)是自动的在应用启动时帮我们进行了这些操作;而我自己创建的子线程默认是没有Looper、MessagwQueue的,所以需要我们显式的执行;并且,需要执行 Looper.looper()让 消息机制启动起来;
所以需要改正为:

new Thread(new Runnable() {
    Handler childHandler;
    @Override
    public void run() {
        // 将 Looper 与线程绑定
        Looper.prepare();
        childHandler = new Handler();
        // 将当前线程的消息机制启动起来
        Looper.looper();
    }
}).start();

Message

在Andorid的消息机制中,Message扮演了重要的角色,是数据信息的搬运工,其承担了消息机制中数据的携带工作;

private static final int MAX_POOL_SIZE = 50;
private static int sPoolSize = 0;
Message next;
private static Message sPool;
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对象来处理、传递信息的,所以需要不停生成新的Message对象,而新生成对象的开销是比较大的,尤其是需要频繁的生成、销毁;大家可能想到了对象池,是的,对象池可以很好的处理这种情况;但是看Message类的源码就会发现,这里并没有关于存储对象的Map集合,那又是如何实现的呢?
对象指向的是内存中的某一块地址;Message对象中包含了两个引用:sPool 和 next;而这里,sPool 实际指向了当前的 Message对象,而 next 指向的是 下一个 Message 对象 的地址;这样就组成了一个链表,变相的达到一个线程池的目的;当执行 ibtain()方法时,优先获取链表头的 Message 对象,边将可链表中可复用对象大小减一;
// Message 对象复用,重新放入到链表队列中。
void recycleUnchecked() {
    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++;
        }
    }
}

而当Message使用过后,则将对象部分数据重置,放入链表头部,可复用对象数目加一;
这样就达到了复用的目的;

Message的传递以及消费

以文章开头的经典示例为例:
1. handler 在主线程定义
2. 在子线程通过 该 handler 发送消息;
这里消息的定义有多种,但是最终 Message 对象中的 target 的值都指向当前的 handler 对象;

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 都指向当前的 handler 
msg.target = this;
if (mAsynchronous) {
    msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

这样,发送消息时的每一个消息都知道最终的消费者是谁。
之后调用native层的消息机制将消息放入到消息队列中;

public static void loop() {
    // 省略代码...
      for (;;) {
      // 从消息队列中获取消息对象
        Message msg = queue.next();
        if (msg == null) {
            return;
        }
        // ...
        try {
        // 分发,执行
            msg.target.dispatchMessage(msg);
            // ...
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        // ...
        msg.recycleUnchecked();
    }
}

上面的 Looper.looper()方法说明了,这是启动了死循环,不段的从线程的消息队列中读取消息;然后使用 target 所指向的 Handler 对象进行分发;最后调用 msg.recycleUnchecked() 方法回收掉已使用过的消息;