• 安卓中的消息循环机制Handler及Looper详解


    我们知道安卓中的UI线程不是线程安全的,我们不能在UI线程中进行耗时操作,通常我们的做法是开启一个子线程在子线程中处理耗时操作,但是安卓规定不允许在子线程中进行UI的更新操作,通常我们会通过Handler机制来完成该功能,即当子线程中耗时操作完成后,在子线程中通过Handler向主线程发送消息,在主线程中的Handler的handleMessage方法中处理接受到的消息。这就是安卓中的消息机制,安卓中的消息机制主要是指Handler的运行机制,但是Handler的运行需要底层的MessageQueue和Looper机制的支撑。

    下面我们来详解讲解安卓中的消息循环机制,即Handler,MessageQueue,Looper机制。注意本博客属于安卓进阶内容,所以一些基础性的东西如果看官不懂情请自行百度解决。

    首先我们来看一个标准的使用Handler机制的代码示范例子:

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



    这段代码大家肯定都不陌生,因为这是安卓中使用Handler机制的一个最标准的代码示范,也许看官可能会说我在使用Handler的时候没使用Looper.prepare()与Looper.loop()

    语句,那时因为我们使用Handler通常是在UI线程中,而UI线程已经默认为我们做好了这些准备工作,至于这个待会后面会讲到。之所以选择在一个子线程中创建Handler的例子来讲解,是因为这样能让我们更加清楚的明白安卓消息循环中Handler,MessageQueue,Looper这三者之间的关系,因为在主线程中很多细节都被掩藏起来了。

    首先从这段代码可以看到这里涉及两个概念(事实上安卓中的消息循环机制涉及三个重要概念),这里先简单介绍下

    Looper:消息循环,用于从MessageQueue消息队列中取出消息进行处理。

    Handler:消息发送与处理,Handler通过sendMessage向消息队列MessageQueue中发送一条消息,通过handlerMessage来处理消息

    MessageQueue:消息队列,用来存放Handler发送的消息,它仅仅用来存放消息


    首先讲解一下安卓中的消息循环的整体过程,然后从上述的示范代码上进行详细的讲解。

    安卓中的消息循环是使用Looper这个类来实现的,而Looper是基于线程的,即一个Looper对象与创建它的线程相关联,当使用 Looper.prepare()语句时它会创建一个Looper对象和一个MessageQueue对象,然后在创建Handler时会先获取到Handler所在的线程的Looper对象(如果调用的是无参构造函数时),通过这个Looper对象同时可以获得其MessageQueue对象,正因为如此相当于Handler与Looper相关联。这样通过Handler发送的消息才知道应该通过哪个Looper去进行处理。这是理解安卓整个消息循环机制核心,即MeaageQueue与Looper相关联,Handler与Looper相关联,从这里也可以看出Looper是安卓消息循环机制的核心。


    下面以上述示范代码为例进行详细讲解。

    首先我们来看一下 Looper.prepare()语句,即看一下prepare的源码:

    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    从这里可以看到prepare()函数作了两件事:创建一个looper对象,然后通过 sThreadLocal.set(new Looper());语句将其加入到 sThreadLocal中,这里就不得不提一下安卓消息循环机制中一个重要的概念:ThreadLocal,因为这涉及到Looper对象与线程相关联的功能的实现。ThreadLocal它的作用是在不同的线程中访问同一个ThreadLocal对象时通过ThreadLocal获取到的值是不一样的,即ThreadLocal中存储的值是基于线程的。我们来看一下ThreadLocal的set方法:

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

    可以看到set函数首先获取当前的线程,然后通过当前线程产生一个Valuse对象,然后通过values的put方法将value(即Looper对象)与this(即ThreadLocal对象)相关联,通过这段代码我们应该知道Looper是与线程相关的,因为set方法会通过线程产生Valuse对象,然后通过Valuse对象put方法,将Looper保存起来。


    接下来我们来看Handler handler=new Handler();这个语句,我们来看一下Handler()这个无参构造函数。

    public Handler() {
        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());
            }
        }
        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 = null;
    }
    从这里我们可以看到首先调用了 Looper.myLooper();方法,该方法是返回当前线程所关联的Looper,且可以看到如果获取到的mLooper为null则会抛出异常,这也说明创建Handler之前必选先创建Handler对象,获得了Looper对象之后,我们就可以获取到与Looper相关联的MessageQueue对象,即 mQueue = mLooper.mQueue;前面讲过创建Looper时会创建消息队列MessageQueue。这段代码我们重点来看一下Looper.myLooper();这个方法,通过这个函数获取到了与当前线程相关联的Looper对象,正因为如此,Handler对象发送的消息才知道应该被那个Looper处理。我们来看一下其代码:

     public static Looper myLooper() {
            return sThreadLocal.get();
        }
    可以看到他是通过sThreadLocal.get()方法来取得Looper对象,这个get与我们上面讲述的ThreadLocal的set方法是相对应的,一个用来存储数据,一个用来取出数据。

    我们来看一下sThreadLocal.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);
        }
    可以看到sThreadLocal.get()方法先获取当前线程,然后通过当前线程构造Values对象,然后通过valuse返回其存储的数据(当然该数据为Looper对象),也正因为如此在Handler中获取到的Looper对象与我们在当前线程中创建的Looper对象是同一个对象,这是保证Handler对象发送的信息被正确的Looper所处理的关键。


    最后看一下Looper.loop();语句,这个语句的作用是开启Looper循环,我们来看一下其源代码:

    public static final void loop() {
        Looper me = myLooper();
        MessageQueue queue = me.mQueue;
        while (true) {
            Message msg = queue.next(); // might block
            if (msg != null) {
                if (msg.target == null) {
                    return;
                }
                if (me.mLogging!= null) me.mLogging.println(
                        ">>>>> Dispatching to " + msg.target + " "
                        + msg.callback + ": " + msg.what
                        );
                msg.target.dispatchMessage(msg);
                if (me.mLogging!= null) me.mLogging.println(
                        "<<<<< Finished to    " + msg.target + " "
                        + msg.callback);
                msg.recycle();
            }
        }
    }
    可以看到loop方法首先获取当前Looper对象,然后获取该Looper对象的MessageQueue对象,然后在一个while死循环中不断的通过 queue.next();从消息队列中取出一个消息,然后通过消息msg.target这个属性来调用dispatchMessage(msg);来分派消息,msg.target这个属性本质上是发送消息给这个MessageQueue的Handler对象,即通过此语句就将Handler发送的消息交给它的dispatchMessage(msg);方法来处理,这样就将代码逻辑切换到创建该Handler的线程中去执行了。

    我们来看一下Handler的dispatchMessage(msg);方法

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    可以看到在该方法中调用了 handleMessage(msg);,而这个正是我们在Handler中处理消息的回调方法。

    最后就是我们使用sendMessage来发送消息的过程,Handler提供了多个发送消息的函数,最终都会调用sendMessageAtTime()方法,我们来看一下其源码:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
            msg.target = this;
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
    }
    可以看到在该方法中最终调用了MessageQueue的enqueueMessage(msg, uptimeMillis);方法,顾名思义,其作用是将一个消息入队,它的源码不重要,我们只需知道它是通过将一个消息插入到一个单向链表中的方式来完成的入队操作即可。


    在前面我们说过在主线程中我们是不需要创建一个Looper的,这是因为这些工作安卓系统几经帮我们做好了,安卓中的主线程即ActivityThread,主线程的入口函数为main,我们来看一下其源代码:

    public static void main(String[] args) {
        SamplingProfilerIntegration.start();
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        EventLogger.setReporter(new EventLoggingReporter());
        Process.setArgV0("<pre-initialized>");
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        AsyncTask.init();
        if (false) {
            Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    可以看到在该方法中调用了Looper.prepareMainLooper()方法,而Looper.prepareMainLooper()方法会调用Looper.prepare()方法。所以在主线程中我们不需自己创建一个Looper对象。


    好了,通过上述的讲解相信看官对安卓中的消息机制已经非常的清楚了,下面总结一下:

    1Looper对象是安卓消息循环的核心,Looper对象是基于线程的,我们在创建一个Looper对象时会通过ThreadLocal对象将我们创建的Looper与其所在的线程相关联起来,具体来说是通过 sThreadLocal.set(new Looper());语句将其保存起来,另外在创建一个Looper对象时其内部会帮我们自动创建了一个消息队列MessageQueue对象。

    2Looper的作用是通过一个while死循环来不断的查看消息队列MessageQueue中是否存在消息Message,若存在则会通过 msg.target.dispatchMessage(msg);将消息交给发送消息的Handler对象来进行处理。

    3我们在创建一个Handler对象时,其内部首先会获得当前线程所关联的Looper对象,即调用 Looper.myLooper();方法,具体来说是通过sThreadLocal.get()方法来完成的,

    这样就保证了在Handler中获取到的Looper对象与我们在当前线程中创建的Looper对象是同一个对象,从而保证Handler对象发送的信息被正确的Looper所处理。

    4另外在创建一个Handler对象时,其内部会通过获取到的Looper对象来获取与Looper对象相关联的MessageQueue对象,这样通过sendMessage发送的消息会发送到这个MessageQueue对象中,这也是保证Handler对象发送的信息被正确的MessageQueue所处理的关键(其本质是通过3来完成的,因为MessageQueue的创建是在Looper内部完成的,即MessageQueue是与Looper相关联的)。

    5MessageQueue仅仅用来保存消息而已,消息的分派是通过Looper来完成的,在Looper的loop循环中会通过一个while死循环来不断的查看消息队列MessageQueue中是否存在消息Message,若存在则会通过 msg.target.dispatchMessage(msg);将消息交给发送消息的Handler对象来进行处理,这样就将代码的逻辑切换到Handler所在的线程中进行处理。


    用图示表示如下:


    好了上述就是本人理解的关于安卓消息循环机制的全部内容,看官如果觉得不错请点个赞哦微笑,也请看官多多支持本人的其它博客文章微笑

  • 相关阅读:
    Jsp补充
    Jsp和Servlet关系
    ServletContext简介
    利用Session实现三天免登陆
    Cookie简介
    JSP简介
    线程基础--同步机制
    深思——工作面试
    response.setcontenttype的參数
    Codeforces 96D Volleyball spfa
  • 原文地址:https://www.cnblogs.com/hainange/p/6334019.html
Copyright © 2020-2023  润新知