当前位置: 首页 > news >正文

Handler机制

目录

  • 一、简介
  • 二、相关概念解释
    • 2.1 Message(消息)
    • 2.2 Handler(处理器)
      • 2.2.1 Handler的构造方法
      • 2.2.2 Handler sendMessage()相关的方法
      • 2.2.3 Handler dispatchMessage()方法
    • 2.3 MessageQueue(消息队列)
    • 2.4 Looper(循环器)
  • 三、引申问题
    • 3.1 主线程的looper是如何创建的?
    • 3.2 一个线程可以有几个Handler?Looper?
    • 3.3 Handler线程间通信的原理是怎样的?
    • 3.4 Handler的内存泄露原因?
    • 3.5 为何主线程可以new Handler?如果想要在子线程中new Handler 要做什么准备?
    • 3.6 子线程中维护的looper,消息队列无消息的时候的处理方案是什么?有什么用?主线程呢?
    • 3.7 使用Message时如何创建?
    • 3.8 handler的消息阻塞是怎么实现的?


一、简介

Handler是一套 Android 消息传递机制,主要用于线程间通信。
用最简单的话描述: handler其实就是主线程在起了一个子线程,子线程运行并生成Message,Looper获取message并传递给Handler,Handler逐个获取子线程中的Message并处理。
Binder/Socket用于进程间通信,而Handler消息机制用于同进程的线程间通信
可以说只要有异步线程与主线程通信的地方就一定会有 Handler。

在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理:

你想刷新主界面的TextView,无奈你不在主线程,此时你就会包装好Message,然后声明一个Handler,让Handler将你的Message送往主线程(Looper),Handler将你的Message送到主线程后,还需要排队等待,等轮到你的时候,主线程就会告诉Handler,这个Message可以处理了,你负责分发一下,于是,Handler将该Message分发到相应的回调或者handleMessage( ) 方法中,于是,你就在该方法中更新了UI。

在这里插入图片描述


二、相关概念解释

可以将Handler相关的原理机制形象的描述为以下情景:

Handler:快递员(属于某个快递公司的职员)
Message:包裹(可以放置很多东西的箱子)
MessageQueue:快递分拣中心(分拣快递的传送带)
Looper:快递公司(具有处理包裹去向的管理中心)

2.1 Message(消息)

Message.class位于android.os.包中。Message的构造函数为无参构造方法,且只有一个构造方法;

public Message() { }

除了构造方法可以创建实例对象外,还可以通过内部的静态方法来创建:

static Message obtain()
static Message obtain(Message orig)
static Message obtain(Handler h)
static Message obtain(Handler h, Runnable callback)
static Message obtain(Handler h, int what)
static Message obtain(Handler h, int what, Object obj)
static Message obtain(Handler h, int what, int arg1, int arg2)
static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)

以上几个静态的方法里面都会首先调用第一个方法来创建一个Message对象,来看看源码

	public static final Object sPoolSync = new Object();    //同步锁对象,用于同步访问消息池的对象。通过synchronized (sPoolSync)来确保在获取和释放消息对象时的线程安全性。private static Message sPool;                           //全局的消息对象池,用于存储可重用的消息对象。private static int sPoolSize = 0;					    //表示当前消息池中的消息对象数量。/*** 从全局池返回一个新的消息实例,允许我们在许多情况下避免分配新对象。*/public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}

如果当前全局池的Message实例不为空,则返回第一个消息实例。所以,大多数情况下,使用obtain()来获得一个Message对象,可以避免消耗更多的内存资源。

对于其他 static obtain( ) 的重载方法,通过源码,可以发现,都是进行赋值操作,没有太多的可讨性。唯一得注意一下的是 obtain(Handler h, Runnable callback)这个静态方法:

	/*package*/ Handler target;/*package*/ Runnable callback;public static Message obtain(Handler h, Runnable callback) {Message m = obtain();m.target = h;m.callback = callback;return m;}

可以看到,也是赋值操作,target是保护级别的成员变量,即只有同包名空间可以访问,此变量意义重大。除了target,还有一个callback,这个callback也是配合着Handler来发挥作用的。

此时,需要记住的就几点:

Message有8个静态方法可以创建Message实例
Message有两个重要的成员变量,分别为target 和callback,一个是Handler,一个是Runnable。
Message有4个公开变量what、arg1、arg2、obj 可以存储消息进行传递
Message还有一个包间成员变量next,它是Message类型,后面会使用到,知道有这个next 就行

以上就是Message的基本秘密了,很简单,没有什么复杂的东西(作为一个包裹箱,就是这么简单,能装一些东西,然后附带一些关键信息)。

