关于Android那些事--handler

简述

Handler 与 Message、MessageQueue、Looper 一起构成了 Android 的消息机制,主要用来进行线程间通信来达到与用户进行交互,View 的绘制、点击事件、Activity 的生命周期回调等作用。

  • Handler:Handler 负责发送和处理 Message;创建时先获取默认或传递来的 Looper 对象,并持有 Looper 对象包含的 MessageQueue,发送消息时使用该 MessageQueue 对象来插入消息并把自己封装到具体的 Message 中。
  • Message:消息,负责传递标示(what) 和数据(obj) ;每个 Message 都会通过 target 这个成员变量来绑定一个 Handler,由这个 Handler 来发送和处理 Message。
  • MessageQueue:消息队列,负责存放有 Handler 发送过来的消息;每个 Handler 中都有一个 final类型的MessageQueu,Handler 发送消息就是把消息加入这个 MessageQueue 。
  • Looper:负责不断的从 MessageQueue 中取出消息然后交给 Handler(Message#target ) 处理;每个 Looper 中都有一个唯一的消息队列(MessageQueue),每个 Handler 中都有一个 final类型的Looper来进行消息的无限循环,Handler 中的 MessageQueue 就是来自 Looper。如果获取的 MessageQueue 没有消息时,便阻塞在 loop 的queue.next() 中的 nativePollOnce() 方法里,反之则唤醒主线程继续工作,之后便使用 Message 封装的 handler 对象进行处理

注意:每个线程只能有一个 Looper 和 一个 MessageQueue,可以有多个 Handler,每个 Handler 可以发送和处理多个 Message。

注意事项

  • 使用Handler时可能引起内存泄漏
    • Java 中非静态内部类和匿名内部类会持有外部类的引用
      • 非静态的内部 Handler 子类、匿名 Handler 子类会持有外部类的引用(Activity),而 Handler 可能会因为要等待处理耗时操作导致存活时间超过 Activity,或者消息队列中存在未被 Looper 处理的 Message ,而 Message 会持有 Handler 的引用。于是,在 Activity 退出时,其引用还是被 Handler 持有,导致 Activity 无法被及时回收,造成内存泄露。
    • Handler 的生命周期比外部类长
      • 非静态的内部 Runnable 子类、匿名 Runnable 子类 post 到任意 Handler 上时,Runnable 其实是 Massage中的 Callback,持有 Message 引用,如果这个 Massage 在消息队列还没有被处理,那么就会造成 Runnable 一直持有外部类的引用而造成内存泄露。
  • 解决方案
    • 根本思路:让使用handler的对象在自己生命周期内使用handler,不使用及时移除Messages
    • 通过静态内部类或者外部类来声明 Handler 和 Runnable。
    • 通过弱引用来拿到外部类的变量。
    • 在 Activity/Fragment 销毁的时候请空 MessageQueue 中的消息。

源码分析

在主线程的入口,ActivityThread 的 main 方法

public static void main(String[] args) {
        // 准备主线程的 Looper     
        Looper.prepareMainLooper();
        // 创建 ActivityThread
        ActivityThread thread = new ActivityThread();
        // 用于绑定应用进程
        thread.attach(false);
        // 获取主线程的 Handler 
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
  
        // 对消息队列进行无线轮询,处理消息
        Looper.loop();
        // 一旦跳出循环,抛出异常(Android 不允许跳出主线程的 Looper.loop())
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

逐步分析
==> Looper.prepareMainLooper()

public static void prepareMainLooper() {
     // 准备一个 Looper, false 不允许退出
     prepare(false);
     synchronized (Looper.class) {
         // main Looper 只能初始化一次,再次初始化会抛出异常
         if (sMainLooper != null) {
             throw new IllegalStateException("The main Looper has already been prepared.");
         }
         // 获取 main Looper
         sMainLooper = myLooper();
     }
 }

==> prepare(false)

// 准备一个 Looper,quitAllowed 是否允许 Looper 中的 MessageQueue 退出
// 默认 prepare() 允许退出,主线程这里不允许退出
private static void prepare(boolean quitAllowed) {
 // 先看下 sThreadLocal 是什么
 // static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 // ThreadLocal:线程本地存储区,每个线程都有本地存储区域,这个区域是每个线程私有的,不同的线程不能之间不能彼此访问
 // 如果 sThreadLocal 中有数据,抛出异常,换句话说 prepare() 这个函数每个线程只能执行一次
 if (sThreadLocal.get() != null) {
     throw new RuntimeException("Only one Looper may be created per thread");
 }
 // 创建 Looper 保存到该线程的 ThreadLocal 中
 sThreadLocal.set(new Looper(quitAllowed));
}

总结:用 ThreadLocal 来保存该线程的 Looper 对象。ThreadLocal 可以看作是一个用来储存数据的类,类似 HashMap、ArrayList等集合类,它存放着属于当前线程的变量。

==> new Looper(quitAllowed)

private Looper(boolean quitAllowed) {
 // 在 Looper 创建的时候创建一个消息队列
 // quitAllowed:消息队列是否可以退出,主线的消息队列肯定不允许退出,所以上面是 prepare(false)
 // quitAllowed 为 false 执行 MessageQueue#quit 退出消息队列时会出现异常
 mQueue = new MessageQueue(quitAllowed);
 // 获取 Looper 存在于哪个线程
 mThread = Thread.currentThread();
}

==> sMainLooper = myLooper()

public static @Nullable Looper myLooper() {
 // 从 sThreadLocal 中获取当前线程的 Looper 
 // 如果当前线程没有调用 Looper.prepare 返回 null, 无准备无对象
 return sThreadLocal.get();
}

==> sMainThreadHandler = thread.getHandler();

final Handler getHandler() {
 // 返回 mH
 return mH;
}

// mH 在成员变量的位置 new H()
final H mH = new H();

// H 继承了 Handler 封装了一系列关于 Acitivty、Service 以及其他 Android 相关的操作
private class H extends Handler 

总结:在主线程的 main 方法中,会创建主线程的 Looper、MessageQueue,然后进入 Looper.loop() 循环中,不断的取出消息,处理消息,以此来驱动 Android 应用的运行。

2、Handler 的创建,Handler 的所有构造方法都会跳转到下面两个之一

public Handler(Callback callback, boolean async) {
 // Hanlder 是匿名类、内部类、本地类时,如果没有声明为 static 则会出现内存泄漏的警告
 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
 mLooper = Looper.myLooper();
 if (mLooper == null) {
     throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
 }
 // 消息队列,从 Looper 中获取
 mQueue = mLooper.mQueue;
 // 处理消息的回调接口
 mCallback = callback;
 // 处理消息的方式是否为异步,默认同步
 mAsynchronous = async;
}

public Handler(Looper looper, Callback callback, boolean async) {
 mLooper = looper;
 mQueue = looper.mQueue;
 mCallback = callback;
 mAsynchronous = async;
}

总结:
在 Handler 的构造方法中,Handler 和 Looper、MessageQueue 绑定起来,如果当前线程没有 Looper 抛出异常(这也是为什么直接在子线程创建 Handler 会出现异常)。
故:

  • 说明了Handler 必须在有 Looper 的线程中使用。
  • Handler 的 MessageQueue 对象是由当前线程 Looper 的 MessageQueue 对象赋值的。
  • Handler 在创建时绑定了当前线程 Looper 的 MessageQueue 对象。
  • 对于传递 Looper 对象创建 Handler 的情况下,传递的 Looper 是哪个线程的,Handler 绑定的就是该线程。

3、使用 Handler 发送消息
==> sendMessageAtTime(Message msg, long uptimeMillis)

// 除了 sendMessageAtFrontOfQueue,Handler 所有的 post、sendMessage 都会跳到这个方法
// Message msg: 要发送的消息
// long uptimeMillis: 发送消息的绝对时间,通过 SystemClock.uptimeMillis() 加上我们自己的延迟时间 delayMillis 计算而来
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  MessageQueue queue = mQueue;
  // 消息队列为空(可能已经退出)返回 false 入队失败
  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);
}

