Handler机制以及引发的知识点

922 阅读13分钟

背景

Handler 是贯穿于整个应用的消息机制。在咱们印象中,Handler 就是处理子线程和主线程桥梁的作用,其实Android中很多都是用着Handler机制,比如屏幕的刷新,打开Activity,按返回键,等等,都是通过Handler来发消息和处理消息的。现在咱们带着几个问题来说一下Handler。MessageQueue,Message 和 Looper 的关系。

问题

  1. 为什么Android要设计一个只有主线程来更新UI的机制呢?
  2. 一个线程中可以有几个Handler 几个Looper ,几个 MessageQueue?
  3. 一个线程只能有一个Looper,怎样保证的呢?怎样和 MessageQueue 绑定在一起
  4. Looper是怎样从消息队列里面取消息的
  5. Handler 内存泄漏的原因以及处理
  6. 子线程创建Handler的需要处理什么?为什么主线程不需要处理?
  7. Handler是如何先主线程发送消息?
  8. 如果创建 Message?
  9. View.post() 、 Handler.sendMessage() 、Handler.postDelayed()、Handler.sendMessage()和sendMessageDelayed的区别
  10. 当 sendMessageDelay() 的时候,是存的时候限制,还是从MessageQueue里取的时候限制?
  11. 子线程真的不能更新UI吗?
  12. Handler的同步屏障机
  13. Looper 死循环为什么不会让主线程卡死?

Handler消息机制源码追踪

Handler机制就是个典型的生产者消费者模式,只不过是 MessageQueue 的大小是无限制的。这里面有4个兄弟在处理,分别是 Handler ,Looper,MessageQueue 和 Message

  • Handler的作用主要是向 MessageQueue 队列里面发消息 或者 删除消息 ,还有就是用来处理消息的。
  • Looper 相当于一个永动机,无限循环的从 MessageQueue 的头部去取消息,来给 Handler 去处理
  • MessageQueue 就是一个用来存储 Message的一个容器,是一个单链表的容器
  • Message 就是消息,用来被处理,和被发送

Handler的创建以及发送,下面都是Android-29的源码

创建

// 这是咱们最常见的创建方式,即我们在什么线程创建 就是什么线程的Handler,与什么线程的Looper和MessageQueue去绑定
public Handler() {
        this(null, false);
    }

// 这是直接跟传入进来的Looper去绑定
public Handler(Looper looper) {
        this(looper, 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());
            }
        }
// 拿出当前的线程的Looper,如果当前的Looper 为null ,也就是还没有创建Looper的话,直接 抛异常
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        // 取出Looper中的 MessageQueue 
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

发送

// 发送一个消息
 public final boolean sendEmptyMessage(int what){
        return sendEmptyMessageDelayed(what, 0);
 }
 
 
 public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
 // 创建 Message ,用 Message的静态方法区创建。
      Message msg = Message.obtain();
      msg.what = what;
      return sendMessageDelayed(msg, delayMillis);
 }
 
// 这个方法 最终也是走到  sendMessageDelayed 到 sendMessageAtTime
public final boolean post(@NonNull Runnable r) {
     return  sendMessageDelayed(getPostMessage(r), 0);
} 


public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
      if (delayMillis < 0) {
          delayMillis = 0;
      }
      // 最终调用到这个地方,SystemClock.uptimeMillis()是指开机的时间
      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

// 无论从那种方式调用,都会走到这里
public boolean sendMessageAtTime(@NonNull 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);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    // 赋值this就是Handler ,给 Message.target
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 这里调用了 MessageQueue 的插入方法
    return queue.enqueueMessage(msg, uptimeMillis);
}

处理

// 当 从 MessageQueue 里面取出来消息的时候,会调用 这个方法
public void dispatchMessage(Message msg) {
      if (msg.callback != null) {
          handleCallback(msg);
      } else {
          if (mCallback != null) {
              if (mCallback.handleMessage(msg)) {
                  return;
              }
          }
          handleMessage(msg);
      }
  }
  
 // 处理方法,这里是空的,主要是让咱们自己实现,去处理
public void handleMessage(Message msg) {
}

