关于android:Android-Handler机制LooperHandlerMessageQueueMessage的关系

6次阅读

共计 11724 个字符,预计需要花费 30 分钟才能阅读完成。

一、概述

Handler 是 Android 中解决异步音讯的机制。Looper、Handler、MessageQueue、Message 概括来说就是:Looper 负责的就是创立一个 MessageQueue,而后进入一个有限循环体一直从该 MessageQueue 中读取音讯 Message,而后回调相应的音讯处理函数,而音讯的创建者就是一个或多个 Handler,执行实现一个音讯后则持续循环。

二、MessageQueue 详解

音讯队列 MessageQueue 就是寄存音讯的队列。那队列中存储的音讯是什么呢?假如咱们在 UI 界面上单击了某个按钮,而此时程序又恰好收到了某个播送事件,那咱们如何解决这两件事呢?因为一个线程在某一时刻只能解决一件事件,不能同时解决多件事件,所以咱们不能同时解决按钮的单击事件和播送事件,咱们只能挨个对其进行解决,只有挨个解决就要有解决的先后顺序。为此 Android 把 UI 界面上单击按钮的事件封装成了一个 Message,将其放入到 MessageQueue 外面去,行将单击按钮事件的 Message 入栈到音讯队列中,而后再将播送事件的封装成以 Message,也将其入栈到音讯队列中。也就是说一个 Message 对象示意的是线程须要解决的一件事件,音讯队列就是一堆须要解决的 Message 的池。线程 Thread 会顺次取出音讯队列中的音讯,顺次对其进行解决。

MessageQueue 中有两个比拟重要的办法,一个是 enqueueMessage 办法,一个是 next 办法。enqueueMessage 办法用于将一个 Message 放入到音讯队列 MessageQueue 中,next 办法是从音讯队列 MessageQueue 中阻塞式地取出一个 Message。

三、Looper 详解

音讯队列 MessageQueue 只是存储 Message 的中央,真正让音讯队列循环起来的是 Looper,这就好比音讯队列 MessageQueue 是个水车,那么 Looper 就是让水车转动起来的河水,如果没有河水,那么水车就是个静止的陈设,没有任何用途,Looper 让 MessageQueue 动了起来。

Looper 是用来使线程中的音讯循环起来的。默认状况下当咱们创立一个新的线程的时候,这个线程外面是没有音讯队列 MessageQueue 的。为了可能让线程可能绑定一个音讯队列,咱们须要借助于 Looper:首先咱们要调用 Looper 的 prepare 办法,而后调用 Looper 的 loop 办法。

(一)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(true));

sThreadLocal 是一个 ThreadLocal 对象,能够在一个线程中存储变量。能够看到,将一个 Looper 的实例放入了 ThreadLocal,并且先判断了 sThreadLocal.get 是否为 null,否则抛出异样。这也就阐明了 Looper.prepare() 办法不能被调用两次,同时也保障了一个线程中只有一个 Looper 实例。

(二)构造函数

下面的代码执行了 Looper 的构造函数,咱们看一下其代码:

private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);
        mRun = true;
        mThread = Thread.currentThread();}

在构造函数中,创立了一个音讯队列 MessageQueue,并将其赋值给其成员字段 mQueue, 这样 Looper 也就与 MessageQueue 通过成员字段 mQueue 进行了关联。

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

下面有几行代码是要害代码:

1. final Looper me = myLooper();

myLooper() 办法间接返回了 sThreadLocal 存储的 Looper 实例,如果 me 为 null 则抛出异样,也就是说 looper 办法必须在 prepare 办法之后运行。

final Looper me = myLooper();
if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() 
            wasn't called on this thread.");
        }
public static Looper myLooper() {return sThreadLocal.get();
}

2. final MessageQueue queue = me.mQueue;

拿到该 looper 实例中的音讯队列 mQueue。变量 me 是通过静态方法 myLooper() 取得的以后线程所绑定的 Looper,me.mQueue 是以后线程所关联的音讯队列。

3. for (;;)

进入了循环。咱们发现 for 循环没有设置循环终止的条件,所以这个 for 循环是个有限循环。

4. Message msg = queue.next(); // might block

