Handler面试那些事

问题1:一个线程中有几个Handler?
答:多个,一个Activity或service就是在主线程中,在主线程中可以new多个Handler。

问题2:一个线程有几个Looper?如何保证?
答:一个线程只有一个Looper(一旦创建不能修改),通过ThreadLocal类中的内部类ThreadLocalMap进行维持。(一个Looper对应一个MessageQueue,MessageQueue是在Looper构造函数中创建的)

Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
ThreadLocalMap 与当前线程是一一对应的

ThreadLocalMap是ThreadLocal的静态内部类
ThreadLocalMap: <key, value>,key = this,即唯一的 ThreadLocal,value = Looper,但是key和value存放在一个数组中,如下格式。

key1
value1
key2
value2

如何保证唯一? --》只让set一次
Looper.java

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();  // 表示sThreadLocal 是唯一的

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {  //sThreadLocal.get()获得的值就是Looper对象
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

  ThreadLocalMap是Map的存储结构,每次存的时候都会根据key(ThreadLocal)进行判断,当前的ThreadLocal是否有对应的Looper,这样就可以表示一个ThreadLocal对应唯一的一个Looper对象。

ThreadLocal.java

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

问题3:Handler内存泄漏原因?为什么其他的内部类没有说过有这个问题?
答:原因:内部类持有外部类的引用,默认的会持有外部this的引用。
  message会持有handler的引用(msg.target.dispatchMessage(msg)),handler会持有this的引用(msg.target = this),message没有处理之前,一直都会处在内存中。如果消息delay 1分钟之后,在这个时间内会一直持有this的引用。
  持有关系:MessageQueue—>Message—>handler—>activity
  其他内部类持有外部类并没有耗时的东西,没有另外的东西持有内部类,就不会出现内存泄漏问题。
  解决办法:软引用 + static

问题4:为何主线程可以new Handler?如果想要在子线程中new Handler要做些什么准备?
答:在ActivityThread类中的main()方法中,有对主函数的Looper进行prepare()(Looper.prepareMainLooper())和loop()(Looper.loop())操作。主线程中的所有代码全部都运行在Looper.prepareMainLooper()和Looper.loop()两个函数之间。

如果想要在子线程中new Handler需要进行如下三步操作:
1)prepare()
2)Loop()
3)quit()或quitSafely()—>用于子线程消息队列中无消息处于等待状态的处理
如果没有执行prepare操作会报“No Looper; Looper.prepare() wasn’t called on this thread.”。
特别注意: 主线程是不允许使用quit操作的。
如果主线程中调用了quit()方法会报如下异常:
MessageQueue.java

 void quit(boolean safe) {
   if (!mQuitAllowed) {
       throw new IllegalStateException("Main thread not allowed to quit.");
   }
   ... ...
}

问题5:子线程中维护的Looper,消息队列无消息的时候处理方案是什么?有什么用?
答:消息队列无消息会一直处于等待状态。
(执行到next()方法中的nativePollOnce(ptr, nextPollTimeoutMillis);语句,处于linux等待状态。直到enqueueMessage()方法中执行了nativeWake(mPtr)语句才会唤醒nativePollOnce之后的操作。)

messageQueue消息处理机制:
① 如果消息队列不为空,当前时间还没到,会执行nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞等待,等待nextPollTimeoutMillis时间,时间到了会自己唤醒。
② 如果消息队列为空,当messageQueue执行enqueueMessage()流程的时候,会执行nativeWake(mPtr)进行唤醒。

解决方案:调用Looper的quitSafely()或quit(),
作用:1)释放内存,2)释放线程。
quitSafely()---->安全的
quit()---->不安全的

子线程调用quit()流程:
① 执行quit()流程,会将mQuitting 变量赋值为 true,并执行nativeWake(mPtr)进行线程唤醒操作;
MessageQueue.java

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;  // 进行mQuitting 设置为true

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr); // 进行唤醒
        }
    }

② 在MessageQueue类中的next()方法会对阻塞流程nativePollOnce(ptr, nextPollTimeoutMillis)进行唤醒。
MessageQueue.java

