安卓学习笔记之Android消息机制

1、android的消息机制概述

1.1 消息机制概述

android的消息机制是通过Handler的运行机制来实现的,handler需配合底层的Looper和MessageQueue,来完成消息的发送、分发与处理等工作流程。一般使用Handler的消息机制来完成线程间通信。

大抵流程总结如下:

子线程通过handler发送Message,并将消息插入消息队列,Looper轮循器在检测到有新消息后,Looper轮循器被唤醒并开始遍历MessageQueue消息队列,轮询取出消息并调用handler来分发消息,最终通过handler的消息分发机制来完成消息的分发,并处理相关的消息。

1.2 几个名词的意义

  • Looper 消息轮循,轮循取出消息,并交由handler分发

  • MessageQueue 消息队列,用来管理消息,底层由链表实现

  • Message 消息对象

  • Handler 消息发送、分发、处理

1.3 Message的获取与发送的几种方法

发送消息

  • 1、handler.sendMessage(msg);

  • 2、msg.setTarget(target); //设置发送给哪个Handler
    msg.sendToTarget();

获取消息

  • 1、handler.obtainMessage(); //obtainMessage()方法实际调用了Message.obtain方法

  • 2、Message msg = Message.obtain(handler);// obtain(handler)调用了Message.obtain()方法

    Message msg = Message.obtain();
    msg.setTarget(target)

  • 3、Message message = new Message();

Message.obtain()方法如下,消息池有消息则直接返回消息,否则创建一个消息返回

     /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     * 消息池有消息则直接返回消息
     * 否则创建一个消息返回
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;  // 取出消息池头部消息对象
                sPool = m.next;  // 将消息池头指向后一位
                m.next = null;  //  将取出的消息与消息池断开连接关系
                sPoolSize--;   //  消息池大小减一
                return m;
            }
        }
        return new Message();
    }

1.4 handler机制的运行大致流程图

注意:为了让图示更清楚,所以将Looper与MessageQueue单独出来,实际上Looper是运行于Handler所在线程(通常情况下为主线程)。

这里写图片描述


2、 Handler消息机制运行流程

2.1 Looper初始化与开启轮循

ActivityThread(UI线程)的main方法中初始化Looper ,并开启消息轮循

看ActivityThread的main方法:

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


...
   // 初始化Looper
       Looper.prepareMainLooper();

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

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

    ...

    // 开启轮循
       Looper.loop();

       throw new RuntimeException("Main thread loop unexpectedly exited");
   }

Looper初始化

  • 1、 prepareMainLooper方法
public static void prepareMainLooper() {
      prepare(false);
      synchronized (Looper.class) {
          if (sMainLooper != null) {
              throw new IllegalStateException("The main Looper has already been prepared.");
          }
          sMainLooper = myLooper();
      }
  }
  • 2、prepare(boolean quitAllowed),创建Looper对象,并将它保存在ThreadLocal对象中。参数quitAllowed表示是否允许可以退出消息轮循,UI线程默认不允许退出消息循环(通过prepare(false);可以看出)。

注意:一个线程只能创建一个Looper对象。

  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)); 
   }
  • 3、Looper的构造方法,初始化了内部的MessageQueue,并将quitAllowed传参给它
  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed); // 确定Queue可不可以退出
        mThread = Thread.currentThread();
  }
  • 4、 myLooper() , 从sThreadLocal中获取Looper对象
     public static Looper myLooper() {
        return sThreadLocal.get();
    }

Looper开启轮循

loop() 方法开启轮循,等待处理消息

  • 1、通过queue.next()取出消息
  • 2、通过queue.enqueueMessage向消息队列插入一条消息

loop方法作了如下几件事

  • 1.取出消息,若无消息则阻塞,queue.next(); // might block

  • 2.分发消息 ,通过msg.target.dispatchMessage(msg);来完成,实际上调用了目标handler的dispatchMessage方法

  • 3.回收消息 ,通过 msg.recycle();来完成

  • 4.特殊情况退出轮循

可以看到有这样一段,判断msg是否为null,正常情况不会为null,只有当我们调用了Looper的quit方法时才会使queue.next()取出的消息为null(内部调用了消息队列的quit方法, mQueue.quit(bool);),同时要注意,只有当quitAllowed为true时才能退出轮循(quitAllowed上面提到),UI线程默认不允许退出消息轮循

if (msg == null) {
  // No message indicates that the message queue is quitting.
   return;
}

loop方法如下

/**
    * Run the message queue in this thread. Be sure to call
    * {@link #quit()} to end the 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;

       // Make sure the identity of this thread is that of the local process,
       // and keep track of what that identity token actually is.
       Binder.clearCallingIdentity();
       final long ident = Binder.clearCallingIdentity();
// 取出消息,若无消息则阻塞
       for (;;) {
           Message msg = queue.next(); // might block
           if (msg == null) {
               // No message indicates that the message queue is quitting.
               return;
           }

           // This must be in a local variable, in case a UI event sets the logger
           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);
           }

           // Make sure that during the course of dispatching the
           // identity of the thread wasn't corrupted.
           final long newIdent = Binder.clearCallingIdentity();
           if (ident != newIdent) {
               Log.wtf(TAG, "Thread identity changed from 0x"
                       + Long.toHexString(ident) + " to 0x"
                       + Long.toHexString(newIdent) + " while dispatching to "
                       + msg.target.getClass().getName() + " "
                       + msg.callback + " what=" + msg.what);
           }
// 回收消息
           msg.recycle();
       }
   }

2.2 Handler的构造方法

handler对象必须在有Looper的线程中创建(除非我们手动创建Looper对象)
做了如下几件事:

  • 1、保存mLooper对象引用,
  • 2、初始化mQueue对象,用于为添加Message做准备
  • 3、初始化mCallback回调
  • 4、设置消息的同异步

Handler构造方法如下:

    /**
     * @param callback The callback interface in which to handle messages, or null.
     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
     *
     * @hide
     */
    public Handler(Callback callback, boolean async) {
       ...
    // 获取Looper对象 
        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;
    }

