android 中的 Handler 线程间通讯

 

1、 在MainActivity中为何只是相似的写一行以下代码就可使用handler了呢?

 

Handler handler = new Handler() {   @Override   public void handleMessage(Message msg) {     // handle the nsg message...
  } };  private void sendMessage() {    handler.sendEmptyMessage(11); }

 

打开handler的源码能够在它的构造函数中,看到以下的几行代码:android

 

mLooper = Looper.myLooper();        // (1)
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) 号注释中的 Looper.myLooper(); 这条语句,近儿,能够看到下面的代码:async

 

public static Looper myLooper() { return sThreadLocal.get();     // (2)
 }

 

 

其中 sThreadLocal Looper 类中被声明为:ide

 

static final ThreadLocal<Looper> sThreadLocal = newThreadLocal<Looper>();

 

可得它是一个静态不可变的键值,由于它是静态成员,由类的加载过程可知在 Looper 被加载后就当即对该对象进行了初始化,并且它被声明为final类型,在接下来的生命周期中将是一个不可改变的引用,这也是称呼它为一个键值一个缘由;固然,当你继续跟进时会发现,称呼它为键值原来是有更好的理由的。跟进(2)号注释中的sThreadLocal.get(); 语句,能够获得下面的逻辑:函数

 

public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();    // (3)
        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); // (4) }

 

咱们只关注上面的代码中的 注释(3)和 (4)两行代码,其中从注释 (3)处咱们得知这里的读取操做是从当前线程的内部得到数据的,而注释(4)则进一步告诉咱们,它是一个以 ThreadLocal 类型的对象为键值,也就是Looper中的 static final 访问控制的 sThreadLocal 对象。奇妙的地方就在这里,由于每次调用时 ThreadLocal 虽然都是同一个 sThreadLocal 对象,但在ThreadLocal 内部它是从当前正在活动的线程中取出 Looper 对象的,因此达到了一种不一样的线程调用同一个 Looper 中的同一个 sThreadLocal 对象的 get 方法,而获得的 Looper是不同的目的。而且从注释(1)处能够得知,当咱们调用 sThreadLocal.get();若是返回是一个null时,咱们的调用将是失败的,也就是在从当前正在活动的线程的内部读取时尚未初始化,抛出的异常提醒咱们先执行ooper.prepare()方法,跟进Looper的prepare()方法:oop

 

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

 

可得知,它一样是经过sThreadLocal对象来存放数据的,跟进一步ui

 

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

 

从上面的代码可得知,每一个线程至多只能存放一个Looper对象,注意这里强调的是每一个线程只能存放一份,并非sThreadLocal 只能放一个,这也是线程本地化存储的秘密所在。因此咱们获得一个前提条件,在调用相似new Handler(){};语句时,必须事先已执行 Looper.prepare(); 语句,并且要确保两条语句都是在同一个线程中被调用的,否则可能得不到咱们指望的结果。可是为何咱们在 MainAcitivity 中没有显示调用 Looper.prepare(); 方法,而只是简单调用 new Handler(); 语句呢?原来在 MainActivity运行所在的主线程(也成UI线程,getId()获得的id号是1)被android系统启动时,就主动调用了一次Looper.prepare()方法:this

 

public static final void More ...main(String[] args) { SamplingProfilerIntegration.start(); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); (跟进该方法) ActivityThread thread = new ActivityThread(); thread.attach(false); Looper.loop(); if (Process.supportsProcesses()) { throw new RuntimeException("Main thread loop unexpectedly exited"); } thread.detach(); String name = (thread.mInitialApplication != null) ? thread.mInitialApplication.getPackageName() : "<unknown>"; Log.i(TAG, "Main thread of " + name + " is now exiting"); } }

 

 

二 、 Handler中是什么原理,使得发送的消息时只要使用handler对象,而在消息被接受并处理时就能够直接调用到handler中覆写的handleMessge方法?

 

就从下面的这行代码开始:spa

 

private void sendMessage() { handler.sendEmptyMessage(0x001); }

 

在handler类中一直跟进该方法,能够发现下面的几行代码:线程

 

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

 

在上面的注释 (5) 处能够看到,这里对每一个msg中的target字段进行了设置,这里就是设置了每一个消息未来被处理时用到的handler对象。code

能够跟进Looper方法的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);    // (6)

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

 

在注释 (6)处能够看到, msg.target.dispatchMessage(msg); 原来是调用的Message的 dispatchMessage()方法,不妨咱们跟进去看看:

 

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

 

一系列优先考虑的回调判断以后,终于轮到了自定义的处理的函数处理。

 

3、遇到This Handler class should be static or leaks might occur 警告 为何?

 

大体意思是说:Handler 类应该为static类型,不然有可能形成泄露。在程序消息队列中排队的消息保持了对目标Handler类的引用。若是Handler是个内部类,那 么它也会保持它所在的外部类的引用。为了不泄露这个外部类,应该将Handler声明为static嵌套类,而且使用对外部类的弱应用。说的很明白,由于内部类对外部类有个引用,因此当咱们在内部类中保存对外部类的弱引用时,这里的内部类对外引用仍是存在的,这是jdk实现的,而只有咱们声明为静态内部类,此时是对外部类没有强引用的(这时它和普通的外部类没有什么区别,只是在访问控制上有很大的方便性,它能够直接访问外部类的私有成员),这样当线程中还有 Looper  和 Looper 的 MessageQueue 还有 Message 时( message 中的 target 保持了对 Handler 的引用),而此时 Activity已经死亡时,就能够对Activity 进行回收了。

 

4、注意事项

 

1.  在本身定义的子线程中若是想实现handler?

Looper.prepare(); handler=new Handler(); ... Looper.loop();

 

这几行代码必定要在run方法中调用,而不是在自定义的 Thread 子类的构造函数中调用。由于子线程的构造方法被调用时,其实子线程尚未执行,也就是说当前的线程并不是子线程,还仍然是父线程,因此调用 Looper.prepare(); 也只是对父线程进行了线程本地化存储了一个 Looper 对象。

2.  简而言之

两个线程之间用 handler 进行通讯,一个线程先在线程本地存放一个Looper对象,该对象中有消息队列 MessageQueue 成员,只是这个Looper对象,是在线程运Looper.prepare() 时初始化的,且保证只初始化一次,进而该线程进入Loop.loop()消息循环状态,等待其它线程调用本身的 hanldler 对象的 sendMessage() 方法往己的消息队列中存放消息,并在有消息时读取并处理,没有则阻塞。

 

2.  多方协做 

若是同时有多个调用方使用一个同一工做线程(调用Looper.loop()的那个线程),则能够先建立一个HandlerThread线程,而后调用它的getLooper方法获得一个Looper对象,

这时每一个调用方均可以拿着这个Looper对象去构造知足各自处理逻辑的的Handler对象了,而后须要出的时候将消息丢到本身的Handler对象中便可。

相关文章
相关标签/搜索