这里的总结:无论是 调用 Handler 里面的任何发送Message都会走到 sendMessageAtTime 方法,最终走到 enqueueMessage,调用到 MessageQueue 的 enqueueMessage方法。View.post()方法其实也是调用Handler.post 方法。其实 Handler还有个 sendMessageAtFrontOfQueue 这个方法是放到 MessageQueue 的前面,优先执行,但是我们要慎用这个方法,有可能导致 MessageQueue 饿死。

然后看Looper的源码

// 构造方法是私有的,传入的参数 代表 是否可以允许退出,绑定 此Looper中的 MessageQueue, 所以说 MessageQueue 和 Looper 是 以及线程。所以都是 一一对应的,一个线程里面只有一个  MessageQueue 一个 Looper
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}


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

// sThreadLocal 使用了 final 修饰的,证明 sThreadLocal 是在同一个Looper中全局唯一,不能被修改的
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

// 准备 Looper
private static void prepare(boolean quitAllowed) {
// 从 sThreadLocal 里面取,如果是不为null ,也就是已经准备好了Looper,再调用此方法直接抛异常,所以 一个线程只有一个Looper
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
   // 如果没有直接把 Looper 设置到 sThreadLocal里面,以后用的时候是直接从 sThreadLocal 这个里面拿的Looper
    sThreadLocal.set(new Looper(quitAllowed));
}

// 开始轮训
public static void loop() {
    final Looper me = myLooper();
    // 如果Looper 为null 直接抛异常
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

// 死循环 去轮训 从 MessageQueue里面取 Message
    for (;;) {
    	// 死循环
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
       /**
         * 省略代码
         */
        try {
        	// 刚才咱们在上面也已经说了,这里的 target就是 Handler,所以到取到 Message 之后,调用handler的方法
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        }  
        /**
         * 省略代码
         */
    
    	// 回收Message
        msg.recycleUnchecked();
    }
}

总结: 这里调用Looper的构造方法时,会创建一个MessageQueue,还有把 Looper 保存到 ThreadLoacl ,让一个线程 只有一个Looper,一个 MessageQueue,当取到 Looper 从 MessageQueue 里面取到 Message 之后,会调用 Message.target.dispatchMessage(msg),也就是 Handler的 dispatchMessage(),最终调用到 Handler的 handleMessage方法。

然后看 MessageQueue 的源码