2.2 Handler(处理器)

Handler.class也位于android.os包中。Handler英文意思为:处理者,管理者,处理机。它在消息传递过程中扮演着重要的角色,是消息的主要处理者,说白了,就是收消息,最终处理消息(它就像一个快递员,收快递,然后领快递单,派送快递)。

2.2.1 Handler的构造方法

Handler()
Handler(Callback callback)
Handler(boolean async)
Handler(Callback callback, boolean async)
Handler(Looper looper)
Handler(Looper looper, Callback callback)
Handler(Looper looper, Callback callback, boolean async)

通过源码可以发现,上面的构造方法都是上面一个个往下调用的,第一个调用第二个,第二个调用第三个…所以,我们首先把目光放在最后一个方法上:

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

这是一个赋值的构造方法。再看另外一个构造方法:

	public Handler(Callback callback, boolean async) {//......mLooper = Looper.myLooper();  //返回与当前线程关联的Looper对象,在后面Looper会讲到if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;  //返回Looper对象的消息队列,在后面MessageQueue会讲到mCallback = callback;  //接口回调mAsynchronous = async; //是否异步}public interface Callback {public boolean handleMessage(Message msg); //这个函数大家都很熟悉了,暂不细说,总之都知道是用来回调消息的}

整个构造方法的过程中会确立以下几件事:

  • 获取当前Handler实例所在线程的Looper对象:mLooper = Looper.myLooper()
  • 如果Looper不为空,则获取Looper的消息队列,赋值给Handler的成员变量mQueue:mQueue = mLooper.mQueue
  • 可以设置Callback 来处理消息回调:mCallback = callback

Handler是消息的处理者,但是它并不是最终处理消息的那个大佬,它有且只能有一个上级个领导,就是Looper,Handler是将消息上报给Looper(领导),然后排队等待,等Looper(领导)处理完消息了,就会通知Handler去领取消息,给Handler分配任务,Handler拿到消息后在自行往下分发,Handler只能听命与Looper(领导)。

举个实际运用中的情景
当你需要在子线程中更新主线程的UI时,你就会在当前的Activity下创建一个Handler对象,然后在它的handleMessage() 中更新UI。

	private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);//... 更新UI}};

在你创建这个mHandler 实例的时候,底层做了以下几件事情:

1、拿到mHandler所在线程的Looper,当前mHandler是在Activity中创建的,很明显,当前的线程就是主线程,所以 mHandler的成员变量mLooper = Looper.myLooper(),此处就已经将当前的主线程Looper赋值过去了。
2、紧接着,判断mLooper 是否为空,明显不为空,所以又会将主线程的消息队列赋值给mQueue。告诉Handler,你要是有消息,就送到这个消息队列中来,我(Looper)会一个个按顺序处理,处理完后我就会告诉你,你再处理。

由此我们可以得出结论

1、Handler有且只能绑定一个线程的Looper
2、Handler的消息是发送给Looper的消息队列MessageQueue,需要等待处理

所以,如果你在子线程中声明了一个Handler,是不能直接更新UI的,需要调用Handler相关的构造方法,传入主线程的Looper,这样创建的Handler实例,你才能进行UI的更新操作。另外的,需要注意的是,子线程默认是没有开启专属的Looper,所以,在子线程中创建Handler之前,你必须先开启子线程的Looper,否则就会爆出异常,然后GG。从上面贴出的构造方法中的部分就可以知道:

	if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}

以上就是创建Handler的过程,有了Handler实例了,怎样传递消息呢?

2.2.2 Handler sendMessage()相关的方法

首先上一幅图来表明sendXXXMessageXXX()的相互调用关系:

在这里插入图片描述

可以看出,当我们调用Handler进行发送消息时,最终都会调用sendMessageAtTime()方法,最后调用enqueueMessage( ) 发送到消息队列。

	public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;  //获得当前的消息队列if (queue == null) {   //若是在创建Handler时没有指定Looper,就不会有对应的消息队列queue ,自然就会为nullRuntimeException 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(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;   //这个target就是前面我们说到过的if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}
  • msg.target = this
    在发送消息到消息队列之前,明确的指定了消息的target为当前的Handler,以便于在后面Looper分发消息时用到。
  • queue.enqueueMessage(msg, uptimeMillis)
    然后调用了消息队列的enqueueMessage()方法,并传递了两个参数,一个Message,一个是long型的时间。

以上就是Handler的创建和发送消息的过程。

2.2.3 Handler dispatchMessage()方法

前面说了消息的发送,交给Looper等待处理,处理完后会重新通知Handler处理,那么,是怎样通知Handler处理消息的呢?秘密就在dispatchMessage()这个方法中:

	/*** 在这里处理系统消息*/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) {}

