乐趣区

关于android:Android入门教程-HandlerLooper与MessageQueue使用与分析

从源码角度剖析 Handler。有利于应用 Handler 和剖析 Handler 的相干问题。意识 Looper 与 Handler 的关系。

Handler 简介

一个 Handler 容许发送和解决 Message,通过关联线程的 MessageQueue 执行 Runnable 对象。
每个 Handler 实例都和一个独自的线程及其音讯队列绑定。能够将一个工作切换到 Handler 所在的线程中去执行。一个用法就是子线程通过 Handler 更新 UI。

Handler 次要有 2 种用法:

  • 做出打算,在将来某个工夫点执行音讯和 Runnable
  • 线程调度,在其余线程布局并执行工作

要应用好 Handler,须要理解与其相干的 MessageQueueMessageLooper;不能孤立的看 Handler。Handler 就像一个操作者(或者像一个对开发者凋谢的窗口),利用MessageQueueLooper来实现任务调度和解决。

Handler 持有 Looper 的实例,间接持有 looper 的音讯队列。

属性与结构器

Handler 类中持有的实例,持有 looper,messageQueue 等等。

final Looper mLooper; // Handler 持有 Looper 实例
final MessageQueue mQueue; // Handler 持有音讯队列
final Callback mCallback;

在 Handler 的结构器中,咱们能够看到 Handler 获取了 Looper 的音讯队列。