// 构造方法
MessageQueue(boolean quitAllowed) {
// 是否可以退出
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

// 向 MessageQueue 里面添加 Message, 第二个参数代表 开机时间+delay的时间
boolean enqueueMessage(Message msg, long when) {
// 这里拿到target.也就是 handler
  if (msg.target == null) {
      throw new IllegalArgumentException("Message must have a target.");
  }
  if (msg.isInUse()) {
      throw new IllegalStateException(msg + " This message is already in use.");
  }
// 锁住,防止多线程操作引起 插入问题
  synchronized (this) {
  	// 如果已经退出了,直接抛异常
      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();
          return false;
      }

      msg.markInUse();
      msg.when = when;
      Message p = mMessages;
      boolean needWake;
      if (p == null || when == 0 || when < p.when) {
          // New head, wake up the event queue if blocked.
          // 也就是四插入到MessageQueue的头部,新的头部,如果 此 MessageQueue 正在被阻塞,就唤醒这个 MessageQueue
          msg.next = p;
          mMessages = msg;
          needWake = mBlocked;
      } else {
          // Inserted within the middle of the queue.  Usually we don't have to wake
          // up the event queue unless there is a barrier at the head of the queue
          // and the message is the earliest asynchronous message in the queue.
          // 插入到 MessageQueue 中间 或者 是最后,通常这里我们不必去唤醒此MessageQueue的,因为MessageQueue没有阻塞,除非头部有一个同步屏障,并且是异步消息,才会唤醒
          needWake = mBlocked && p.target == null && msg.isAsynchronous();
          Message prev;
          // 又是for循环去添加 Message 到那个节点,从这里看 就像个单链表结构
          for (;;) {
              prev = p;
              p = p.next;
              if (p == null || when < p.when) {
                  break;
              }
              if (needWake && p.isAsynchronous()) {
                  needWake = false;
              }
          }
          msg.next = p; // invariant: p == prev.next
          prev.next = msg;
      }

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


Message next() {
  /**
   * 省略代码
   */
   // for 循环去取 
  for (;;) {
  /**
   * 省略代码
   */
   // 又是同步去取 Message
      synchronized (this) {
          // Try to retrieve the next message.  Return if found.
          final long now = SystemClock.uptimeMillis();
          Message prevMsg = null;
          Message msg = mMessages;
          // 咱们可以发现 这里竟然有个 msg.target ==null 的情况
          // 其实这就是系统调用的,同步屏障。如果有同步屏障,就会轮训查找 msg.target==null 的Message
          // 在 MessageQueue 中查找下一个异步消息
          if (msg != null && msg.target == null) {
              // Stalled by a barrier.  Find the next asynchronous message in the queue.
              do {
                  prevMsg = msg;
                  msg = msg.next;
              } while (msg != null && !msg.isAsynchronous());
          }
          if (msg != null) {
              if (now < msg.when) {
                  // Next message is not ready.  Set a timeout to wake up when it is ready.
                  // 当这个 Message 时间还没到的时候,设置超时时间以准备就绪时唤醒
                  nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
              } 
       /**
         * 省略代码
         */  
  }
}

// 发送一个同步屏障的消息,即 target==null的消息,优先去执行
private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
    //这个token 就是用来删除的 同步屏障 用的
        final int token = mNextBarrierToken++;
        // 这里创建了一个 Message 但是没有给 target赋值,所以这里发起的同步屏障
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
		/**
         * 省略代码
         */ 
        return token;
    }
}

// 移除同步屏障
 public void removeSyncBarrier(int token) {
 		/**
         * 省略代码
         */ 
 }

总结:当MessageQueue 存取消息都是有同步代码块的,防止多线程操作时 出现问题。当有同步屏障时优先执行的,当handler==null的时候会有同步屏障,比如屏幕刷新,View的绘制等等。比如当发送一个ViewRootImpl里面的scheduleTraversals方法就会发出同步屏障。postDelay的Message的时候,是在取的时候 去 处理这个时间。

然后看 Message 的源码

// 用静态方法区创建,因为 android 里面的消息太多了,不只是咱们自己创建的,还有很多系统内部去创建的。所以这里弄了一个缓存。
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

// 回收这个 Message。把所有的值 制成默认值
void recycleUnchecked() {
      flags = FLAG_IN_USE;
      what = 0;
      arg1 = 0;
      arg2 = 0;
      obj = null;
      replyTo = null;
      sendingUid = -1;
      when = 0;
      target = null;
      callback = null;
      data = null;

      synchronized (sPoolSync) {
          if (sPoolSize < MAX_POOL_SIZE) {
              next = sPool;
              sPool = this;
              sPoolSize++;
          }
      }
  }

总结: Message 的创建 google 建议我们用 obtain 方法去创建,这样可以解决内存

回答一下上面的问题

  1. 为什么Android要设计一个只有主线程来更新UI的机制呢?

    • Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行.假设如果在一个Activity当中,有多个线程去更新UI,页面就会错乱,如果更新UI时都加锁,就会很耗性能。 所以,android给我们提供了一套去主线程更新UI。相当于伪锁吧
    • 并不是主线程,是创建的线程View只能由创建的线程更新,自线程也可以弹dialog,只能自线程去更新
    • 当RootViewImpl没有创建之前,都是可以更新的,只有在RootViewImpl后才会有这个校验
    • 比如当一个TextView 固定宽高,在自线程中setText(),是不会触发的,因为宽高没有变,不会涉及到requestLayout。
  2. 一个线程中可以有几个Handler 几个Looper ,几个 MessageQueue?
    一个线程中可以有很多个Handler ,但是只能有一个Looper和一个MessageQueue,

  3. 一个线程只能有一个Looper,怎样保证的呢?怎样和 MessageQueue 绑定在一起
    android 是通过 ThreadLoacl,来保证Looper的唯一性,在Looper的构造方法中,初始化了 MessageQueue,并且绑定在一起

  4. Looper是怎样从消息队列里面取消息的

Looper 会死循环的从 MessageQueue里面取消息,如果没有消息就会挂起。

  1. Handler 内存泄漏的原因以及处理

通过源码可以看出 Message.target 就是 Handler。如果messsage没有处理完,所以 Handler 也不会释放,而在Activity中 非静态内部类 会 引用到外部类,所以此时 Activity 也不会被释放,可以用 弱引用包裹起来,也可以用静态handler,也可以在 Activity的OnDestroy方法中移除 MessageQueue里面的Message

  1. 子线程创建Handler的需要处理什么?为什么主线程不需要处理?
    当我们在子线程中 new Handler的时候,会抛出 以下异常,所以我们需要先调用 Looper.prepare()和Looper.loop()方法
Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()"

7. 那为什么主线程并不用去初始化Looper呢?
其实在我们android的入口类中 即 ActivityThread 类中的 main方法就是,android的入口。已经为咱们创建了主线程的Looper

public static void main(String[] args) {
 Looper.prepareMainLooper();
 ...
 Looper.loop();
}

8. Handler是如何在子线程中向主线程发送消息?
说简单点,就是当我们在主线程创建Handler的时候,这个Handler已经和主线程的Looper和主线程的MessageQueue已经绑定了。 用主线程的Handler在子线程中使用,往主线程的 MessageQueue 里面发送 Message 都是没问题的。

  1. 如果创建 Message?

使用 Message.obtain() ,可以减少内存开销

  1. View.post() 、 Handler.sendMessage() 、Handler.postDelayed()、Handler.sendMessage()和sendMessageDelayed的区别
    我感觉是没啥区别的,最终都会走到 sendMessageAtTime中,要是有区别的话,是在 View.post()里面和 Handler.post里面都有个Runable,只是处理的时候在这个Runable里面

  2. 当 sendMessageDelay() 的时候,是存的时候限制,还是从MessageQueue里取的时候限制? 是取的时候来处理,当不到时间的时候,记录这个时间以便唤醒

  3. Handler的同步屏障机制
    当View的刷新,界面的刷新等等 会发一个同步消息,来保证这个消息先去执行

由于下面两个问题延伸比较长,所以起了个目录

子线程真的不能更新UI吗?

其实子线程也可以去刷新UI的,比如你在 Activity的onCreate()方法中,找到TextView,直接在子线程中赋值,是没问题的。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv_name = findViewById(R.id.tv_name);
    new Thread(){
        @Override
        public void run() {
            super.run();
                tv_name.setText("子线程更新了");
        }
    }.start();
当在线程中sleep(1000)然后再更新就会报错

会报下面的错误 上面说只有原始线程才能更新此UI,也就是创建的UI线程才能更新UI。所以说 主线程才能更新主线程的UI。子线程可以更新子线程的UI,比如你在子线程

那为什么直接在Activity的onCreate中 子线程更新没问题呢?

其实检测是否是当前线程是在 ViewRootImpl中

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
    	// 这个就是检测当前是否是当前创建Ui的线程
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
// 这里就是使用Handler 发送同步消息,来更新UI。
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

然后ViewRootImp是在 Activity.onCreate() 中还没有创建,可以追溯 ViewRootImp的源码查看什么时候创建的。我们看 ViewRootImp的构造方法,只有一个构造方法,

public ViewRootImpl(Context context, Display display) {}

然后我们全局搜索关键字 new ViewRootImpl 这个关键字,也就是创建时。我们可以看到只有两个类去实例化了这个类。当我们看到 WindowMangerGlobal,就知道和 WindowMange 和 Window有关系,所以我们直接看这个里面的代码。咱们的View都是添加到 Window上的。咱们每个Activity 都至少有一个Window,然后window 是 由WindowManger添加的,咱们都知道当Activity在onResume,中才会显示第一帧,此时在这里创建了ViewRootImpl。具体可查看具体分析

如果View的大小是固定的,比如Textview,在setText的时候,没有必要触发requestLayout,也就不会检测侧线程

Looper 死循环为什么不会让主线程卡死?

我感觉这是是两个不同的问题, 在android 系统中本身就是一个死循环的过程,当这个死循环停止,app也就退出了。比如 MessageQueue 中不只是处理咱们的消息,还会处理系统的消息。轮训涉及到Linux pipe/epoll机制。当 MessageQueue 里面没有消息的时候,会在管道头那里阻塞,当有Message的时候,会唤醒 ,然后继续轮训。 其实当发生ANR的时候,此时系统也会发送一个Message,然后主线程处理来弹出来 ANR的弹窗