==> sendMessageAtFrontOfQueue(Message msg)

// 发送消息到 MessageQueeu 的队头
public final boolean sendMessageAtFrontOfQueue(Message msg) {
 MessageQueue queue = mQueue;
 if (queue == null) {
     RuntimeException e = new RuntimeException(
     this + " sendMessageAtTime() called with no mQueue");
     Log.w("Looper", e.getMessage(), e);
     return false;
 }
 // 通过设置 uptimeMillis 为 0,是消息加入到 MessageQueue 的队头
 return enqueueMessage(queue, msg, 0);
}

==> enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)

// 所有 Handler 的 post 、sendMessage 系列方法和 runOnUiThread 最终都会调用这个方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
 // msg.target 是一个 Handler,将 Message 和 Handler 绑定
 // 也就是用哪个 Handler 发送消息,这个 Message 就和哪个 Handler 绑定
 msg.target = this;
 // 如果设置了消息处理方式为异步处理
 if (mAsynchronous) {
     msg.setAsynchronous(true);
 }
 // MessageQueue 的方法,将消息入队
 return queue.enqueueMessage(msg, uptimeMillis);
}

-> MessageQueue#enqueueMessage(Message msg, long when)

boolean enqueueMessage(Message msg, long when) {
     // Messgae 没有绑定 Handler 抛出异常
     if (msg.target == null) {
         throw new IllegalArgumentException("Message must have a target.");
     }
     // Messgae 正在使用 抛出异常
     if (msg.isInUse()) {
         throw new IllegalStateException(msg + " This message is already in use.");
     }
 
     synchronized (this) {
         // 消息队列正在退出,回收 Message
         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();  // 调用 Message#recycleUnchecked() 
             return false;
         }
         msg.markInUse();  // 标记 Message 正在使用
         msg.when = when;  // 设置 Message 的触发时间
       
         // mMessages 记录着 MessageQueue 的队头的消息 
         Message p = mMessages;  
         boolean needWake;
         // MessageQueue 没有消息、Message 触发时间为 0、Messgae 触发时间比队头 Message 早
         // 总之这个 Message 在 MessageQueue 中需要最先被分发
         if (p == null || when == 0 || when < p.when) {
             // New head, wake up the event queue if blocked.
             msg.next = p;     // 将以前的队头 Message 链接在这个 Message 后面
             mMessages = msg;  // 将这个 Message 赋值给 mMessages
             needWake = mBlocked;  // 队列是否阻塞
         } else {
             // 标记队列是否阻塞
             needWake = mBlocked && p.target == null && msg.isAsynchronous();
             Message prev;
           
             // 按照时间顺序将 Message 插入消息队列
             for (;;) {
                 prev = p;   // prev 记录队头
                 p = p.next; // p 记录队头的后一个
                 // 队头后面没有消息或者其触发事件比要插入的 Message 晚,跳出循环
                 if (p == null || when < p.when) {
                     break;
                 }
                 if (needWake && p.isAsynchronous()) {
                     needWake = false;
                 }
             }
             // 将 Message 插入队列
             msg.next = p; 
             prev.next = msg;
         }

         // We can assume mPtr != 0 because mQuitting is false.
         if (needWake) {
             nativeWake(mPtr);
         }
     }
     return true;
 }

