Handler

Start

前言

转载至:https://github.com/francistao/LearningNotes/blob/master/Part1/Android/%E7%BA%BF%E7%A8%8B%E9%80%9A%E4%BF%A1%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90.md

Android的线程间通信主要Handler、Looper、Message、MessageQueue这四个。

1. Looper

在 Looper 中维持一个 Thread 对象以及 MessageQueue,通过 Looper 的构造函数可以知道:

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

Looper 在构造函数里干了两件事:

  • 将线程对象指向了创建 Looper 的线程
  • 创建了一个新的 MessageQueue

Looper 主要有两个方法:

  • looper.loop()
  • looper.prepare()

1.1 looper.loop()

在当前线程启动一个Message loop机制,此段代码将直接分析出Looper、Handler、Message、MessageQueue的关系:

public static void loop() {
        final Looper me = myLooper();//获得当前线程绑定的Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//获得与Looper绑定的MessageQueue

        // 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();
        
        //进入死循环,不断地去取对象,分发对象到Handler中消费
        for (;;) {
            Message msg = queue.next(); // 不断的取下一个Message对象,在这里可能会造成堵塞。
            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);
            }
            
            //在这里,开始分发Message了
            //至于这个target是神马?什么时候被赋值的? 
            //我们一会分析Handler的时候就会讲到
            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);
            }
            
            //当分发完Message之后,当然要标记将该Message标记为 *正在使用* 啦
            msg.recycleUnchecked();
        }
    }

分析了上面的源代码,我们可以意识到,最重要的方法是:

  • queue.next()
  • msg.target.dispatchMessage(msg)
  • msg.recycleUnchecked()

Looper 中最重要的部分都是由 Message、MessageQueue 组成的!这段最重要的代码中涉及到了四个对象,他们与彼此的关系如下:

  • MessageQueue :装食物的容器
  • Message :被装的食物
  • msg.target :食物消费者
  • Looper :负责分发食物的人

1.2 looper.prepare()

在当前线程关联一个Looper对象。

 private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //在当前线程绑定一个Looper
        sThreadLocal.set(new Looper(quitAllowed));
 }

以上代码只做了两件事:

  1. 判断当前线程有没有 Looper,如果有就抛出异常(Android 规定一个线程只能拥有一个与自己关联的 Looper)。

  2. 如果没有就设置一个新的 Looper 到当前线程

2. Handler

我们通常使用 Handler 的通常方法是:

 Handler handler = new Handler(){
        //这个方法是在哪里被回调的?
        @Override
        public void handleMessage(Message msg) {
            //Handler your Message
        }
 };

还是先看一下 Handler 的构造方法:

//空参数的构造方法与之对应,这里只给出主要的代码,具体大家可以到源码中查看
public Handler(Callback callback, boolean async) {
        //打印内存泄露提醒log
        ....
        
        //获取与创建Handler线程绑定的Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //获取与Looper绑定的MessageQueue
        //因为一个Looper就只有一个MessageQueue,也就是与当前线程绑定的MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
        
}

带上问题:

  1. Looper.loop() 死循环中的 msg.target 是什么时候被赋值的?
  2. handler.handlerMessage(msg) 在什么时候被回调的?

2.1 Looper.loop() 死循环中的msg.target是什么时候被赋值的?

要分析这个问题,很自然的我们想到从发送消息开始,无论是handler.sendMessage(msg) 还是 handler.sendEmptyMessage(what),我们最终都可以追溯到以下方法:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //引用Handler中的MessageQueue
        //这个MessageQueue就是创建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;
        }
        //将新来的Message加入到MessageQueue中
        return enqueueMessage(queue, msg, uptimeMillis);
}

接下来分析 enqueueMessage(queue, msg, uptimeMillis):

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //显而易见,在此给msg.target赋值handler!
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

2.2 handler.handleMessage(msg) 在什么时候被回调的?

通过以上的分析,我们很明确的知道 Message 中的 target 是在什么时候被赋值的了,我们先来分析在 Looper.loop() 中出现过的过的 dispatchMessage(msg) 方法:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //在这里回调
            handleMessage(msg);
        }
}