取出一条音讯,如果没有音讯则阻塞。咱们通过音讯队列 MessageQueue 的 next 办法从音讯队列中取出一条音讯,如果此时音讯队列中有 Message,那么 next 办法会立刻返回该 Message,如果此时音讯队列中没有 Message,那么 next 办法就会阻塞式地期待获取 Message。

5. msg.target.dispatchMessage(msg);

msg 的 target 属性是 Handler,该代码的意思是让 Message 所关联的 Handler 通过 dispatchMessage 办法让 Handler 解决该 Message。

6. msg.recycle();

开释音讯占据的资源。

(四)Looper 次要作用

1. 与以后线程绑定,保障一个线程只会有一个 Looper 实例,同时一个 Looper 实例也只有一个 MessageQueue。

2.loop() 办法,一直从 MessageQueue 中去取音讯,交给音讯 Message 的 target 属性,即 Handler 的 dispatchMessage 去解决。

二、Handler 详解

(一)构造函数

应用 Handler 之前,咱们都是初始化一个实例,比方用于更新 UI 线程,咱们会在申明的时候间接初始化,或者在 onCreate 中初始化 Handler 实例。所以咱们首先看 Handler 的构造方法,看其如何与 MessageQueue 分割上的,它在子线程中发送的音讯(个别发送音讯都在非 UI 线程)怎么发送到 MessageQueue 中的。

public Handler() {this(null, false);
}
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());
            }
        }
 
        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;
    }

下面有几行代码是要害代码:

1. public Handler(Callback callback, boolean async)

Handler.Callback 是用来解决 Message 的一种伎俩,如果没有传递该参数,那么就应该重写 Handler 的 handleMessage 办法,也就是说为了使得 Handler 可能解决 Message,咱们有两种方法:

(1)向 Hanlder 的构造函数传入一个 Handler.Callback 对象,并实现 Handler.Callback 的 handleMessage 办法。

(2)无需向 Hanlder 的构造函数传入 Handler.Callback 对象,然而须要重写 Handler 自身的 handleMessage 办法。

 // 在主线程中创立 mHandler,所以主动绑定主线程
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {switch (msg.what){
                case 1:
                    System.out.println("handleMessage thread id" + Thread.currentThread().getId());
                    System.out.println("msg.arg1:" + msg.arg1);
                    System.out.println("msg.arg2:" + msg.arg2);
                    System.out.println("msg.obj:" + msg.obj.toString());
                    System.out.println("msg.setDate:" + msg.getData().get("QQ"));

                    textview.setText("success");
                    break;
            }
        }
    };

   private Handler mHandler2 = new Handler(new Handler.Callback() {
       @Override
       public boolean handleMessage(@NonNull Message msg) {switch (msg.what){
               case 1:
                   System.out.println("handleMessage thread id" + Thread.currentThread().getId());
                   System.out.println("msg.arg1:" + msg.arg1);
                   System.out.println("msg.arg2:" + msg.arg2);
                   System.out.println("msg.obj:" + msg.obj.toString());
                   System.out.println("msg.setDate:" + msg.getData().get("QQ"));

                   textview.setText("success");
                   break;
           }
           return false;
       }
   });

也就是说无论哪种形式,咱们都得通过某种形式实现 handleMessage 办法,这点与 Java 中对 Thread 的设计有殊途同归之处。

在 Java 中,如果咱们想应用多线程,有两种方法:

(1)向 Thread 的构造函数传入一个 Runnable 对象,并实现 Runnable 的 run 办法。

(2)无需向 Thread 的构造函数传入 Runnable 对象,然而要重写 Thread 自身的 run 办法。

2. mLooper = Looper.myLooper();

首先通过 Looper.myLooper() 获取了以后线程保留的 Looper 实例。

3.mQueue = mLooper.mQueue;

而后再获取该 Looper 实例中保留的音讯队列 MessageQueue,这样就保障了 Handler 的实例与 Looper 实例中 MessageQueue 关联上了。

(二)sendMessage() 办法

   public final boolean sendMessage(Message msg)
    {return sendMessageDelayed(msg, 0);
    }
   public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }
 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;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

