Android的消息机制(一)

简介

Android开发规范了不能在子线程中访问UI控件,否则就会触发CalledFromWrongThreadException异常,因为Android的UI控件在效率和使用简单方面考虑没有加锁保护,所以若在多线程中并发访问可能会导致UI错乱。一般地,我们在实际开发过程中也知道,要想在子线程中去更新UI的话,就要使用Handler的sendMessage方法,然后在UI线程中接收此消息,然后进行相应的处理。所以,我们今天所说的Android 的消息机制,其实就是指Handler的运行机制。

要了解Handler运行机制,就要先认识几个相关的概念,它们是:MessageQueue(消息队列)、Looper(消息循环) 和 ThreadLocal(线程数据):

MessageQueue

是一个单链表数据结构 ,用于存储消息列表。因为它要常做插件和删作操作,所以设计上用单链表比较有优势。

Looper

是一个死循环,它会一直地去查找是否有新消息。线程默认是没有Looper的,只能向存在Looper的线程发消息,否则会抛RuntimeException异常。所以主线程为什么能接收消息,因为它在被创建时就初始化了Looper。

ThreadLocal

是可以在每个线程中存储数据,通过它就可以获得线程的Looper(如果存在Looper的话,像主线程)。

流程概述

两个线程的通信流程:线程1通过Handler的post或sendMessage方法将一个消息发出去,其实post只是sendMessage的封装,它们最终都会调用到内部的MessageQueue的enqueueMessage方法将这个消息放进线程2MessageQueue消息队列中,然后Looper在循环过程中发现新消息就会处理它。如果开始使用了post的话,那post传入的Runnable回调就会被调用,在线程2中执行Runnable中run方法的逻辑,如果开始使用了sendMessage方法的话,那么线程2中Handler的handleMessage方法就会被调用。这个过程大概如下图:

使用Demo

public class MainActivity extends AppCompatActivity {

    private  final static int MSG_SCAN_START = 0;
    private  final static int MSG_SCAN_END = 1;

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

        test();
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int what = msg.what;
            Object obj = msg.obj;
            switch (what) {
                case MSG_SCAN_START:
                    Log.e("zyx", "OnScanStart");
                    break;
                case MSG_SCAN_END:
                    boolean result = (boolean)obj;
                    Log.e("zyx", "OnScanEnd:" + result);
                    break;
            }
        }
    };

    private void test() {

        new Thread(new Runnable() {
            @Override
            public void run() {

                mHandler.sendEmptyMessage(MSG_SCAN_START);

                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    Message msg = mHandler.obtainMessage();
                    msg.what = MSG_SCAN_END;
                    msg.obj = true;
                    mHandler.sendMessage(msg); 
                }
            }
        }).start();
    }
}

原理解说

ThreadLocal(线程数据)的工作原理

ThreadLocal是一件线程内部的数据存储类,通过它可以在指定的线程中存储数据。一般来说,当某些数据是以线程为作用域并且不同数据具有不同的数据副本时就可以考虑采用ThreadLoad。

示例

public class MainActivity extends AppCompatActivity {

    private ThreadLocal<String> mThreadLocal= new ThreadLocal<>();

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

        test();
    }

    private void test() {
        mThreadLocal.set(Thread.currentThread().getName());
        Log.d("zyx", "[Thread Main] mThreadLocal = " + mThreadLocal.get());

        new Thread("Thread1") {
            @Override
            public void run() {
                mThreadLocal.set(Thread.currentThread().getName());
                Log.d("zyx", "[Thread1] mThreadLocal = " + mThreadLocal.get());
            }

        }.start();

        new Thread("Thread2") {
            @Override
            public void run() {
                Log.d("zyx", "[Thread2] mThreadLocal = " + mThreadLocal.get());
            }

        }.start();
    }
}

运行结果:

D/zyx: [Thread Main] mThreadLocal = main

D/zyx: [Thread1] mThreadLocal = Thread1

D/zyx: [Thread2] mThreadLocal = null

原理

ThreadLocal是一个泛型类,它的定义是:public class ThreadLocal<T>。

set方法源码如下

public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}
……
Values values(Thread current) {
    return current.localValues;
}

