Android的消息机制

一.消息机制的概述

Android的消息机制主要指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。主要用于UI线程和子线程之间的交互。
 
一般情况下,出于安全的考虑,所有与UI控件的操作都要放在主线程及UI线程,而一些耗时操作应当放在子线程中。当在子线程中完成耗时操作并要对UI控件进行操作时,就要用Handler来控制了。
 
延伸:

  • 系统为何不允许在子线程中访问UI?
    因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预估的状态。
  • 为什么系统不对UI控件的访问加上锁机制呢?
    首先加上锁机制会让UI访问间的逻辑变得复杂,其次锁机制会降低UI访问的效率。

1.模型

消息机制主要包含:

方法解释
Message消息的载体:用于线程之间传递信息,在不同的线程间交换数据
MessageQueue消息队列:主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);每个线程中只有一个MessageQueue对象
Handler消息辅助类:主要功能向消息池发送各种消息事件
Looper每个线程中MessageQueue的管家,调用Looper的loop()方法之后,就会进入到一个无限循环中,每当发现MessageQueue中存在一条消息,就会将他取出,并传递到Handler的handleMessage()

2.流程概述

当创建一个Message对象,并通过header将这条消息发送出去时,这条消息就会被添加到消息队列中等待被处理,而looper则会一直尝试从MessageQueue中取出待处理的信息,最后分发回Header的handleMessage()方法中。由于Hander是在主线程创建的,所以此时的handerMessage()方法在主线程运行

在这里插入图片描述
解释:

  • Handler通过sendMessage()发送Message到MessageQueue队列;
  • Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理;
  • 经过dispatchMessage()后,交回给Handler的handleMessage()来进行相应地处理。

3.典型实例

1.在主线程使用Handler

private Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch(msg.what){
            	case 1:{
            		//更新ui等操作
            	}
            }
        }
    } ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread("Thread#1"){
            @Override
            public void run() {
                Message message = new Message();
                message.what = 1;
                handler.sendMessage(message);
            }
        }.start();
    }

2.子线程的使用

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();  
        mHandler = new Handler() {  
            public void handleMessage(Message msg) {
                
            }
        };
        Looper.loop(); 
    }
}

二.Android的消息机制分析

1.ThreadLocal

ThreadLocal是一个线程内部的数据存储类(Thread Local Storage,简称为TLS),通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
 
ThreadLocal在消息机制中的使用:Looper的作用域是线程并且不同线程有不同的Looper,通过ThreadLocal可以实现Looper在线程中的存取。
 
ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法

ThreadLocal的两个重要方法:

  • ThreadLocal.set(T value):将value存储到当前线程的TLS区域,源码如下:
public void set(T value) {
        Thread t = Thread.currentThread(); //获取当前线程
        ThreadLocalMap map = getMap(t);		//查找当前线程的本地储存区
        if (map != null)
            map.set(this, value);//保存数据value到当前线程this
        else
        //当线程本地存储区,尚未存储该线程相关信息时,
        //对象创建ThreadLocalMap
            createMap(t, value);
    }


// ThreadLocalMap.set
 private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

说白了,当前线程中存在一个Map变量,KEY是ThreadLocal,VALUE是你设置的值(用在消息机制中,就是当前线程的Looper)。

  • ThreadLocal.get( )获取当前线程TLS区域的数据,源码如下:
public T get() {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//查找当前线程的本地储存区
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;	//返回当前线程储存区中的数据
            }
        }
        return setInitialValue();
    }

ThreadLocalMap 内部是一个entry对象, 还是 K : V 形式存在的。它用的是当前所在线程的 ThreadLocal , 作为key 要隔离的那个变量作为value。 并且 这个entry 对象 继承了 弱引用类型 这个类。

ThreadLocalMap.Entry:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

可参考的博客
ThreadLocalMap的内存泄漏问题
<一>深入理解Threadlocal的实现原理
https://cloud.tencent.com/developer/article/1393135
 

2. MessageQueue

消息队列,但是它的内部实现并不是队列,实际上是通过一个单链表的数据结构来维护消息队列。

2.1 创建MessageQueue
MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
          //通过native方法初始化消息队列,其中mPtr是供native代码使用
        mPtr = nativeInit();
    }
2.2 next()

从消息队列里取出一条消息并将其从消息队列中移除。

Message next() {
     
        final long ptr = mPtr;
        if (ptr == 0) {	//当消息循环已经退出,则直接返回
            return null;
        }

        int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
			//阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
               
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //当消息的Handler为空时,则查询异步消息
                if (msg != null && msg.target == null) {
                 //当查询到异步消息,则立刻退出循环
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                      //当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 获取一条消息,并返回
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);					 
                        //设置消息的使用状态,即flags |= FLAG_IN_USE
                        msg.markInUse();
                        return msg;//成功地获取MessageQueue中的下一条即将要执行的消息
                    }
                } else {
                   //没有消息
                    nextPollTimeoutMillis = -1;
                }
              //消息正在退出,返回null
                if (mQuitting) {
                    dispose();
                    return null;
                }
               //当消息队列为空,或者是消息队列的第一个消息时
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                      //没有idle handlers 需要运行,则循环并等待。
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            //只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; //去掉handler的引用

                boolean keep = false;
                try {
                    keep = idler.queueIdle();//idle时执行的方法
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
             //重置idle handler个数为0,以保证不会再次重复运行
            pendingIdleHandlerCount = 0;
            //当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message.
            nextPollTimeoutMillis = 0;
        }
    }

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

