Android开发艺术探索(十)

Andrid的消息机制

消息机制概述

Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。
Handler的主要作用是将一个任务切换到某个指定的线程中去执行。

Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,程序就会排除异常。ViewRootImpl对UI操作做了验证,这个验证工作是由ViewRootImpl的checkThread方法来完成的。

 void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

Android系统不允许在子线程中访问UI?

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

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

1、加上锁机制会阻塞某些线程的执行
2、锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
鉴于这两个缺点,最简单高效的方法就是采用单线程模型来处理UI操作,这个时候我们只需要使用Handler切换UI访问的执行线程即可。

Android的消息机制分析

ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,存储数据以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
我们来做一个例子:首先定义一个ThreadLocal对象,这里选择Boolean类型的。如下:

private ThreadLocal<Boolean> mBooleanThreadLocal=new ThreadLocal<>();

然后分别再主线程、子线程1、子线程2中设置和访问它的值,代码如下:

mBooleanThreadLocal.set(true);
        Log.e(TAG,"{Thread#main}mBooleanThreadLocal="+mBooleanThreadLocal.get());

        /**thread1*/
        new Thread(){
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                Log.e(TAG,"{Thread#1}mBooleanThreadLocal="+mBooleanThreadLocal.get());
            }
        }.start();
        /**thread2*/
        new Thread(){
            @Override
            public void run() {
                Log.e(TAG,"{Thread#2}mBooleanThreadLocal="+mBooleanThreadLocal.get());
            }
        }.start();

运行结果

我们来分析一下ThreadLocal的内部实现:

/**泛型类*/
public class ThreadLocal<T> {
/**get方法获取值*/
  public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }
        return (T) values.getAfterMiss(this);
    }
/**set方法设置值*/
    public void set(T value) {
    //获取当前运行的线程
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
}

从ThreadLocal的get和set方法中可以看到他们所操作的对象都是当前线程的localValues对象的table数组,因此在不同的线程中访问同一个ThreadLocal的set和get方法,他们对ThreadLocal所做的读/写操作仅限于个子线程的内部!所以ThreadLocal可以在多个线程中互不干扰的存储和修改数据。

消息队列的工作原理
消息队列在Android中指的是MessageQueue,MessageQueue包含两个操作:插入和读取。读取操作本身会伴随着删除操作。

插入的方法为:enqueueMessage,作用是往消息队列中插入一条消息。
读取的方法为:next,作用是从消息队列中读取出一条消息并将其从消息队列中移除。
MessageQueue内部实现是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。
MessageQueue的enqueueMessage方法:

  boolean enqueueMessage(Message msg, long when) {
       ....
        synchronized (this) {
         ....
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                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;
            }

            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

MessageQueue的next方法:

   Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                ....
            }
         ....
        }
    }

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

Looper的工作原理
Looper在Android的消息机制中扮演着循环的角色,具体来说就是它会不停的从MessageQueue中查看是否有新消息,如果有新消息就会立即处理,否则就一直阻塞在哪里。

Looper的构造方法:

/**在构造方法中会创建一个MessageQueue消息队列*/
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

通过Looper.prepare()为当前线程创建Looper对象,然后通过Looper.loop();开启消息循环。

  new Thread(){
            @Override
            public void run() {
                Looper.prepare();
                Handler handler=new Handler();
                Looper.loop();
            }
        }.start();

Looper中还提供了一个prepareMainLooper方法,这个方法主要是给主线程也就是ActivityThread创建Looper使用的,其本质也是通过prepare方法来实现的。

同样Looper也是可以退出的:Looper提供了quit和quitSafely来退出一个Looper,他们的区别:

quit会直接退出Looper
quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全退出。

Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false,同样在不需要Looper时,应该调用quit方法来终止消息循环,否则这个子线程会一直处于等待的状态。

Looper的loop方法:

      public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        /**这里一个死循环*/
        for (;;) {
            /**通过MessageQueue的next方法获取到消息*/
            Message msg = queue.next(); // might block
            if (msg == null) {
                /**如果返回的消息是null,则跳出循环*/
                return;
            }
            /**如果消息不为空,则进行消息的处理*/
            msg.target.dispatchMessage(msg);
            final long newIdent = Binder.clearCallingIdentity();

            msg.recycleUnchecked();
        }
    }

Handler的工作原理
Handler的工作主要包含消息的发送和接收过程,消息的发送可以通过post的一系列方法以及send的一系列方法来实现,post的一系列方法最终是通过send的一系列方法来实现的。
我们来看一下Handler的sendMessage方法:

/**调用了sendMessageDelayed方法*/
    public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
    }
    /**这里又调用了sendMessageAtTime方法*/
    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;
        }
        /**这里又调用了enqueueMessage方法*/
        return enqueueMessage(queue, msg, uptimeMillis);
    }
   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        /**这里调用了MessageQueue的enqueueMessage方法*/
        return queue.enqueueMessage(msg, uptimeMillis);
    }

如图流程:
发消息流程

然后我们再来看处理消息:Looper对象将消息返回给Handler,Handler使用dispatchMessage去处理消息。

    public void dispatchMessage(Message msg) {
    //检查Messge的callback是否为null,不为null就通过handleCallback来处理消息
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
        /**然后再这里判断CallBack是否为空,不为null就调用mCallback的handlerMessage来处理消息*/
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

这里呢:msg.callback()是一个Runnable对象。

 private static void handleCallback(Message message) {
        message.callback.run();
    }

mCallBack是一个接口:

   public interface Callback {
        public boolean handleMessage(Message msg);
    }

通过Callback 可以采用如下方式创建Handler对象:
Handler handler=new Handler(callback);
通过CallBack创建一个Handler的实例,并且不需要派生Handler的子类。

Handler还有一个特殊的构造方法,通过Looper来构造Handler来构造。当looper为空时,就会抛出异常。这也解释了在没有Looper的子线程中创建Handler会引发程序异常的原因。

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

主线程的消息循环

Android的主线程就是ActivityThread,主线程的入口为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()俩开启主线程的消息循环。

   public static void main(String[] args) {
       ....

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    private class H extends Handler {
        public static final int LAUNCH_ACTIVITY         = 100;
        public static final int PAUSE_ACTIVITY          = 101;
        public static final int PAUSE_ACTIVITY_FINISHING= 102;
        public static final int STOP_ACTIVITY_SHOW      = 103;
        public static final int STOP_ACTIVITY_HIDE      = 104;
        public static final int SHOW_WINDOW             = 105;
    ......
    }

ActivityThread通过Application和AMS进行进程间通讯,AMS以进程间通讯方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发消息,H收到消息后会将ApplicationThread中的逻辑切换到ACtivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值