当Looper处理完Message后,会使用到Message的target,即上面说到的target,即发送消息的那个Handler,Looper会调用Handler的dispatchMessage()方法分发消息,所以前面在enqueueMessage()发送消息的时候,为什么非得指明Message的target就是这个道理。

回到dispatchMessage()这个方法:

1、首先会判断Message的callback是否为空,此处的callback就是前面我们在Message中说到的,在静态方法创建Message时,可以指定的callback,若不为空,则将结果回调到callback中;
2、若Handler的mCallback 不为空,也一样的道理。
3、平时我们都没有传入这个callback,而是直接实现handleMessage()这个方法,在这个方法中处理更新UI任务。

以上就是Handler发送和接收消息的基本过程:把消息发送到队列—>然后喝茶等待—>接收消息—>分发消息—>在回调中处理。

2.3 MessageQueue(消息队列)

前面我们知道,Handler发送消息会调用MessageQueue的enqueueMessage()方法,直接上源码

	boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {  //判断msg的所属Handlerthrow new IllegalArgumentException("Message must have a target.");}//......synchronized (this) {  //因为是队列,有先后之分,所以用了同步机制//......msg.when = when;Message p = mMessages;  //对列中排在最后的那个Message //......if (p == null || when == 0 || when < p.when) {    //若队列为空,或者等待时间为0,或者比前面那位的等待时间要短,就插队msg.next = p;  //此处的next就是前面我们在Message提到的,指向队列的下一个结点mMessages = msg;//......} else { //......Message prev;for (;;) {     //此处for循环是为了取出一个空的或者when比当前Message长的一个消息,然后进行插入prev = p;p = p.next;if (p == null || when < p.when) {break;}//......}msg.next = p;       // 置换插入prev.next = msg;  // 置换插入}//......}return true;}

以上就是消息队列插入消息的过程原理,通过链表的数据结构来存储消息。既然有了插入消息的方法供Handler插入消息,那么应该有对应的取出消息的方法,供Looper 调用取出消息处理,它就是Message next()这个方法,代码就不贴了,自行前往查看,过程还是挺简单的。

2.4 Looper(循环器)

Looper在Handler机制中扮演着关键的一环,他是循环处理消息的发动机,永不停息(永动鸡),它不断的从消息队列中取出的消息,处理,然后分发处理事件。每个线程都可以且只能绑定一个Looper。主线程之所以能处理消息,也是因为在APP启动时,在ActivityThread中的main()方法中就已经启动了Looper循环。
下面直接上Looper关键方法loop( )的源码

public static void loop() {final Looper me = myLooper();   //获得当前的Looperif (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;  //获取当前Looper的消息队列//......for (;;) {Message msg = queue.next();  //取出队头的消息if (msg == null) {// 如果消息为空,则跳过,继续执行下一个messagereturn;}//......try {msg.target.dispatchMessage(msg);//......} finally {//......}//......msg.recycleUnchecked();  //回收可能正在使用的消息}}

由此可见,Looper的处理消息的循环还是挺简单的,就是拿出消息,然后分发,然后回收 … …

综上所述,Handler机制可以简述为

Handler将Message发送到Looper的消息队列中,即MessageQueue,等待Looper的循环读取Message,处理Message,然后调用Message的target,即附属的Handler的dispatchMessage()方法,将该消息回调到handleMessage()方法中,然后完成更新UI操作。

三、引申问题

3.1 主线程的looper是如何创建的?

创建主线程时,会自动调用ActivityThread的1个静态的main();
而main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时也会生成其对应的MessageQueue对象。为进程中的管理进行初始化。

ActivityThread.java

    public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// Install selective syscall interceptionAndroidOs.install();// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);// Call per-process mainline module initialization.initializeMainlineModules();Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.// It will be in the format "seq=114"long startSeq = 0;if (args != null) {for (int i = args.length - 1; i >= 0; --i) {if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {startSeq = Long.parseLong(args[i].substring(PROC_START_SEQ_IDENT.length()));}}}ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}

3.2 一个线程可以有几个Handler?Looper?

可以创建多个Handler实例,但是只能能有一个Looper!

为什么?看一下Looper创建过程prepare,涉及到ThreadLocal机制,当从sThreadLocal(一个map缓存对象)获取不到时会报错
Only one Looper may be created per thread,一个线程只能创建一个looper,ThreadLocal机制的核心就在于线程隔离。

    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));}

在一个Android线程中,可以创建多个Handler实例,每个Handler实例都有自己的消息队列,并且能够独立地发送和处理消息。
每个线程只能拥有一个Looper对象,以确保消息的有序处理。

