Android消息机制的分析

2019-05-11  本文已影响0人  dashingqi

Android 的消息机制主要涉及到这几个类 Handler,Looper,Message和MessageQueue

讲解下这几个类的作用以及之间的关系
先看个小demo
  public class MainActivity extends AppCompatActivity {

  private Button sendMessage;
  private Button post;
  private static TextView tvMessage;


  private static Handler mHandler = new Handler() {
      @Override
      public void handleMessage(Message msg) {
          super.handleMessage(msg);
          switch (msg.what) {
              case 1:
                  tvMessage.setText(msg.obj.toString());
                  break;
          }
      }
  };


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

  private void initView() {
      sendMessage = findViewById(R.id.sendMessage);
      post = findViewById(R.id.post);
      tvMessage = findViewById(R.id.tvMessage);

      sendMessage.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {

              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      //在子线程中进行消息的发送
                      Message msg = Message.obtain();
                      msg.what = 1;
                      msg.obj = "Hello World";
                      mHandler.sendMessage(msg);
                  }
              }).start();

          }
      });

      post.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      mHandler.post(new Runnable() {
                          @Override
                          public void run() {
                              //这也说明了 post更新UI只需在run中进行,不用在handleMessage中进行
                              tvMessage.setText("Hello World2");
                          }
                      });
                  }
              }).start();
          }
      });

  }
}
UI线程(主线程)

下面针对上面的两个问题,来一一解答

这里面着重分析这两个方法,第一个方法影响着Handler在子线程中的创建,第二个方法是进行消息的取出操作。

 //我们进入到Looper类中找到prepareMainLooper方法
 public static void prepareMainLooper() {
       //分析1.1 
       prepare(false);
       synchronized (Looper.class) {
           if (sMainLooper != null) {
               throw new IllegalStateException("The main Looper has already been prepared.");
           }
           sMainLooper = myLooper();
       }
   }

 //分析1.1
  private static void prepare(boolean quitAllowed) {
       //这里面的sThreadLocal 其实是 ThreadLocal sThreadLcoal = new ThreadLocal()  是sThreadLocal的一个实例
       // ThreadLocal:是一个线程的本地存储区,每个线程都有一个独立的本地存储区,各个线程的本地储存区是不能互相访问的。 
       // 分析1.1.1
       // 当在当前线程中去ThreadLocal中获取值,如果不为空那么抛出异常,意思就是一个线程中只能存在一个Looper对象。
       if (sThreadLocal.get() != null) {
           throw new RuntimeException("Only one Looper may be created per thread");
       }
       //如果ThreadLcoal中没有存储Looper,那么就创建一个新的Looper存储在ThreadLocal中。
       sThreadLocal.set(new Looper(quitAllowed));
   }
// 在Looper的构造方法中
 private Looper(boolean quitAllowed) {
       //1. 创建了一个MessageQueue的对象
       mQueue = new MessageQueue(quitAllowed);
       //获取到当前的线程。
       mThread = Thread.currentThread();
   }

 //总结说来:在Android程序启动的时候,默认会在UI线程中创建一个Looper对象,并且看源代码显示,一个线程中只能存在一个Looper对象,如果有第二个会抛出异常。

//分析1.1.1
 // ThreadLocal主要是将Looper对象存储在当前线程的本地存储区中,下面来看下ThreadLocal中的主要两个方法。
 
//本地存储区的存储数据方法
public void set(T value) {
       // 获取到当前的线程
       Thread t = Thread.currentThread();
       //获取到线程的本地存储区
       ThreadLocalMap map = getMap(t);
       //本地存储区已经初始化了,经将值存储进本地存储区中
       if (map != null)
           map.set(this, value);
       else
           // 创建一个本地存储区,将数据存储到本地存储区中。
           createMap(t, value);
   }
 //取数据方法
 
