Handler机制以及引发的知识点

Handler机制以及引发的知识点

Android小彩虹2021-07-15 22:01:09140A+A-

背景

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。相当于伪锁吧

  1. 一个线程中可以有几个Handler 几个Looper ,几个 MessageQueue?

一个线程中可以有很多个Handler ,但是只能有一个Looper和一个MessageQueue,

  1. 一个线程只能有一个Looper,怎样保证的呢?怎样和 MessageQueue 绑定在一起

android 是通过 ThreadLoacl,来保证Looper的唯一性,在Looper的构造方法中,初始化了 MessageQueue,并且绑定在一起

  1. 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()"
  1. 那为什么主线程并不用去初始化Looper呢?

其实在我们android的入口类中 即 ActivityThread 类中的 main方法就是,android的入口。已经为咱们创建了主线程的Looper

public static void main(String[] args) {
 Looper.prepareMainLooper();
 ...
 Looper.loop();
}
  1. 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里面

  1. 当 sendMessageDelay() 的时候,是存的时候限制,还是从MessageQueue里取的时候限制?

是取的时候来处理,当不到时间的时候,记录这个时间以便唤醒

  1. 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的弹窗

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们