在主线程(UI线程)中,默认已经创建了一个Looper实例,因此可以直接使用Handler来发送和处理消息。在其他线程中,如果需要使用Handler进行消息通信,则需要显式地为该线程创建一个Looper实例。

一个线程中有几个Handler?几个Looper?怎么保证?

3.3 Handler线程间通信的原理是怎样的?

一般情况下:

子线程 发送MSG: handler.sendMessage()
主线程 取消息、处理消息:· handler.handleMessage()

消息发送过程:
Handler.java
sendMessage( Message msg) MessageQueue添加消息的过程 Handler.java
 —>sendMessageDelayed(msg, 0)
  —>sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
   —>enqueueMessage(queue, msg, uptimeMillis)
    —>MessageQueue.enqueueMessage(Message msg, long when) MessageQueue.java

处理消息过程:
ActivityThread.main() ActivityThread.java
 —>Looper.loop() Looper.java
  —>loopOnce(me, ident, thresholdOverride)
   —>MessageQueue.next
    —> msg.target.dispatchMessage(msg);
     —> Handler.dispatchMessage(msg); Handler.java
       —>Handler.handleMessage()

线程间内存共享 核心 : MessageQueue 共享
每个Looper对象都有一个MessageQueue,用于存放待处理的消息。一个线程中的Handler可以往自己所关联的Looper的MessageQueue中发送消息,其他线程的Handler则可以通过向目标Handler所关联的Looper的MessageQueue中发送消息来实现线程间通信。

3.4 Handler的内存泄露原因?

主线程的Looper对象的生命周期 = 该应用程序的生命周期
在Java中,非静态内部类 & 匿名内部类都默认持有 外部类的引用

static ThreadLocal—》Looper—》MessageQueue—》Message—》Handler—》Activity.this
形成了一条持有链,只要有前面的不释放,导致后续的东西都不会被GC杀死,从而导致内存泄漏

泄露原因:

  • 当Handler消息队列 还有未处理的消息 / 正在处理消息时,存在引用关系: “未被处理 / 正处理的消息 -> Handler实例 -> 外部类”
  • 若出现 Handler的生命周期 > 外部类的生命周期 时(即 Handler消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁时),将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露

解决方案:
静态内部类 + 弱引用的方式+当外部类结束生命周期时,清空Handler内消息队列

原理:静态内部类不默认持有外部类的引用,从而使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 不存在。
弱引用的特点: 在垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 所以用户在关闭 Activity 之后,就算后台线程还没结束,但由于仅有一条来自 Handler 的弱引用指向 Activity,Activity 也会被回收掉。这样,内存泄露的问题就不会出现了。

static class MyHandler extends Handler {WeakReference<Activity > mActivityReference;MyHandler(Activity activity) {mActivityReference= new WeakReference<Activity>(activity);}@Overridepublic void handleMessage(Message msg) {final Activity activity = mActivityReference.get();if (activity != null) {//...}}
}@Overrideprotected void onDestroy() {super.onDestroy();mHandler.removeCallbacksAndMessages(null);// 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期}

详解 Handler 内存泄露的原因
Handler造成内存泄漏的原因以及解决方案

3.5 为何主线程可以new Handler?如果想要在子线程中new Handler 要做什么准备?

主线程 在new Handler前已经有了loop的创建和循环

想要在子线程中new Handler,需要创建looper和loop循环
Looper. prepare()以及Looper.loop()

3.6 子线程中维护的looper,消息队列无消息的时候的处理方案是什么?有什么用?主线程呢?

在子线程中维护的Looper和消息队列,当消息队列中没有消息时,一般会进入阻塞状态,等待新的消息到来或者手动退出循环,以节省CPU资源。

如何退出阻塞?
如果在子线程中创建了一个Handler,那么就必须做三个操作:

  1. prepare();
  2. loop();
  3. quit();

也可以使用官方推荐的HandlerThread

loop()操作会让Looper一直处于一个阻塞状态。它就一直在等待,这个子线程也已经干不了其他的事情了,其实也被卡死了。
有什么用:释放线程

而在主线程中,通常是在UI线程中维护的Looper和消息队列。当消息队列无消息时,主线程仍需要保持响应,因此不能使用阻塞的方式等待新消息到来。在主线程中的消息队列无消息时,系统会继续处理UI事件,并执行UI线程中的其他任务。

参考链接:
子线程中:new Handler需要做哪些准备?
Android HandlerThread 详解

3.7 使用Message时如何创建?

创建一个Message对象,可以通过以下方式创建Message对象:

Message message = Message.obtain(); // 使用Message的obtain方法获取一个Message对象,Android会尝试从消息池中获取一个空闲对象,避免频繁创建对象
// 设置Message的参数
message.what = YOUR_WHAT_VALUE;  // 设置消息类型标识,用于处理不同类型的消息
message.obj = YOUR_OBJECT;  // 设置消息内容,可以是任意类型的对象
// 其他需要设置的参数,如arg1, arg2, data等

向Handler发送消息,使得Handler在自己关联的线程中处理这个消息:

handler.sendMessage(message); // 向Handler发送消息

3.8 handler的消息阻塞是怎么实现的?

handler的消息阻塞实现机制:
Loop.java

    public static void loop() {......for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {return;}......}

loop方法虽然是一个死循环,但是内部会检测loopOnce的返回值,如果为false,那么还是会退出阻塞状态,退出loop循环。

再来看loopOnce方法,当发现队列里的消息为空时,会返回false。

    private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return false;}......

也就是说,如果loop发现消息为空时,看样子那么就会退出循环机制,否则将会阻塞接收、分发消息。
可是不对啊!子线程没有消息时,默认是阻塞的啊?

没错,其实me.mQueue.next()即MessageQueue的next()方法中,仍然有阻塞机制,

主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。 Gityuan–Handler(Native层)