2.3 enqueueMessage

往消息队列中插入一条消息。

boolean enqueueMessage(Message msg, long when) {
		// 每一个普通Message必须有一个target
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {	
                throw new IllegalStateException(msg + " This message is already in use.");
            }
            if (mQuitting) {//正在退出时,回收msg,加入到消息池
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                 //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
				//将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
            //消息队头存在barrier,并且同时Message是队列中最早的异步消息。
                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;
            }
             //消息没有退出,我们认为此时mPtr != 0
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

MessageQueue是按照消息触发时间的先后顺序排列的,队头的消息是将要初始化触发的消息。当有消息需要加入消息发生时,会从此头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。

3. Looper

Looper在消息机制中扮演这消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立即处理,负责会一直阻塞在那里。

2.1 prepare()
//可见sThreadLocal的get()和set()操作的类型都是Looper类型。
 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 
 public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
         //创建Looper对象,并保存到当前线程的TLS区域
        sThreadLocal.set(new Looper(quitAllowed));
    }

Looper.prepare()在每个线程只允许执行一次,该方法会创建Looper对象,再将Looper对象保存到当前线程TLS。
 
另外,与prepare()相近功能的,还有一个prepareMainLooper()方法,该方法主要在ActivityThread类中使用。

 public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

 
Looper还提供了一个getMainLooper方法,通过它可以在任何地方获取到主线程的Looper。

2.2 loop()

只有调用了loop()后,消息循环才会真正起作用

public static void loop() {
        final Looper me = myLooper();//获取TLS存储的Looper对象
     
        final MessageQueue queue = me.mQueue;
        
        Binder.clearCallingIdentity();
        //确保在权限检查时基于本地进程,而不是调用进程。
        final long ident = Binder.clearCallingIdentity();

        for (;;) {	//进入loop的主循环方法
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }
             //默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
                msg.target.dispatchMessage(msg);
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
            //恢复调用者信息
            final long newIdent = Binder.clearCallingIdentity();
            msg.recycleUnchecked();
        }
    }

loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回null。当Looper的quit方法被调用时,looper就会调用MeaaageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标为以退出状态时,它的next方法就会返回null。

2.3 quit()
public void quit() {
        mQueue.quit(false);		//消息移除
    }

 
    public void quitSafely() {
        mQueue.quit(true);		//安全地消息移除
    }

消息退出的方式:

  • 当safe = true时,只可移除尚未触发的所有消息,对于正在触发的消息并不可移除;
  • 当safe = flase时,可移除所有的消息

4. Handler

4.1 构造方法

所有的构造方法实际调用的都是这个方法

public Handler(@Nullable Callback callback, boolean async) {

        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "");
            }
        }
		//必须先执行Looper.prepare(),才能获取Looper对象,否则为null.
        mLooper = Looper.myLooper();//从当前线程的TLS中获取Looper对象
        if (mLooper == null) {
            throw new RuntimeException(
                "");
        }
        mQueue = mLooper.mQueue; //消息队列,来自Looper对象
        mCallback = callback;//回调方法
        mAsynchronous = async;//设置消息是否为异步处理方式
    }
4.2 sendMessage方法
 public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

 public boolean sendMessageAtTime(@NonNull 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);
    }

可以看到通过sendMessage发送的方法最终会进入到MessageQueue中,而MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就开始处理了,最终消息由Looper交给Handler处理。即Handler的dispatchMessage方法会调用,这时就进入了处理消息的阶段。

流程图片:
在这里插入图片描述

4.3 dispatchMessage方法
public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
          //当Message存在回调方法,回调msg.callback.run()方法;
            handleCallback(msg);
        } else {
            if (mCallback != null) {
             //当Handler存在Callback成员变量时,回调方法handleMessage();
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
             //Handler自身的回调方法handleMessage()
            handleMessage(msg);
        }
    }

发行消息流程:

  1. 当Message的某些方法不为空时,则某些方法msg.callback.run(),其中callBack数据类型为Runnable,否则进入步骤2;
  2. 当Handler的mCallback成员变量不为空时,则某些方法mCallback.handleMessage(msg),否则进入步骤3;
  3. 调用Handler本身的替代方法handleMessage(),该方法替换为空,Handler子类通过覆写该方法来完成具体的逻辑。

对于很多情况下,消息分发后的处理方法是第3种情况,即Handler.handleMessage(),一般地经常通过覆盖写该方法从而实现自己的业务逻辑。

5.主线程的消息循环

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

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

        Looper.prepareMainLooper();	 //创建主线程的Looper以及MessageQueue

      
      .....
        Looper.loop();		//开启主线程的消息循环。

      ....
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值