2.3 消息的发送与插入消息队列

  • 1、发送消息的几种方法最终都会调用sendMessageDelayed方法
public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
  • 2、 sendMessageDelayed方法又调用了sendMessageAtTime方法
    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);
    }
  • 3、在sendMessageAtTime中调用enqueueMessage方法插入消息
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        // 设置要发送给那个handler对象,设置当前的handler对象为发送的目标
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 插入消息
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  • 4、在enqueueMessage方法中通过MessageQueue的enqueueMessage向消息队列插入消息,从下面可知,消息队列是使用链表来实现的。
  boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {
            throw new AndroidRuntimeException(msg + " This message is already in use.");
        }
        if (msg.target == null) {
            throw new AndroidRuntimeException("Message must have a target.");
        }

        synchronized (this) {
            if (mQuitting) {
                RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            }

            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

2.4 handler消息分发机制

Looper通过loop方法取出消息,并交由Handler来分发消息,即msg.target.dispatchMessage(msg);其中msg.target即Handler对象

/**
   * Handle system messages here.
   */
  public void dispatchMessage(Message msg) {
      if (msg.callback != null) {
          handleCallback(msg);
      } else {
          if (mCallback != null) {
              if (mCallback.handleMessage(msg)) {
                  return;
              }
          }
          handleMessage(msg);
      }
  }
  • 1、首先会判断msg.callback是否为空,msg.callback即是handler.post的那个Runnable对象,不为空则调用 handleCallback(msg);

注意:handler.post()并没有开启一个新线程,Runnable的run方法运行于Handler所在线程 ,因此主线程的handler的post方法中不推荐做耗时操作


    private static void handleCallback(Message message) {
        message.callback.run();
    }
  • 2、当msg.callback为空时,要判断mCallback是否为空,mCallback是一个回调接口
 public interface Callback {
        public boolean handleMessage(Message msg);
    }

Handler有如下构造

     public Handler(Callback callback) {
            this(callback, false);
        }

可以用来构造Handler,如下

    Handler h=new Handler(new Handler.Callback() {

            @Override
            public boolean handleMessage(Message msg) {
                // TODO Auto-generated method stub
                return false;
            }
        });
  • 3、mCallback不为空时,则调用mCallback.handleMessage(msg),即调用了Callback中的handleMessage方法,如下handleMessage1方法

  • 4、mCallback为空时,则调用了handler的handleMessage方法,即调用了handleMessage2方法

    Handler h = new Handler(new Handler.Callback() {

        // handleMessage1
            @Override
            public boolean handleMessage(Message msg) {
                // TODO Auto-generated method stub
                return false;
            }
        }) {

        // handleMessage2
            @Override
            public void handleMessage(Message msg) {
                // TODO Auto-generated method stub
                super.handleMessage(msg);
            }

        };

3、runOnUiThread原理

public final void runOnUiThread(Runnable action) {
      if (Thread.currentThread() != mUiThread) {
          mHandler.post(action);
      } else {
          action.run();
      }
  }
  • 可以发现,当是UI线程的时候,直接调用Runnable 的run方法,它运行于主线程。

  • 当不为主线程时,需要通过handler的post方法发送消息到主线程消息队列,如下流程 :

1 通过 mHandler.post方法发送一个消息

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

2 通过getPostMessage方法初始化一个消息对象,并给它的callback赋值

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

3 Handler在dispatchMessage中处理消息,此时msg.callback不为空,msg.callback即是handler.post的那个Runnable对象,此时handleCallback(msg);会被调用。最终导致Runnable的run方法被调用。

View的post方法原理类似。

4、几个思考

1、handler.post(Runnable r)有开启一个新线程吗?

追踪源码发现,最终是通过 message.callback.run();来开启任务的,并没有开启一个新线程,Runnable的run方法运行于Handler所在线程

2、子线程是如何切换到主线程的?

通过Handler来切换线程。在子线程中将消息发送到MessageQueue,在Looper轮循消息时,调用了msg.target.dispatchMessage(msg);让Handler来分发处理消息,此时已从子线程切换到主线程了。

3、Looper的loop方法为什么不会卡死主线程?
loop方法会阻塞主线程但不会卡死主线程。在源码中我们知道,在 Looper.loop();之后又这么一句

throw new RuntimeException(“Main thread loop unexpectedly exited”);
我们可以知道如果loop结束运行则会抛出此异常。因此loop方法正常是不会结束的。它配合ActivityThread内部Handler(即mH),通过不停的消息轮循与事件处理来完成与用户的交互。

4、为什么在子线程中创建Handler时会出错?
由上面我们分析源码可知,在构造Handler时会初始化Looper对象,而子线程通常是没有Looper对象的(如果我们并没有自己创建Looper对象的话),会抛出如下异常

09-07 19:55:44.050: E/AndroidRuntime(1870): java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()

为了解决这个错误,我们可以手动创建Looper对象,并开启消息轮循(以下仅仅简单示例,可以做更多拓展)

new Thread(){
    public void run() {
        Looper.prepare();
        Handler h=new Handler(){
            public void handleMessage(android.os.Message msg) {};
        };
        Looper.loop();
    };
}.start();

这样就解决了问题了,但同时要注意,当我们不需要使用此子线程的Handler时,我们应该手动停止Looper以回收资源,通过Looper的quit(立即停止)或quitSafely(当消息轮循完时)即可停止消息轮循。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值