    Message next() {// Return here if the message loop has already quit and been disposed.// This can happen if the application tries to restart a looper after quit// which is not supported.final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}nativePollOnce(ptr, nextPollTimeoutMillis);......

主线程的Looper在初始化时,看代码

//子线程常用的Looperpublic static void prepare() {prepare(true);}//主线程的Looper@Deprecatedpublic static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}//这里是重点  quitAllowedsThreadLocal.set(new Looper(quitAllowed));}

主线程和子线程初始化Looper的时候new Looper(quitAllowed)的值一个是true,一个是false,new Looper(quitAllowed)做了什么?

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

quitAllowed是MessageQueue初始化的构造参数,quitAllowed在里面干了什么?

    MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;mPtr = nativeInit();}

MessageQueue代码中mQuitAllowed是一个boolean 类型,且只在quit方法中使用了。

    // True if the message queue can be quit.@UnsupportedAppUsageprivate final boolean mQuitAllowed;void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {if (mQuitting) {return;}mQuitting = true;if (safe) {removeAllFutureMessagesLocked();} else {removeAllMessagesLocked();}// We can assume mPtr != 0 because mQuitting was previously false.nativeWake(mPtr);}
}

主线程的mQuitAllowed值false,默认消息队列永远不会退出!
子线程可以通过quit方法最后唤醒,而主线程默认永远不会退出

