Android中的消息机制:Handler、Message

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() 方法回收掉已使用过的消息;

猜你喜欢

转载自blog.csdn.net/qq_16251833/article/details/81358727