说明:

1. 首先通过values方法获取当前线程中的ThreadLocal数据,此份数据是一个数组:private Object[] table。在Thread类内有一个成员(ThreadLocal.Values  localValues)专门存储线程ThreadLocal的数据。

2. 如果values的值为null,那么就初始化再将value值进行存储。

get方法源码如下:

public T get() {
    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;
        intindex = hash & values.mask;
        if (this.reference== table[index]) {
            return (T) table[index + 1];
        }
    } else {
        values = initializeValues(currentThread);
    }
    return (T) values.getAfterMiss(this);
}

说明:

如果localValue对象不为null,那就取出它的table数组并找出ThreadLocal的reference对象在table数组中的位置,然后table数组中的下一个位置所存储的数据就是ThreadLocal的值。

小结

从set和get方法可以看出,ThreadLocal所操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set和get方法是各自线程的内部。所以说,主线程在创建时就给自己添加了一个Looper对象存放在自己的数据数组里。

MessageQueue(消息队列)的工作原理

MessageQueue主要包含两个操作:插入 和 读取。它们对应的方法分别是:enqueueMessage(往消息队列中插入一条消息)和 next (从消息队列中取出一条消息并将其从消息队列中移除)。前面讲过,MessageQueue是一个单链表的数据结构,因为它需要常插入和删除,所以设计方面使用单链表会比较有优势。

源码就不在此详细贴出,大概理解为下面即可:

next方法是一个死循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,next方法会返回这条消息并将其从单链表中移除。

Looper(消息循环)的工作原理

Looper就是消息循环,它会不停地从MessageQueue(消息队列)中查看是否有新消息,如果有就会立刻处理,否则就一直阻塞在那里。

从它的构造方法中会创建一个MessageQueue,然后将当前线程的对象保存起来,如下所示:

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

Handler的工作原理需要Looper。通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。如果线程中没有Looper而使用Handle发消息就会报错。所以,我们可以去查阅主线程:ActivityThread中的Main方法,该方法下你会发现有:Looper.prepareMainLooper() 和 Looper.loop()两行代码。其中,prepareMainLooper方法是prepare的封装。Looper的创建示例如:

class LooperThread extends Thread {

    public Handler mHandler;

    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // TODO 这里处理消息
            }
        };
        Looper.loop();
    }
}

Handle的工作原理

Handler的工作主要包含消息的发送和接收过程。消息的发送可以通过post和send的一系列方法来实现,post方法最终也是通过send方法来完成。像sendMessage方法最终会调到enqueueMessage方法去,而enqueueMessage方法内部就是调用了MessageQueue对象的enqueueMessage方法。过程可查阅源码,如下所示:

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(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(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

在这之后,MessageQueue对象的next方法就会返回这条消息给Looper,Looper收到消息后就开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这里Handlder就进入了处理消息的阶段。dispatchMessage方法的实现源码如下所示:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback!= null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

说明:

1. Message里有一个callback对象,实际上就是一个Runnable对象,也就是Handler的post方法所传递的Runnable参数,可以查阅Handler的post方法便知。

2. mCallback是一个接口,也就是我们在new Handler中构造函数中传入的回调。

3. 若mCallback为空,最后就交由handleMessage方法来处理,该方法是由Handler的子类来重写。

总结

就是通过这样:MesageQueue、Looper 和 Handler合作工作,也对应上面“流程概述”中所说的一样,整个消息机制就完成了。

有趣例子

看见过网上有大神做过一个形像有趣的比喻,大概是这样:
在我们出行坐火车或飞机时会先经过安检,然而,安检的机器通电就是Looper.prepare,开始运作是Looper.loop,这时正在拿着行李排队过安检的队伍就是消息队列MessageQueue,在行李过机时,机器自动检查行李是否存在违禁器就是Handler.handleMessage。假如,队伍中存在有人的行李东西比较复杂,假设就是MessageQueue里有Message_A消息,在机器循环检查Looper过程中检查到Message_A有问题,时间比较长,导致整个过程停滞,后面的乘客等的时间较长而不满就是ANR...

 

——本文部分内容参考自《Android开发艺术探索》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值