辗转反则最初调用了 sendMessageAtTime,在此办法外部有间接获取 MessageQueue,而后调用了 enqueueMessage 办法,咱们再来看看此办法:

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

下面有几行代码是要害代码:

1. msg.target = this;

该代码将 Message 的 target 绑定为以后的 Handler。

2. queue.enqueueMessage;

变量 queue 示意的是 Handler 所绑定的音讯队列 MessageQueue,通过调用 queue.enqueueMessage(msg, uptimeMillis) 咱们将 Message 放入到音讯队列中。

当初曾经很分明了 Looper 会调用 prepare() 和 loop() 办法,在以后执行的线程中保留一个 Looper 实例,这个实例会保留一个 MessageQueue 对象,而后以后线程进入一个有限循环中去,一直从 MessageQueue 中读取 Handler 发来的音讯。而后再回调创立该音讯的 Handler 中的 dispathMessage 办法。Handler 的 dispatchMessage 的源码如下:

public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);
        } else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}
            }
            handleMessage(msg);
        }

下面有几行代码是要害代码:

1. if (msg.callback != null) {handleCallback(msg); }

首先会判断 msg.callback 存不存在,msg.callback 是 Runnable 类型,如果 msg.callback 存在,那么阐明该 Message 是通过执行 Handler 的 postXXX 系列办法将 Message 放入到音讯队列中的,这种状况下会执行 handleCallback(msg), handleCallback 源码如下:

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

这样咱们咱们就分明地看到咱们执行了 msg.callback 的 run 办法,也就是执行了 postXXX 所传递的 Runnable 对象的 run 办法。

2. else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;} }

如果咱们不是通过 postXXX 系列办法将 Message 放入到音讯队列中的,那么 msg.callback 就是 null,代码持续往下执行,接着咱们会判断 Handler 的成员字段 mCallback 存不存在。mCallback 是 Hanlder.Callback 类型的,咱们在下面的 Handler 构造函数提到过,在 Handler 的构造函数中咱们能够传递 Hanlder.Callback 类型的对象,该对象须要实现 handleMessage 办法,如果咱们在构造函数中传递了该 Callback 对象,那么咱们就会让 Callback 的 handleMessage 办法来解决 Message。

3.handleMessage(msg);

如果咱们在构造函数中没有传入 Callback 类型的对象,那么 mCallback 就为 null, 那么咱们会调用 Handler 本身的 hanldeMessage 办法,该办法默认是个空办法,咱们须要本人是重写实现该办法。

综上,咱们能够看到 Handler 提供了三种路径解决 Message,而且解决有前后优先级之分:首先尝试让 postXXX 中传递的 Runnable 执行,其次尝试让 Handler 构造函数中传入的 Callback 的 handleMessage 办法解决,最初才是让 Handler 本身的 handleMessage 办法解决 Message。

让咱们看一下 handleMessage(msg)

  /**
    * Subclasses must implement this to receive messages.
    */
   public void handleMessage(Message msg) { }

能够看到这是一个空办法,为什么呢,因为音讯的最终回调是由咱们管制的,咱们在创立 handler 的时候都是复写 handleMessage 办法,而后依据 msg.what 进行音讯解决。

private Handler mHandler = new Handler(){public void handleMessage(android.os.Message msg){switch (msg.what){
            case value:
                break;
            default:
                 break;
            }   
        };
    };

到此,sendMessage 形式流程曾经解释结束,接下来看下 post 形式。

(三)post() 办法

mHandler.post(new Runnable()
        {
            @Override
            public void run()
            {Log.e("TAG", Thread.currentThread().getName());
                mTxt.setText("yoxi");
            }
        });

而后 run 办法中能够写更新 UI 的代码,其实这个 Runnable 并没有创立什么线程,而是发送了一条音讯,上面看源码:

 public final boolean post(Runnable r){return  sendMessageDelayed(getPostMessage(r), 0);
    }
  private static Message getPostMessage(Runnable r) {Message m = Message.obtain();
        m.callback = r;
        return m;
    }