public T get() {
       //获取到当前的线程池
       Thread t = Thread.currentThread();
       //查找当前线程的本地存储区
       ThreadLocalMap map = getMap(t);
       //线程的本地存储区有数据时
       if (map != null) {
           //获取到本地存储区存着的数据
           ThreadLocalMap.Entry e = map.getEntry(this);
           if (e != null)
               return (T)e.value;
       }
       return setInitialValue();
   }
   private T setInitialValue() {
       //初始化值 为null
       T value = initialValue();
       //获取到当前的线程
       Thread t = Thread.currentThread();
       //根据线程查找到本地存储区
       ThreadLocalMap map = getMap(t);
       //当前本地存储区不为空的时候,将数据存储到本地存储区中,
       if (map != null)
           map.set(this, value);
       else
           //本地存储区为空的时候,初始化一个本地存储区,value为null
           createMap(t, value);
       return value;
   }

//在Android的消息机制中ThreadLocal操作的类型都是Looper类型。
  //上面的demo中,我们在主线程中,创建了一个Handler,那么我们看下Handler的构造方法的代码,看看初始化的时候Handler都需要干什么?

public Handler(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, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
        //这个地方,调用了Looper的myLooper方法,获取到当前线程存本地存储区中储的Looper对象。能在不同线程存储Looper对象,得益于ThreadLocal这个类。
        mLooper = Looper.myLooper();
        //如果mLooper为空,就会抛出异常
        //这个地方说下:开启一个子线程,是不会默认创建Looper对象,所以在子线程中创建Handler之前需要在子线程中创建一个Looper对象,这样Handler的创建才会成功。
        //可以通过Looper的preapre()或者getMainLooper()方法在子线程中创建一个Looper对象,存储在当前线程的ThreadLcoal中。
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //这里把Looper的MessageQueue对象和Handler中的联系在一起了。
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
  //在Handler的构造方法中,我们可以看到,获取了当前线程的Looper对象,如果为空,说明Looper没有创建,没有创建就会报错。
  //那么Handler的创建失败了,当前线程的本地存储区中有没有Looper对象,直接影响着Handler的创建。
 //source
  public static void loop() {
        //获取到本地存储区中存储的Looper对象
        final Looper me = myLooper();
        if (me == null) {
            //主线程会默认创建一个Looper对象,如果在子线程中,没有调用prepare就会抛出异常。
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //将Looper中的mQueue赋给queue变量
        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();

        //开启无限循环,取消息
        //为什么不使用 while(true)? 因为可以通过一些黑科技,来修改这个条件(比如通过反射)
        for (;;) {
            //当消息队列的next方法返回为null的时候才会退出这个循环
            //next方法什么时候退回的呢 ?当Looper调用了quit或者quitSafely 来通知消息队列退出。这时next方法就会返回null。
            //next是一个阻塞的方法,当MessageQueue中没有消息时,next方法就处于阻塞状态,那么Looper也处于阻塞的状态,直到消息队列中有消息
            //next方法取出消息,Looper中就会分发处理这条消息。
            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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                //调用handler中的dispatchMessage()方法
                //dispatchMessage方法是在创建Handler时使用的Looper中调用
                //拿主线程来说吧,Handler创建是在UI线程,使用的Looper是存储在UI线程的本地存储区中,其中Looper对象的创建是在UI线程中,那么MessgeQueue也是在UI线程中创建啦,那么存储的Message也是在ui线程的MessageQueue中了,在Looper的loop方法中msg.target.dispatchMessage,其中的Message是存在UI线程的MessageQueue中的,所以保存的target(handler)变量也存在UI线程中,所以Handler的dispatchMessage方法是运行在UI线程中的。说白了就是,Looper.loop在那个线程中调用, handleMessage就运行在那个线程中。
                //在UI线程中执行,那么handleMessage中就是运行在UI线程中。
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            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.recycleUnchecked();
        }
    }

上述我们通过ActivityThread中的main方法延伸上述的分析,主要是Looper的创建和存储本地存储区中,然后Looper调用loop方法,该方法中是一个死循环,通过next方法不停的从MessageQueue中取出消息,如果没有消息next方法就会阻塞,这时Looper就处于阻塞状态,当MessageQueue中有消息的时候,这时会调用next方法来获取到消息,然后通过dispatchMessage方法进行消息的分发。该方法是Handler中的。

那么一个消息是怎么来的呢?怎么进入MessageQueue中的呢?

在Handler有一系列的send和一系列的post方法,来进行消息的发送,其中一系列的post方法会最终调用send系列的方法。我们知道最基本的一个消息发送方法是sendMessage(),那么我们来看看这个方法中究竟怎么运行的。

  //Handler的sendMessage方法。
  public final boolean sendMessage(Message msg)
    {
          //内部调用了sendMessageDelayed()方法,
          return sendMessageDelayed(msg, 0);
    }

  public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        //内部调用了sendMessageAtTime
        //其中 SystemClock.uptimeMIllis()是代表着系统的开机时间到运行这段代码的时间,
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //这个mQueue 是Looper中的MessageQueue
        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 就是发送消息的Handler
        //这里将handler对象赋值给msg.target
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //将消息存储到MessageQueue队列中(先进先出)
        // MesageQueue调用了enqueueMessage 将Message存储到消息队列中
        //其中这个时间 uptimeMills 代表消息如入队列的顺序时间。
        return queue.enqueueMessage(msg, uptimeMillis);
    }

当发送一条消息到消息队列中的时候,通过next方法就取出个消息,在Looper的loop方法中通过调用
Handler中的dispatchMessage方法来进行消息的分发。

对于MessageQueue来说 主要体现在Mesasge消息的存储和取出的操作上,MessageQueue说是一个队列,其实底层是一个单链表结构,单链表对于增加和查询非常的友好,很是方便。消息的存储就是将消息存储到消息队列中,消息的取出就是将消息从消息队列中取出,然后将消息从消息队列中移除,所以消息的取出的同时伴随着消息的删除。

  //对于MessageQueue,存入消息是调用了enqueueMessage方法,取出消息是调用了next方法。
  //enqueueMessage方法。
   boolean enqueueMessage(Message msg, long when) {
      //每个Message中都会定义一个Tartget,这个Terget类型就是Handler
      // 
      if (msg.target == null) {
          throw new IllegalArgumentException("Message must have a target.");
      }
      if (msg.isInUse()) {
          throw new IllegalStateException(msg + " This message is already in use.");
      }

      synchronized (this) {
          if (mQuitting) {
              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) {
              // 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;
  }

通过源码我们可以看出,当线程中没有处理它的Handler或者当前有正在处理的相同的消息,Meesage都不能进入到队列中。当线程处于死亡的状态下的时候,Message对象将会被回收,然后退出enqueueMessage方法,否则的话就进行单链表的插入操作,将消息放进消息队列中。

  //next方法
   Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        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;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

next方法中是一个死循环操作,进行着消息的不间断的取出操作。如果当前消息池中没有消息了,next方法就会处于阻塞的状态,如果消息池中出现消息了,会立刻将消息取出,并将该消息池的这个消息给移除。

到此我们消息机制的分析就完事了,接下来我用一张图来描述一下消息机制中Handler ,Message ,MessageQueue 和Looper的运作流程以及各个类之间的层级关系(这里只做Java层面的分析,native层间的日后在做分析)。

Android消息机制运行图.png

这里总结几个关于Android消息机制的问题吧。

  1. Handler引起的内存泄漏和解决的办法?
    • 内存泄漏的引起:如果我们通过handler.postDelay()发送一个消息,此时消息没有得到执行Activity就finish了,此时Message持有了Handler实例(Target),而Handler又持有了Activity的实例(内部类默认持有外部类的引用),那么就会导致GC不能回收这个Activity,就会发生了内存泄漏。
  1. 为什么在主线程中可以直接使用Handler,而不需要创建Looper对象?
  1. 主线程中的Looper不让退出!
  1. 创建Message的最佳方式
  1. 在子线程中用Toast弹出提示?
  1. Handler中的Callback 有什么用呢?
  1. Android中的Looper.loop()是一个无限循环的方法,为什么没有导致CPU占用率飙升呢?
  1. Handler 创建的所在的线程决定handleMessage或者 post(new Runnable(){run()})吗?
上一篇 下一篇

猜你喜欢

热点阅读