Message next() {
	...
	for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis); //进行阻塞
            ...
     }
     ...
     if (mQuitting) {  //此时mQuitting为true
         dispose();
         return null;  //返回为null
     }
	... 
}

③ Looper.loop流程中进行next()取消息流程
Looper.java

public static void loop() {
    final Looper me = myLooper();
    ...
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {  // 往下执行loopOnce() 流程
            return; //loopOnce()返回为false的时候直接return
        }
    }
}


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

当message为空时,loopOnce()返回为false的时候直接return,表现子线程退出,

问题6:既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程),那它内部是如何确保线程安全的?
答:MessageQueue是一个优先级队列,根据时间先后顺序排队的单链表。MessageQueue没有设置限制,可以无限加入message,但是受手机内存的限制。
  在enqueueMessage()加消息过程中会进行synchronized操作,并且在next()取消息过程中也会进行synchronized操作。从而保证了线程安全性。

MessageQueue.java
往消息队列中存消息

boolean enqueueMessage(Message msg, long when) {
   if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
    ... ...
}

从消息队列中取消息
MessageQueue.java

@UnsupportedAppUsage
Message next() {
	... ...
	for (;;) {
       if (nextPollTimeoutMillis != 0) {
           Binder.flushPendingCommands();
       }

       nativePollOnce(ptr, nextPollTimeoutMillis);

       synchronized (this) {
           // Try to retrieve the next message.  Return if found.
           final long now = SystemClock.uptimeMillis();
           Message prevMsg = null;
           Message msg = mMessages;
           if (msg != null && msg.target == null) {
           ... ...
          
}

  为了保证安全性,加入synchronized,从而导致Handler delay的消息中的时间不完全准确。

synchronized可以修饰:方法,静态方法、代码块(object)、代码块(this)

问题7:我们使用Message时应该如何创建它?
答:采用obtain()方法进行创建。采用享元设计模式,内存复用。
当MessageQueue进行释放消息的时候,其实并没有把消息释放掉,在执行recycleunchecked()方法时只是把消息里面的内容置空,然后把置空的消息放到池子中,往队列头中进行添加。
Obtain()是从池子队列头取一个点
如果采用new()进行创建,可能会出现内存抖动现象。

问题8:使用Handler的postDelay后消息队列会有什么变化?
答:如果消息队列为空,该消息不会执行,计算等待时间,让消息进行wait。

问题9:Looper死循环为什么不会导致应用卡死?
答:每个app都有一个自己的虚拟机。
Launcher—>application—>zygote—>虚拟机—>ActivityThread
所有的生命周期都发运行在Loop里面,都是以消息的方式存在

主线程的唤醒方式有哪些?
① 输入的事件
② Looper添加消息

ANR产生原因:
①在5秒内没有响应输入的事件(例如:按键按下,屏幕触摸)
②BroadcastReceiver在10秒内没有执行完毕

每一个事件就相对于一个message。

为什么不会卡死:
疑惑点:Message msg = queue.next();
此时主线程处于休眠状态,卡死是ANR

问题10:Message、Handler、MessageQueue、Looper 的之间的关系?
  首先,是这个 MessageQueue,MessageQueue是一个消息队列,它的数据结构形式是有单链表实现的优先级队列,它可以存储 Handler 发送过来的消息,其内部提供了进队和出队的方法来管理这个消息队列,其出队和进队的原理是采用单链表的数据结构进行插入和删除的,即enqueueMessage()方法和 next()方法。这里提到的 Message,其实就是一个Bean 对象,里面的属性用来记录 Message 的各种信息。

  然后,Looper 是一个循环器,它可以循环的取出MessageQueue 中的 Message,其内部提供了 Looper 的初始化和循环出去Message 的方法,即 prepare()方法和loop()方法。在 prepare()方法中,Looper会关联一个MessageQueue,而且将 Looper 存进一个 ThreadLocal 中,在loop()方法中,通过 ThreadLocal 取出 Looper,使用MessageQueue的next()方法取出 Message 后,判断 Message 是否为空,如果是则 Looper 阻塞,如果不是,则通过 dispatchMessage()方法分发该 Message 到 Handler 中,而Handler 执行 handlerMessage()方法,由于 handlerMessage()方法是个空方法,这也是为什么需要在 Handler 中重写 handlerMessage()方法的原因。这里要注意的是Looper 只能在一个线程中只能存在一个。这里提到的ThreadLocal,其实就是一个对象,用来在不同线程中存放对应线程的 Looper。

  最后,Handler 是Looper 和MessageQueue的桥梁,Handler内部提供了发送 Message 的一系列方法,最终会通过 MessageQueue的enqueueMessage()方法将 Message 存进 MessageQueue 中。我们平时可以直接在主线程中使用 Handler,那是因为在应用程序启动时,在入口的 main 方法中已经默认为我们创建好了 Looper。

问题11:为什么在子线程中创建 Handler 会抛异常?
  Handler 的工作是依赖于 Looper 的,而 Looper(与消息队列)又是属于某一个线程(ThreadLocal 是线程内部的数据存储类,通过它可以在指定线程中存储数据,其他线程则无法获取到),其他线程不能访问。因此 Handler 就是间接跟线程是绑定在一起了。因此要使用 Handler 必须要保证 Handler 所创建的线程中有 Looper 对象并且启动循环。因为子线程中默认是没有 Looper 的,所以会
报错。 正确的使用方法是:

private final class WorkThread extends Thread {
	private Handler mHandler;
	
	public Handler getHandler() {
		return mHandler;
	}
	
	private final class WorkThread extends Thread {
		public void quit() {
			mHandler.getLooper().quit();
		}
		
		@Override
		public void run() {
			super.run();
			//创建该线程对应的Looper,
			// 内部实现
			// 1。new Looper()
			//2。将1步中的lopper放在ThreadLocal里,ThreadLocal是保存数据的, 
			主要应用场景是:线程间数据互不影响的情况
			// 3。在1步中的Looper的构造函数中new MessageQueue();
			//其实就是创建了该线程对用的Looper,Looper里创建MessageQueue来实现
			消息机制
			/对消息机制不懂得同学可以查阅资料,网上很多也讲的很不错。
			Looper.prepare();
			mHandler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				 super.handleMessage(msg);
				Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
			}
		};
		//开启消息的死循环处理即:dispatchMessage
		Looper.loop();
		/注意这3个的顺序不能颠倒
		Log.d("WorkThread", "end");
	}
}

问题12、如何从子线程发送消息到主线程中进行处理的?
答:首先需要了解下handler调度流程。通过new Thread()创建一个子线程执行hander.sendMessage()操作往MessageQueue消息队列中插入消息(MessageQueue其实是一个存储消息的容器,属于是一个变量,不属于具体哪个线程,线程中执行的函数属于具体哪个线程)。一个线程对应一个ThreadLocalMap,ThreadLocalMap存储着唯一的ThreadLocal(key)和唯一的Looper(value),并且一个Looper对应一个MessageQueue。取消息流程会执行主线程中Looper.loop(),会调用到next()方法进行msg.target.dispatchMessage()以及后续的handleMessage()操作,这些都是在主线程执行的。

(Handler调度流程)
插入消息流程:
handler  send***/post*** -----> Handler.sendMessage() -->...--> messageQueue.enqueueMessage   //往messageQueue消息队列中插入消息
取消息流程:
Looper. loop() ----> messageQueue ----> next()----> msg.target.dispatchMessage() ----> handlerMessage

附录一些视频截图(https://ke.qq.com/course/341933?_bid=167&_wv=2147483649&term_id=100406336&singlemessage=&taid=9303882710988717)
在这里插入图片描述
根据时间先后顺序生产一个优先级队列:(Handler调度流程)
插入消息流程:
handler send***/post*** -----> Handler.sendMessage() -->…–> messageQueue.enqueueMessage //往messageQueue消息队列中插入消息
取消息流程:
Looper. loop() ----> messageQueue ----> next()----> msg.target.dispatchMessage() ----> handlerMessage

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值