总结:到现在为止,我们的 Handler 已经将 Message 发送到了 MessageQueue,Message 静静的等待被处理。

4、Looper.loop() 还记得这个方法在 ActivityThread 的 main 调用了吗?正是它在不断处理 MessageQueue 里面的消息。

public static void loop() {
     // 获取 Looper.Looper.prepare 准备好的 Looper
     final Looper me = myLooper();
     if (me == null) {
         throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
     }
     // 获取 Looper 中的消息队列
     final MessageQueue queue = me.mQueue;

     // 进入无线循环
     for (;;) {
         // 取出下一条消息
         Message msg = queue.next(); 
         
         // 没有消息,退出 loop
         // 其实上面 queue.next() 也是一个无限循环,获取到消息就返回,没有消息就一直循环
         if (msg == null) {
             return;
         }

         try {
             // msg.target 实际上就是一个 Handler
             // 获取到了消息,使用绑定的 Handler#dispatchMessage 分发消息
             msg.target.dispatchMessage(msg);
         } finally {
             
         }

         // 释放消息,把 Message 的各个变量清空然后放进消息池中
         msg.recycleUnchecked();
     }
 }

5、Handler#dispatchMessage(msg) 消息是如何处理的

public void dispatchMessage(Message msg) {
 // 调用 Handler 的 post 系列方法执行handleCallback(msg)
 if (msg.callback != null) {
     handleCallback(msg);
 } else {
     // 创建 Handler 传入 Callback
     if (mCallback != null) {
         if (mCallback.handleMessage(msg)) {
             return;
         }
     }
     // 3. 创建 Handler 时重写的 handleMessage
     handleMessage(msg);
 }
}

从这就可以看出消息分发的优先级:

  • Message 的回调方法:message.callback.run(); 优先级最高;
  • Handler 的回调方法:mCallback.handleMessage(msg)优先级次于上方;
  • Handler 的回调方法:handleMessage() 优先级最低。

Handler 机制总结:想使用 Handler 必须要有 Looper,创建 Looper 的时候会创建 MessageQueue,在 Handler 的构造的时候会绑定这个 Looper 和 MessageQueue,Handler 将 Message 发送到 MessageQueue 中,Looper.loop() 会不断的从 MessageQueue 取出消息再交给这个 Handler 处理。

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

推荐阅读更多精彩内容