public Handler(Callback callback, boolean async) {
    // rustfisher 解决异样
    mLooper = Looper.myLooper();
    // rustfisher 解决非凡状况...
    mQueue = mLooper.mQueue; // 获取的是 Looper 的音讯队列
}

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue; // 获取的是 Looper 的音讯队列
    mCallback = callback;
    mAsynchronous = async;
}` </pre>

Handler 应用办法

Handler 发送和解决音讯的几个办法

  • void handleMessage(Message msg): 解决音讯的办法,该办法通常被重写。
  • final boolean hasMessage(int what): 查看音讯队列中是否蕴含有 what 属性为指定值的音讯
  • final boolean hasMessage(int what ,Object object) : 查看音讯队列中是否蕴含有 what 好 object 属性指定值的音讯
  • sendEmptyMessage(int what): 发送空音讯
  • final Boolean send EmptyMessageDelayed(int what ,long delayMillis): 指定多少毫秒发送空音讯
  • final boolean sendMessage(Message msg): 立刻发送音讯
  • final boolean sendMessageDelayed(Message msg,long delayMillis): 多少秒之后发送音讯

Handler.sendEmptyMessage(int what) 流程解析

获取一个 Message 实例,并立刻将 Message 实例增加到音讯队列中去。
简要流程如下:

// 立即发送一个 empty 音讯
sendEmptyMessage(int what) 

// 发送提早为 0 的 empty 音讯  这个办法里通过 Message.obtain()获取一个 Message 实例
sendEmptyMessageDelayed(what, 0) 

// 计算音讯的打算执行工夫,进入下一阶段
sendMessageDelayed(Message msg, long delayMillis)

// 在这里判断队列是否为 null  若为 null 则间接返回 false
sendMessageAtTime(Message msg, long uptimeMillis)

// 将音讯增加到队列中
enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)

// 接下来是 MessageQueue 增加音讯
// MessageQueue.java
boolean enqueueMessage(Message msg, long when)

能够看到,最初是把 message 增加到了 messageQueue 中。

Handler 勾销工作

要勾销工作时,调用上面这个办法removeCallbacksAndMessages(Object token)

public final void removeCallbacksAndMessages(Object token) {mQueue.removeCallbacksAndMessages(this, token);
}

通过调用 Message.recycleUnchecked() 办法,勾销掉与此 Handler 相关联的 Message。

相干的音讯队列会执行勾销指令

void removeCallbacksAndMessages(Handler h, Object object)

音讯驱动与 Handler

Android 是音讯驱动的,实现音讯驱动有几个因素

  • 音讯的示意:Message
  • 音讯队列:MessageQueue
  • 音讯循环,用于循环取出音讯进行解决:Looper
  • 音讯解决,音讯循环从音讯队列中取出音讯后要对音讯进行解决:Handler

初始化音讯队列

在 Looper 结构器中即创立了一个 MessageQueue,Looper 持有音讯队列的实例。

发送音讯

通过 Looper.prepare 初始化好消息队列后就能够调用 Looper.loop 进入音讯循环了,而后咱们就能够向音讯队列发送音讯,音讯循环就会取出音讯进行解决,在看音讯解决之前,先看一下音讯是怎么被增加到音讯队列的。

音讯循环

Java 层的音讯都保留在了 Java 层 MessageQueue 的成员 mMessages 中,Native 层的音讯都保留在了 Native Looper 的 mMessageEnvelopes 中,这就能够说有两个音讯队列,而且都是按工夫排列的。

Message 和 MessageQueue

与 Handler 工作的几个组件 Looper、MessageQueue 各自的作用:

  • Handler:它把音讯发送给 Looper 治理的 MessageQueue , 并负责解决 Looper 分给它的音讯
  • MessageQueue:治理 Message,由 Looper 治理
  • Looper:每个线程只有一个 Looper,比方 UI 线程中,零碎会默认的初始化一个 Looper 对象,它负责管理 MessageQueue,一直的从 MessageQueue 中取音讯,并将绝对应的音讯分给 Handler 解决。

Message

Message 属于被传递,被应用的角色。Message 是蕴含形容和任意数据对象的“音讯”,能被发送给 Handler。Message 蕴含 2 个 int 属性和一个额定的对象。尽管结构器是公开的,但获取实例最好的方法是调用 Message.obtain()Handler.obtainMessage()。这样能够从他们的可回收对象池中获取到音讯实例。一般来说,每个 Message 实例持有一个 Handler。

Message 局部属性值

/*package*/ Handler target; // 指定的 Handler

/*package*/ Runnable callback;

// 能够组成链表
// sometimes we store linked lists of these things
/*package*/ Message next;

从这里也不难看出,每个 Message 都持有 Handler 实例。如果 Handler 持有 Activity 的援用,Activity onDestroy 后 Message 却依然在队列中,因为 Handler 与 Activity 的强关联,会造成 Activity 无奈被 GC 回收,导致内存泄露。
因而在 Activity onDestroy 时,与 Activity 关联的 Handler 应革除它的队列由 Activity 产生的工作,防止内存泄露。

重置本身的办法,将属性全副重置

public void recycle()
void recycleUnchecked()

获取 Message 实例的罕用办法,失去的实例与传入的 Handler 绑定

/**
 * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
 * @param h  Handler to assign to the returned Message object's <em>target</em> member.
 * @return A Message object from the global pool.
 */
public static Message obtain(Handler h) {Message m = obtain();
    m.target = h;

    return m;
}

将音讯发送给 Handler

/**
 * Sends this Message to the Handler specified by {@link #getTarget}.
 * Throws a null pointer exception if this field has not been set.
 */
public void sendToTarget() {target.sendMessage(this); // target 就是与音讯绑定的 Handler
}

调用这个办法后,Handler 会将音讯增加进它的音讯队列 MessageQueue 中。

MessageQueue

持有一列能够被 Looper 散发的 Message。一般来说由 Handler 将 Message 增加到 MessageQueue 中。获取以后线程的 MessageQueue 办法是Looper.myQueue()。通过 Looper.getMainLooper() 获取到主线程的 looper。

Looper 简介

Looper 与 MessageQueue 严密关联。在一个线程中运行的音讯循环。线程默认状况下是没有与之治理的音讯循环的。
要创立一个音讯循环,在线程中调用 prepare,而后调用 loop。即开始解决音讯,直到循环进行。大多数状况下通过 Handler 来与音讯循环互动。

Handler 与 Looper 在线程中交互的典型例子

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {Looper.prepare(); // 为以后线程筹备一个 Looper
        // 创立 Handler 实例,Handler 会获取以后线程的 Looper
        // 如果实例化 Handler 时以后线程没有 Looper,会报异样 RuntimeException
        mHandler = new Handler() {public void handleMessage(Message msg) {// process incoming messages here}
        };
        Looper.loop(); // Looper 开始运行}
} 

调用了 Looper.loop() 之后,looper 开始运行。当 looper 的 messageQueue 中没有音讯时,相干的线程处于什么状态呢?查看 looper 的源码,看到 loop 办法外面有一个死循环。queue.next()办法是可能会阻塞线程的。如果从 queue 中获取到 null,则表明此音讯队列正在退出。此时 looper 的死循环也会被返回。

for (;;) {Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }

调用 looper 的 quit 办法,实际上调用了 mQueue.quit(false)。音讯队列退出后,looper 的 loop 死循环也被退出了。

进入 MessageQueue 的 next 办法去看,发现外面也有一个死循环。没有音讯时,这个死循环会阻塞在 nativePollOnce 这个办法。

Message next() {
    // ...
    for (;;) {
        // ...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        // 解决 message 对象

咱们晓得 Thread 有 New(新建,未运行),RUNNABLE(可运行),BLOCKED,WAITING(线程领有了某个锁之后, 调用了他的 wait 办法, 期待其余线程 / 锁拥有者调用 notify / notifyAll),TIMED_WAITING,TERMINATED(曾经执行结束)这几种状态。

音讯队列中没有音讯,在 nativePollOnce 办法中“期待”。nativePollOnce成果上大抵等同于 Object.wait(),但它们的实现齐全不同。nativePollOnce 应用 epoll, 而 Object.wait 应用 futex

“期待”时,相干线程则处于 WAITING 状态。

Looper 中的属性

Looper 持有 MessageQueue;惟一的主线程 Looper sMainLooper;Looper 以后线程 mThread;存储 Looper 的 sThreadLocal

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class

final MessageQueue mQueue; // Handler 会获取这个音讯队列实例(参考 Handler 结构器)final Thread mThread; // Looper 以后线程

ThreadLocal 并不是线程,它的作用是能够在每个线程中存储数据。

Looper 办法

筹备办法,将以后线程初始化为 Looper。退出时要调用 quit

public static void prepare() {prepare(true);
}

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)); // Looper 实例存入了 sThreadLocal
}

prepare办法新建 Looper 并存入 sThreadLocal sThreadLocal.set(new Looper(quitAllowed))

ThreadLocal<T>

public void set(T value) {Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

public T get() {Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();}

当要获取 Looper 对象时,从 sThreadLocal 获取

// 获取与以后线程关联的 Looper,返回能够为 null
public static @Nullable Looper myLooper() {return sThreadLocal.get();
}

在以后线程运行一个音讯队列。完结后要调用退出办法quit()

public static void loop()

筹备主线程 Looper。Android 环境会创立主线程 Looper,开发者不应该本人调用这个办法。
UI 线程,它就是 ActivityThread,ActivityThread 被创立时就会初始化 Looper,这也是在主线程中默认能够应用 Handler 的起因。

public static void prepareMainLooper() {prepare(false); // 这里示意了主线程 Looper 不能由开发者来退出
    synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();}
}

获取主线程的 Looper。咱们开发者想操作主线程时,可调用此办法

public static Looper getMainLooper()

同一个 Thread 的不同 Handler

与 UI 线程对应的 MainLooper,能够关联多个 Handler。多个 Handler 之间的打算工作不会相互影响。比方有 2 个关联了 UI 线程的 handler。

Handler mMainHandler1;
Handler mMainHandler2;

private void initUtils() {mMainHandler1 = new Handler(Looper.getMainLooper());
    mMainHandler2 = new Handler(Looper.getMainLooper());
    Log.d(TAG, "mMainHandler1 post 工作");
    mMainHandler1.postDelayed(new Runnable() {
        @Override
        public void run() {Log.d(TAG, "mMainHandler1 的演示工作已执行 rustfisher");
        }
    }, 1500);
    mMainHandler2.removeCallbacksAndMessages(null);
}

mMainHandler2 勾销它的工作并不会影响 mMainHandler1。

Handler 相干面试题

1. ⼦线程⼀定不能更新 UI 吗?

答:不⼀定。

  • Activity 存在⼀种审计机制,这个机制会在 Activity 齐全显示之后⼯作,如果⼦线程在 Activity 齐全显示
    之前更新 UI 是可⾏的;
  • SurfaceView:多媒体视频播放,也能够在⼦线程中更新 UI
  • Progress:进度相干控件,也能够在⼦线程中更新 UI

2. 给我说说 Handler 的原理

3. Handler 导致的内存泄露你是如何解决的?

4. 如何使⽤ Handler 让⼦线程和⼦线程通信?

  • 发送音讯的⼦线程

      package com.cdc.handler;
      import android.os.Handler;
      import android.os.Message;
      import android.os.SystemClock;
      // 发送音讯的⼦线程
      public class Thread1 extends Thread {
           private Handler handler;
           public Thread1(Handler handler){super.setName("Thread1");
               this.handler=handler;
           }
           @Override
           public void run() {Message msg = Message.obtain();
               msg.what = 1;
               msg.obj = System.currentTimeMillis()+"";
               handler.sendMessage(msg);
               System.out.println((Thread.currentThread().getName() + "---- 发送了消
    息!" + msg.obj));
              SystemClock.sleep(1000);
      }
    }
  • 接管音讯的⼦线程

      package com.cdc.handler;
      import android.os.Handler;
      import android.os.Looper;
      // 接管音讯的⼦线程
      public class Thread2 extends Thread{
          private Handler handler2;
          public Handler getHandler(){// 留神哦,在 run 执⾏之前,返回的是 null
              return handler2;
          }
          public Thread2(){super.setName("Thread2");
          }
          @Override
          public void run() {// 在⼦线程⾥⾯新建 Handler 的实例,须要先调⽤ Looper.prepare(); 否则会报
    错:Can't create handler inside thread that has not called Looper.prepare()
              Looper.prepare();
              handler2 = new Handler(){public void handleMessage(android.os.Message msg) {
                  // 这⾥解决音讯
                  System.out.println(("收到音讯了:" +
    Thread.currentThread().getName() + "----" + msg.obj));
              };
          };
          Looper.loop();}
    }
  • 调⽤

    private Handler myHandler=null;
    private Thread2 thread1;
    private Thread1 thread2;
    @OnClick(R.id.handler3)
    public void handler3(){thread1=new Thread2();
      thread1.start();
      myHandler=thread1.getHandler();
      while(myHandler==null){SystemClock.sleep(100);
          myHandler=thread1.getHandler();}
      thread2=new Thread1(myHandler);
      thread2.start();}

5. HandlerThread 是什么 & 原理 & 使⽤场景?

6. IdleHandler 是什么?

7. ⼀个线程是否创立多个 Handler,Handler 和 Looper 之间的对应关系?

8 为什么 Android 零碎不倡议⼦线程拜访 UI?
⾸先,UI 控件不是线程平安的,如果多线程并发拜访 UI 控件可能会呈现不可预期的状态
那为什么零碎不对 UI 控件的拜访加上锁机制呢?毛病有两个:

  • 加上锁机制会让 UI 拜访的逻辑变得复杂;
  • 锁机制会升高 UI 拜访的效率,因为锁机制会阻塞某些线程的执⾏

鉴于这两个毛病,最简略且⾼效的⽅法就是采⽤单线程模型来解决 UI 操作,所以源码 ViewRootImpl 中会有对线程的⼀个判断,代码如下:frameworks/base/core/java/android/view/ViewRootImpl.java

void checkThread() {if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can
touch its views.");
     }
 }

对于开发者来说也不是很麻烦,只是通过 handler 切换⼀下 UI 拜访的执⾏线程即可

9. Looper 死循环为什么不会导致应⽤卡死?

10. 使⽤ Handler 的 postDealy 后音讯队列有什么变动?

Android 零根底入门教程视频参考

退出移动版