加上以上分析,我们将之前分析结果串起来,就可以知道了某些东西: Looper.loop() 不断地获取 MessageQueue 中的 Message,然后调用与 Message 绑定的 Handler对象的 dispatchMessage 方法,最后,我们看到了 handleMessage 就在 dispatchMessage 方法里被调用的。

3. Handler内存泄漏分析及解决

首先,请浏览下面这段 handler 代码:

public class SampleActivity extends Activity {
  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ... 
    }
  }
}

在使用 handler 时,这是一段很常见的代码。但是,它却会造成严重的内存泄漏问题。在实际编写中,我们往往会得到如下警告:

⚠️:In Android, Handler classes should be static or leaks might occur.

3.1 分析

3.1.1 Android 角度

当 Android 应用程序启动时,framework 会为该应用程序的主线程创建一个 Looper 对象。这个 Looper 对象包含一个简单的消息队列Message Queue,并且能够循环的处理队列中的消息。这些消息包括大多数应用程序 framework 事件,例如 Activity 生命周期方法调用、button 点击等,这些消息都会被添加到消息队列中并被逐个处理。

另外,主线程的 Looper 对象会伴随该应用程序的整个生命周期。

然后,当主线程里,实例化一个 Handler 对象后,它就会自动与主线程 Looper 的消息队列关联起来。所有发送到消息队列的消息 Message 都会拥有一个对 Handler 的引用,所以当 Looper 来处理消息时,会据此回调 [Handler#handleMessage(Message)] 方法来处理消息。

3.1.2 Java角度

在 java 里,非静态内部类匿名类 都会潜在的引用它们所属的外部类。但是,静态内部类却不会。

3.2 泄漏来源

请浏览下面一段代码:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

当 activity 结束 (finish) 时,里面的延时消息在得到处理前,会一直保存在主线程的消息队列里持续10分钟。而且,由上文可知,这条消息持有对 handler 的引用,而 handler 又持有对其外部类(在这里,即 SampleActivity)的潜在引用。这条引用关系会一直保持直到消息得到处理,从而,这阻止了SampleActivity 被垃圾回收器回收,同时造成应用程序的泄漏。

⚠️:上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。

3.3 解决方案

首先,上面已经明确了内存泄漏来源:

只要有未处理的消息,那么消息会引用 handler,非静态的 handler 又会引用外部类,即 Activity,导致 Activity 无法被回收,造成泄漏;

Runnable 类属于非静态匿名类,同样会引用外部类。

为了解决遇到的问题,我们要明确一点:静态内部类不会持有对外部类的引用。所以,我们可以把 handler 类放在单独的类文件中,或者使用静态内部类便可以避免泄漏。

另外,如果想要在 handler 内部去调用所在的外部类 Activity,那么可以在 handler 内部使用弱引用的方式指向所在 Activity,这样统一不会导致内存泄漏。

对于匿名类 Runnable,同样可以将其设置为静态类。因为静态的匿名类不会持有对外部类的引用。

public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }

  private final MyHandler mHandler = new MyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

原文链接🔗

4. 总结

通过以上的分析,我们可以很清晰的知道 Handler、Looper、Message、MessageQueue 这四者的关系以及如何合作的了。

当我们调用 handler.sendMessage(msg) 方法发送一个 Message 时,实际上这个 Message 是发送到与当前线程绑定的一个 MessageQueue 中,然后与当前线程绑定的 Looper 将会不断的从 MessageQueue 中取出新的 Message,调用 msg.target.dispathMessage(msg) 方法将消息分发到与 Message 绑定的 handler.handleMessage() 方法中。

一个 Thread 对应多个 Handler, 一个 Thread 对应一个 Looper 和 MessageQueue,Handler 与 Thread 共享 Looper 和 MessageQueue。 Message 只是消息的载体,将会被发送到与线程绑定的唯一的 MessageQueue 中,并且被与线程绑定的唯一的 Looper 分发,被与其自身绑定的 Handler 消费。

申明:开始的图片来源网络,侵删

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容