   void quit(boolean safe) {if (!mQuitAllowed) {//注意,主线程是不能退出消息循环的throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {if (mQuitting) {//如果当前循环消息已经退出了,直接返回return;}mQuitting = true;//该标记十分重要,十分重要if (safe) {//如果是安全退出removeAllFutureMessagesLocked();} else {//如果不是安全退出removeAllMessagesLocked();}nativeWake(mPtr);//叫醒等待}}

另外看一下如果子线程looper.loop(),那么默认会阻塞,如果此时调用quit()方法
nativeWake(mPtr)这个函数的调用,就会叫醒等待的地方,醒来之后,就接着往下执行。

    //native的方法,在没有消息的时候回阻塞管道读取端,只有nativePollOnce返回之后才能往下执行//阻塞操作,等待nextPollTimeoutMillis时长nativePollOnce(ptr, nextPollTimeoutMillis);

往下执行后,发现 Message msg = mMessages; 是空的,然后就执行了这个,就接着往下走。

                if (msg != null) {......} else {// No more messages.//没有消息,nextPollTimeoutMillis复位nextPollTimeoutMillis = -1;}

然后又调用了这个方法,并且return了null。

                // Process the quit message now that all pending messages have been handled.//如果消息队列正在处于退出状态返回null,调用dispose();释放该消息队列if (mQuitting) {dispose();return null;}

所以说,这个时候Looper就结束了(跳出了死循环),则达成了第二个作用:释放线程。

相关文章:

Handler机制

目录 一、简介二、相关概念解释2.1 Message&#xff08;消息&#xff09;2.2 Handler&#xff08;处理器&#xff09;2.2.1 Handler的构造方法2.2.2 Handler sendMessage&#xff08;&#xff09;相关的方法2.2.3 Handler dispatchMessage&#xff08;&#xff09;方法 2.3 Mes…...

鸿蒙实现金刚区效果

前言&#xff1a; DevEco Studio版本&#xff1a;4.0.0.600 所谓“金刚区"是位于APP功能入口的导航区域&#xff0c;通常以“图标文字”的宫格导航的形式出现。之所以叫“金刚区”&#xff0c;是因为该区域会随着业务目标的改变&#xff0c;展示不同的功能图标&#xff…...

Ubuntu 查看设备温度

要在Ubuntu中查看设备的温度&#xff0c;可以使用一些命令行工具来获取系统硬件的温度信息。下面列出了几种常用的方法&#xff1a; 方法 1: 使用 sensors 命令 sensors 命令用于读取和显示系统中的传感器数据&#xff0c;包括CPU温度和其他硬件传感器的信息。首先需要安装 l…...

大型网站优化指南:打造流畅的在线体验

大型网站 大型网站是指具有高并发、大流量、高可用性、海量数据处理能力&#xff0c;并能提供7*24小时不间断服务的网站。 这些网站通常面临用户分布广泛、网络情况复杂、安全环境恶劣等挑战。 同时需要快速适应市场变化和用户需求&#xff0c;通过渐进式的发展策略运营成大型…...

Redis变慢了?

Redis变慢了&#xff1f; 什么是Redis&#xff1f;测定Redis变慢&#xff1f;最大响应延迟平均响应延迟设置Redis慢日志 分析Redis变慢bigkeysbigkey的危害bigkey优化 写在最后 什么是Redis&#xff1f; 作为一个技术人员来说&#xff0c;大家用的最多的可能就是Redis了&#…...

11.6.k8s实战-节点扩缩容

目录 一&#xff0c;需求描述 二、集群缩容-节点下线 1&#xff0c;节点下线案例说明 2&#xff0c;查看现有节点 3&#xff0c;查看所有名称空间下的pod ​编辑4&#xff0c;驱逐下线节点的pod 5&#xff0c;驱逐后再次查看pod 6&#xff0c;驱逐pod后再次查看节点信息…...

相亲交友APP系统|婚恋交友社交软件|语音聊天平台定制开发

在现代社会&#xff0c;婚恋交友已经成为了人们日常生活中的一项重要任务。为了方便用户进行相亲交友活动&#xff0c;各种相亲交友APP系统和婚恋交友社交软件应运而生。本文将介绍相亲交友APP系统、婚恋交友社交软件的开发以及语音聊天平台的定制开发的相关知识和指导。 一、…...

2005-2022年款福特福克斯维修手册和电路图线路图接线图资料更新

经过整理&#xff0c;2005-2022年款福特福克斯全系列已经更新至汽修帮手资料库内&#xff0c;覆盖市面上99%车型&#xff0c;包括维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图解对…...

nodejs爬取小红书图片

昨天的文章已经描述了可以抓取评论区内容&#xff0c; 抓取图片内容和抓取评论区的内容基本一致 我们可以看到接口信息中含有图片链接&#xff0c;我们要做的就是爬取图片链接然后下载 这边要用到的模块为const downloadrequire(download) 将爬到的图片链接存放到images数组…...

MySQL从5.7升级到8.0步骤及其问题

MySQL从5.7升级到8.0步骤及其问题 前言 本文源自微博客&#xff0c;且以获得授权&#xff0c;请尊重版权。 一、需求背景 Docker环境下&#xff0c;MySQL5.7升级到8.0&#xff0c;数据迁移时使用的是mysqldump方式迁移。 二、迁移步骤 数据备份&#xff1a; docker exec -i 1…...

中年帕金森:守护健康,从容面对生活挑战

在快节奏的现代生活中&#xff0c;中年人群面临着越来越多的健康挑战。其中&#xff0c;帕金森病作为一种常见的神经系统疾病&#xff0c;逐渐引起了人们的关注。帕金森病不仅影响患者的身体健康&#xff0c;还对其日常生活造成极大的困扰。那么&#xff0c;我们该如何应对中年…...

oracle块跟踪

1.查询块跟踪 select status,filename,bytes from v$block_change_tracking;2.打开块跟踪 ALTER DATABASE ENABLE BLOCK CHANGE TRACKING USING FILE /home/oracle/block_change_tracking.log;3.关闭块跟踪 ALTER DATABASE DISABLE BLOCK CHANGE TRACKING;4.解释 Oracle数据…...

【机器学习】第3章 K-近邻算法

一、概念 1.K-近邻算法&#xff1a;也叫KNN 分类 算法&#xff0c;其中的N是 邻近邻居NearestNeighbor的首字母。 &#xff08;1&#xff09;其中K是特征值&#xff0c;就是选择离某个预测的值&#xff08;例如预测的是苹果&#xff0c;就找个苹果&#xff09;最近的几个值&am…...

求和 最大值 最小值 reduce Math.min Math.max

let arr [ 8,4,3,9,2]let sum arr.reduce((a,b) > ab)console.log(sum) // 求和 26let max arr.reduce((a,b) > a>b?a:b)console.log(max) // 最大值 9console.log(Math.max(...arr))let min arr.reduce((a,b) > a<b?a:b)console.log(min) // 最小值 2co…...

MyBatis 源码分析--获取SqlSession

前言&#xff1a; 前文我们从源码层面梳理了 SqlSessionFactory 的创建过程&#xff0c;本篇我们继续分析一下 SqlSession 的获取过程。 初识 MyBatis 【MyBatis 核心概念】 案例代码&#xff1a; public class MyBatisTest {Testpublic void test() throws IOException {/…...

Upload-Labs:Pass - 1(JS前端白名单)

Pass_1 1. 上传测试2. 代码审计**获取文件输入的值**&#xff1a;**检查是否选择了文件**&#xff1a;**定义允许的文件类型**&#xff1a;**提取文件的扩展名**&#xff1a;**检查文件类型是否允许上传**&#xff1a;**构建错误消息并提醒用户**&#xff1a; 3.绕过思路3.1 将…...

vue大作业-实现学校官网

vue大作业-实现学校官网 基于vue2实现的学校官网 项目展示 学校官网介绍 欢迎访问我们学校的官方网站&#xff0c;这里为您提供了全面的信息和资源&#xff0c;帮助您更好地了解我们的教育理念、教学资源和学术活动。 首页 首页是您了解我们学校的起点。这里展示了学校的最…...

24面试记录002

文章目录 12、brpc的优化2.1 brpc网络有啥降级&#xff1f; 3、移动语义4、python协程 二、1. mq怎么保障数据的顺序&#xff1f;3.brpc中上下游通信&#xff0c;怎么测评新增字段大小&#xff0c;对耗时的影响&#xff1f; 1 1、brpc和grpc区别&#xff0c;为啥选择brpc? gr…...

cocos 按钮

1、创建按钮 2、创建脚本 3、将脚本挂载到其他节点上 4、将节点和按钮绑定 即可实现点击按钮触发脚本。 在触发的脚本函数里面设置按钮节点的位置&#xff0c;将其移除屏幕&#xff0c;可以实现点击消失按钮的效果。...

文件扫描工具都有哪些?职场大佬都在用的文本提取工具大盘点~

回想起刚毕业初入职场那阵子&#xff0c;领导让帮忙把纸质文件扫描提取为文本时&#xff0c;还只会傻乎乎地一点点操作&#xff0c;属实是费劲得很&#xff01; 好在后面受朋友安利&#xff0c;找到了4个能够快速实现文件扫描文字提取的方法&#xff0c;这才让我的办公效率蹭蹭…...

【学习】程序员资源网站

1 书栈网 简介&#xff1a;书栈网是程序员互联网IT开源编程书籍、资源免费阅读的网站&#xff0c;在书栈网你可以找到很多书籍、笔记资源。在这里&#xff0c;你可以根据热门收藏和阅读查看大家都在看什么&#xff0c;也可以根据技术栈分类找到对应模块的编程资源&#xff0c;…...

游戏缓存与异步持久化的完美邂逅

1、问题提出 游戏服务器,需要频繁的读取玩家数据,同时也需求频发修改玩家数据,并持久化到数据库。为了提高游戏服务器的性能,我们应该怎么处理呢? 2、针对读——使用缓存 缓存,是指应用程序从数据库读取完数据之后,就将数据缓存在进程内存或第三方内存(例如redis)。…...

MySQL 高级 - 第十二章 | 数据库的设计规范

目录 第十二章 数据库的设计规范12.1 为什么需要数据库设计12.2 范式12.2.1 范式简介12.2.2 范式都包括哪些12.2.3 键和相关属性的概念12.2.4 第一范式&#xff08;1st NF&#xff09;12.2.5 第二范式&#xff08;2nd NF&#xff09;12.2.6 第三范式&#xff08;3rd NF&#xf…...

【Python】AJAX

AJAX基础 一、AJAX1.1 概述1.2 XMLHttpRequest对象1.3 AJAX请求六部曲1.4 图解AJAX请求步骤 二、jQuery与AJAX2.1 jQuery.get()2.2 jQuery.getJSON()2.3 jQuery.post()2.4 jQuery.ajax() 三、Django使用AJAX3.1 请求类型3.2 PUT与PATCH的区别3.3 接收及响应JSON3.3.1 接收JSON3…...

scikit-image安装报错

scikit-image安装报错&#xff1a; pip install scikit-image0.21.0 报错信息&#xff1a; Collecting PyWavelets>1.1.1 (from scikit-image0.21.0) Installing build dependencies … error error: subprocess-exited-with-error 解决方法&#xff1a; 提前安装好PyWave…...

STM32(七)———TIM定时器(基本and通用)

文章目录 前言一、通用定时器TIM简介1.STM32F10X系列总共最多有八个定时器&#xff1a;2.三种STM32定时器的区别&#xff1a;3.STM32 的通用定时器功能&#xff1a;4.计数器模式 二、基本定时器1.基本定时器的结构框图2.定时时间的计算3.定时器的结构体和库函数 总结 前言 一个…...

Spring中网络请求客户端WebClient的使用详解

Spring中网络请求客户端WebClient的使用详解_java_脚本之家 Spring5的WebClient使用详解-腾讯云开发者社区-腾讯云 在 Spring 5 之前&#xff0c;如果我们想要调用其他系统提供的 HTTP 服务&#xff0c;通常可以使用 Spring 提供的 RestTemplate 来访问&#xff0c;不过由于 …...

那些年我为了考PMP踩过的坑.....

说到考PMP我尊嘟很难过且伤心&#xff0c;众所周知&#xff0c;报考PMP都是要报机构的而且还是PMI认证的机构&#xff0c;所以在报考PMP过程中选的机构我可以说踩过了很多坑了...... Q&#xff1a;包过吗&#xff1f; 大家千万不要信某某机构说的包过噱头&#xff0c;真的很坑…...

邦芒解析:新人入职后存在的三种职场心理误区

​​多数职场新人会认为自己工作不快乐&#xff0c;不能正确处理职场人际关系。尤其是新人入职后在处理人际关系方面更明显&#xff0c;下面简述新人入职后主要存在的三种职场心理误区。 误区一&#xff1a;面对对上司的恐惧 学会和上司沟通&#xff0c;新人要采用上司容易接受…...

MFC案例:利用SetTimer函数编写一个“计时器”程序

一、希望达成效果 利用基于对话框的MFC项目&#xff0c;做一个一方面能够显示当前时间&#xff1b;另一方面在点击开始按钮时进行读秒计时&#xff0c;计时结果动态显示&#xff0c;当点击结束时读秒结束并保持最后结果。 二、编程步骤及相关代码、注释 1、启动VS…...

注册公司需要多久/seo培训机构哪家好

文中大部分中心思想来源于Event Processing In Action一书的9.4节。 模式识别器的基本策略是&#xff1a;所有时间窗内的事件被称为候选事件&#xff08;Participant Events&#xff09;&#xff0c;再由候选事件根据规则产生匹配集&#xff08;Matching Set&#xff09;。 1. …...

如何修改网站联系人/宁波seo高级方法

1.在云函数的文件夹下面建立一个文件/node.js云函数 2.然后需要cnpm i 下载依赖 3.存储 &#xff08;通过点击进行存储到云函数&#xff09; 4.读取云函数里面的数据 云函数文件入口&#xff1a; const cloud require(wx-server-sdk)...

网站建设骗/品牌推广策划

特征归一化:为什么需要对数值类型的特征做归一化? 特征归一化的方法为什么要对数值型特征做归一化?是否所有方法都需要对数值进行归一化?代码演示参考资料对数值类型的特征做归一化可以将所有的特征都统一到一个大致相同的数值区间。 特征归一化的方法 最常用的方法主要有…...

服装logo创意设计/优化百度涨

转载请标明出处&#xff1a;http://blog.csdn.net/zhaoyanjun6/article/details/76408024 本文出自【赵彦军的博客】 一&#xff1a;Gradle 是什么 Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置…...

洛阳建设企业网站公司/关键词com

&#xff08;1&#xff09;Google一直想在计算机体系中有所建树&#xff1a;芯片、存储、网络操作系统编程语言数据库、大数据中间件人工智能甚至想在通用应用上也有所建树&#xff1a;通信&#xff1a;邮件、IM办公文档国人在谷歌应用方面被墙&#xff0c;这个按下不表。谷歌的…...

wordpress mysql配置文件/百度app交易平台

在sql/plus中可调用dbms_metadata.get_ddl()函数来查看数据库对象的ddl&#xff0c;便于显示&#xff0c;先设置如下参数&#xff1a;set line 200&#xff1b;--设置行宽为200字符set pagesize 0&#xff1b;--设置每页的行数为0,(避免分页)set long 99999&#xff1b;--设置…...