能够看到,在 getPostMessage 中,失去了一个 Message 对象,而后将咱们创立的 Runable 对象作为 callback 属性,赋值给了此 message。注:产生一个 Message 对象,能够 new 也能够应用 Message.obtain() 办法;两者都能够,然而更倡议应用 obtain 办法,因为 Message 外部保护了一个 Message 池用于 Message 的复用,防止应用 new 从新分配内存。

 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;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

最终和 handler.sendMessage 一样,调用了 sendMessageAtTime,而后调用了 enqueueMessage 办法,给 msg.target 赋值为 handler,最终退出 MessagQueue。

 public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);
        } else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}
            }
            handleMessage(msg);
        }
    }

能够看到,这里 msg 的 callback 和 target 都有值,那么会执行 msg.callback != null 的判断,则执行 handleCallback 回调,也就是咱们的 Runnable 对象。

三、总结

到此,这个流程曾经解释结束,让咱们首先总结一下:

  1. 首先 Looper.prepare() 在本线程中保留一个 Looper 实例,而后该实例中保留一个 MessageQueue 对象;因为 Looper.prepare() 在一个线程中只能调用一次,所以 MessageQueue 在一个线程中只会存在一个。
  2. 在 Lopper 构造函数中,创立了一个音讯队列 MessageQueue,并将其赋值给其成员字段 mQueue, 这样 Looper 也就与 MessageQueue 通过成员字段 mQueue 进行了关联。
  3. Looper.loop() 会让以后线程进入一个有限循环,不端从 MessageQueue 的实例中读取音讯,而后回调 msg.target.dispatchMessage(msg) 办法。
  4. Handler 的构造方法,会首先失去以后线程中保留的 Looper 实例,进而与 Looper 实例中的 MessageQueue 想关联。
  5. Handler 的 sendMessage 办法,会给 msg 的 target 赋值为 handler 本身,而后退出 MessageQueue 中。
  6. 在结构 Handler 实例时,咱们会重写 handleMessage 办法,也就是 msg.target.dispatchMessage(msg) 最终调用的办法。

四、一图胜千言

咱们在本文探讨了 Thread、MessageQueue、Looper 以及 Hanlder 的之间的关系,咱们能够通过如下一张传送带的图来更形象的了解他们之间的关系。

在现实生活的生产生存中,存在着各种各样的传送带,传送带下面洒满了各种货物,传送带在发动机滚轮的带动下始终在向前滚动,一直有新的货物搁置在传送带的一端,货物在传送带的带动下送到另一端进行收集解决。

咱们能够把传送带上的货物看做是一个个的 Message,而承载这些货物的传送带就是装载 Message 的音讯队列 MessageQueue。传送带是靠发送机滚轮带动起来转动的,咱们能够把发送机滚轮看做是 Looper,而发动机的转动是须要电源的,咱们能够把电源看做是线程 Thread,所有的音讯循环的所有操作都是基于某个线程的。所有准备就绪,咱们只须要按下电源开关发动机就会转动起来,这个开关就是 Looper 的 loop 办法,当咱们按下开关的时候,咱们就相当于执行了 Looper 的 loop 办法,此时 Looper 就会驱动着音讯队列循环起来。

那 Hanlder 在传送带模型中相当于什么呢?咱们能够将 Handler 看做是放入货物以及取走货物的管道:货物从一端顺着管道划入传送带,货物又从另一端顺着管道划出传送带。咱们在传送带的一端放入货物的操作就相当于咱们调用了 Handler 的 sendMessageXXX、sendEmptyMessageXXX 或 postXXX 办法,这就把 Message 对象放入到了音讯队列 MessageQueue 中了。当货物从传送带的另一端顺着管道划出时,咱们就相当于调用了 Hanlder 的 dispatchMessage 办法,最终执行 handleMessage 办法,在该办法中咱们实现对 Message 的解决。

相干视频举荐:
【Android handle 面试】MessageQueue 如何放弃各线程增加音讯的线程平安?
【Android handle 面试】子线程中的 looper 在无音讯的时候应该怎么解决缩小性能问题?
【Android handle 面试】一个线程有几个 hander 和几个 looper?

本文转自 https://juejin.cn/post/6844904195653386248,如有侵权,请分割删除。

正文完
 0