公网带宽1m能建设电商网站吗,南通哪里有做网站的,石家庄微网站建设,哪里专业做网站Handler 老生常谈的问题了#xff0c;非常建议看一下Handler 的源码。刚入行的时候#xff0c;大佬们就说 阅读源码 是进步很快的方式。
Handler的基本原理
Handler 的 重要组成部分
Message 消息MessageQueue 消息队列Lopper 负责处理MessageQueue中的消息 消息是如何添加…Handler 老生常谈的问题了非常建议看一下Handler 的源码。刚入行的时候大佬们就说 阅读源码 是进步很快的方式。
Handler的基本原理
Handler 的 重要组成部分
Message 消息MessageQueue 消息队列Lopper 负责处理MessageQueue中的消息 消息是如何添加到队列的
对照着上面的大的逻辑图我们深入一下看一下一个消息 是如何被发送到 MessageQueue 又是如何被 Lopper 处理的
handler 发送一个message 的方法如下图所示 而这些方法最终都会执行 Handler 中的 enqueueMessage 方法我们看一下 enqueueMessage 方法做了什么
//Handler
private boolean enqueueMessage(NonNull MessageQueue queue, NonNull Message msg,long uptimeMillis) {//...//这里执行MessageQueue的 enqueueMessagereturn queue.enqueueMessage(msg, uptimeMillis);
}消息队列如何将消息排序
MessageQueue 收到 消息以后会根据时间进行排列
//MessageQueue
boolean enqueueMessage(Message msg, long when) {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;//step1 获取头部的messageMessage p mMessages;boolean needWake;//step2 头部的message 和 当前的message 对比如果头部的message 执行时间要 小于 当前message 的时候//那么就先执行当前的messageif (p null || when 0 || when p.when) {msg.next p;//头部的message 就变成了 当前的messagemMessages msg;needWake mBlocked;} else {//step3 将当前消息 插入到 中间排队needWake mBlocked p.target null msg.isAsynchronous();Message prev;//根据时间进行排序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.nextprev.next msg;}// We can assume mPtr ! 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;
}Handler的消息队列在哪创建的
回到创建Handler的地方他的构造方法
//Handler
public Handler() {this(null, false);
}//Handler
public Handler(Callback callback, boolean async) {//...//获取当前的loopermLooper Looper.myLooper();//...//获取looper 的 MessageQueuemQueue mLooper.mQueue;//...
}//Looper
final MessageQueue mQueue;private Looper(boolean quitAllowed) {//在这里创建了一个 MessageQueuemQueue new MessageQueue(quitAllowed);//...
}可以看到 Handler其实是拿着Looper 的MessageQueue当做自己的MessageQueue
Loope有什么作用
消息被有序的添加到了消息队列中而Looper就是负责将消息从消息队列中取出。当执行Looper的loop()方法Looper会从消息队列中取出消息然后交给handler的dispatchMessage去处理消息
//Looper
public static void loop() {//...for (;;) {//从消息队列中获取消息Message msg queue.next(); // might block//...try {//msg.traget 就是Handler //使用 Handler 的 dispatchMessage() 处理消息msg.target.dispatchMessage(msg);//...} catch (Exception exception) {//...}//...}
}一个线程有几个Looper
要想知道有几个Lopper,肯定要先知道Looper在哪里创建。Looper有一个prepare方法
//Looper
public static void prepare() {prepare(true);
}在这里会创建一个新的Looper 并且设置到了ThreadLocal
//Looper
private static void prepare(boolean quitAllowed) {//通过 sThreadLocal get 检查是否已经有 looper if (sThreadLocal.get() ! null) {//如果已经有了 就抛出异常throw new RuntimeException(Only one Looper may be created per thread);}//没有的话 就设置一个新的LoopersThreadLocal.set(new Looper(quitAllowed));
}在ThreadLocal可以看到是以map的形式去保存保证了一个线程只有一个map,又将looper和ThreadLocal进行绑定
//ThreadLocal
public void set(T value) {//获取当前线程Thread t Thread.currentThread();//获取 ThreadLocalMap ThreadLocalMap map getMap(t);//有的话 就将当前的 ThreadLocal 和 Looper 绑定在一起if (map ! null)//set 以后 在上面 sThreadLocal.get() 就不会在为null了map.set(this, value);else//没有的话 创建一个 ThreadLocalMap 在绑定在一起createMap(t, value);
}看到Looper中的 sThreadLocal
//Looper
static final ThreadLocalLooper sThreadLocal new ThreadLocalLooper();他是一个静态的 final 保证了 一个Looper只有一个 sThreadLocal
最终保证了一个线程只有一个Looper
主线程什么时候执行preapre
想要使用Looper肯定需要先prepare 去创建一个Looper,那么主线程如何创建Looper的呢我们知道 java 程序的入口是 main 方法 对于Android来说其实也有一个main 方法他的位置在 ActivityThread
//ActivityThread
public static void main(String[] args) {//...//可以看到在这里 程序启动以后Android 系统帮我们将主线程的Looper prepareLooper.prepareMainLooper();//...//然后帮助我们启动了 loopLooper.loop();//...
}Handler内存泄露
Handler为什么会有可能导致内存泄露 我们知道 内部类会持有外部类的引用,当我们做一个延时任务延时10S,然后在10S内退出Activity,在我们sendMessage的时候,handler对象被传递给msg 如所示然后被存放在MessageQueue中。在这10S内即使Activity销毁了但是引用关系依然被保存在MessageQueue中,那么即使Activity销毁了,他的对象依然不会被GC销毁因为他依然被引用。就导致内存未被回收。
//Handler
private boolean enqueueMessage(NonNull MessageQueue queue, NonNull Message msg,long uptimeMillis) {//这里 将 handler 本身的对象 传给 msg 的targetmsg.target this;//...
}那么如何处理Handler内存泄露呢
1.将Handler改成静态类。原因是因为静态类不会持有外部类的引用 2.继承Handler,将Activity作为弱引用使用 3.在界面退出的时候,调用Handler的removeMessages方法
消息队列没有消息时Handler如何挂起
Looper从MessageQueue中获取message当获取不到message的时候会将 nextPollTimeoutMillis置成-1然后进入下次循环当执行nativePollOnce方法时候如果nextPollTimeoutMillis-1那么就会执行Linux的epoll机制让线程处于挂起状态阻塞线程。
//MessageQueue
Message next() {for (;;) {//step3: nextPollTimeoutMillis -1 执行native 函数//执行 linux epoll 机制线程处于等待状态线程挂起nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {//...if (msg ! null) {} else {// step1:如果没有消息 nextPollTimeoutMillis 变成-1nextPollTimeoutMillis -1;}if (pendingIdleHandlerCount 0) {// step2:跳出循环 进入下一次循环mBlocked true;continue;}}}
}//Looper
public static void loop() {for (;;) {//step4:这里也就挂起了Message msg queue.next(); // might block}
}Handler如何退出
使用looper去执行quit方法退出
handler.looper.quit()//Looper
public void quit() {mQueue.quit(false);
}
public void quitSafely() {mQueue.quit(true);
}//MessageQueue
void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException(Main thread not allowed to quit.);}synchronized (this) {if (mQuitting) {return;}//step1:将mQuitting 变量变成truemQuitting true;//step2:删除所有的消息if (safe) {removeAllFutureMessagesLocked();} else {removeAllMessagesLocked();}//step3:唤醒线程nativeWake(mPtr);}
}//MessageQueue
Message next() {for (;;) {//step4:线程被唤醒。继续执行nativePollOnce(ptr, nextPollTimeoutMillis);//step5:检查到状态是 true 返回null 出去if (mQuitting) {dispose();return null;}}
}//Looper
public static void loop() {for (;;) {//step6:这里也被唤醒获取到message nullMessage msg queue.next(); // might block//step7:最终在这里循环if (msg null) {return;}}
}总结
Looper会先将消息队列中的消息全部清空然后使用nativeWake的native方法唤醒线程在上面我们介绍了当消息队列中没有消息的时候线程会挂起处于等待状态当我们唤醒以后Looper的loop方法会继续执行下去然后从MessageQueue中获取到一个null的Message,最终将Looper的loop()方法退出
主线程能够Quit么
我们知道了 主线程是在ActivityThread的main方法中执行了Looper.prepareMainLooper()创建的Looper
//Looper
Deprecated
public static void prepareMainLooper() {//step1: 注意看这里是一个falseprepare(false);
}//Looper
private static void prepare(boolean quitAllowed) {//step2:new的Looper传入的是falsesThreadLocal.set(new Looper(quitAllowed));
}//Looper
private Looper(boolean quitAllowed) {//step3:创建的MessageQueue 传入的也是falsemQueue new MessageQueue(quitAllowed);mThread Thread.currentThread();
}//MessageQueue
MessageQueue(boolean quitAllowed) {//step4:将mQuitAllowed 变量变成了falsemQuitAllowed quitAllowed;
}//MessageQueue
void quit(boolean safe) {//step5:如果是false 就是主线程 会直接抛出错误if (!mQuitAllowed) {throw new IllegalStateException(Main thread not allowed to quit.);}
}回头在看一下 Looper的prepare方法只有主线程可以创建一个不可以quit的MessageQueue,其他线程创建的都是可以quit的
//Looper
//公开方法 prepare 传入的是true
public static void prepare() {prepare(true);
}
//私有方法
private static void prepare(boolean quitAllowed) //主线程 传入的是false
public static void prepareMainLooper() {prepare(false);
}为什么设计主线程不能被quit
在ActivityThread中定义了一个H的类继承了Handler,这个H的handler执行了Android所有的主要事件比如广播serviceActivity生命周期等都是在这里进行处理所以不能把主线程quit
//ActivityThread
class H extends Handler {}消息如何知道是由哪个Handler发送的
一个线程可以有多个Handler,想new几个都可以在我们往MessageQueue中添加消息的时候会加入一个target标记是哪个handler发送的
//Handler
private boolean enqueueMessage(NonNull MessageQueue queue, NonNull Message msg,long uptimeMillis) {//step1:在这里 就标记了是哪一个handler 发送的 msg.target this;//...
}//Looper
public static void loop() {//...for (;;) {//...try {//step2:这里就对应起来是哪一个handler 发送的message msg.target.dispatchMessage(msg);//...} catch (Exception exception) {//...}//...
}Handler如何确保线程安全的
//MessageQueue
boolean enqueueMessage(Message msg, long when) {//step1:通过加锁的方式保证了添加消息到消息队列的安全synchronized (this) {}
}//MessageQueue
Message next() {for (;;) {//step2:通过枷锁的方式保证了读取消息的安全synchronized (this) {}}
}Message如何复用的
看一下我们quit的时候是怎么从消息队列中清空消息的
//MessageQueue
void quit(boolean safe) {synchronized (this) {//step1: 清除所有的消息if (safe) {removeAllFutureMessagesLocked();} else {removeAllMessagesLocked();}}
}//MessageQueue
private void removeAllMessagesLocked() {Message p mMessages;while (p ! null) {Message n p.next;//step2:执行message的方法p.recycleUnchecked();p n;}mMessages null;
}//Message
void recycleUnchecked() {//step3:将所有的变量全部清空flags FLAG_IN_USE;what 0;arg1 0;arg2 0;obj null;replyTo null;sendingUid UID_NONE;workSourceUid UID_NONE;when 0;target null;callback null;data null;synchronized (sPoolSync) {//默认50个Messageif (sPoolSize MAX_POOL_SIZE) {//step4:将已经清空状态的Message 放到一个新的链表中next sPool;sPool this;sPoolSize;}}
}使用obtain方法会从之前清空状态的链表中取出一个Message去使用减少创建Message带来的内存消耗。
//Message
public static Message obtain() {synchronized (sPoolSync) {if (sPool ! null) {//step5:从已经清空状态的链表中取出一个Message使用Message m sPool;sPool m.next;m.next null;m.flags 0; // clear in-use flagsPoolSize--;return m;}}return new Message();
}这种设计模式称为享元设计模式 为什么主线程loop不会导致ANR
首先要知道ANR是怎么出现的ANR出现的条件有两个
5秒内没有响应输入的事件触摸反馈等广播10秒内没有执行完毕
在上面我们分析知道所有的事件都是由Handler进行分发在主线程上发送一个事件这个事件耗时将主线程的loop()给卡主让他只能执行当前任务不能去处理其他事件就出现了ANR
ANR的本质是由于不能及时处理消息导致的和他的loop是没有任何关系的
Handler同步屏障
同步屏障概念
啥叫同步屏障字面意思就是阻挡同步消息那么Handler同步屏障是干啥的没错你没听错就是阻挡同步消息让异步消息过去。阻挡同步消息 这就是同步屏障
在发送消息的时候mAsynchronous 控制着是否发送的消息是否为异步消息
//Handler
private boolean enqueueMessage(NonNull MessageQueue queue, NonNull Message msg,long uptimeMillis) {msg.target this;msg.workSourceUid ThreadLocalWorkSource.getUid();//如果是true 则将消息标记为异步消息if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}在Handler构造方法中控制则是否是异步消息。但是这个方法是hide正常我们是不能调用的
//Handler
hide
public Handler(Nullable Callback callback, boolean async) {//这里控制着变量mAsynchronous async;
}开启同步屏障
那么如何开启同步屏障呢,MessageQueue 中提供了一个 postSyncBarrier 方法 开启同步屏障
//MessageQueue
public int postSyncBarrier() {return postSyncBarrier(SystemClock.uptimeMillis());
}//MessageQueue
private int postSyncBarrier(long when) {// Enqueue a new sync barrier token.// We dont need to wake the queue because the purpose of a barrier is to stall it.synchronized (this) {final int token mNextBarrierToken;final Message msg Message.obtain();msg.markInUse();// 注意这里 开启以后没有设置target, 所以Messaged的target 是 nullmsg.when when;msg.arg1 token;Message prev null;Message p mMessages;if (when ! 0) {while (p ! null p.when when) {prev p;p p.next;}}if (prev ! null) { // invariant: p prev.nextmsg.next p;prev.next msg;} else {msg.next p;mMessages msg;}//返回一个 token 用来取消同步屏障时候使用return token;}
}同步屏障工作原理
开启以后同步屏障如何将异步消息传递出去将同步消息阻挡下来呢
//MessageQueue
Message next() {//...//step1: 看到这里 一旦收到target null 表示同步屏障打开了if (msg ! null msg.target null) {do {prevMsg msg;msg msg.next;//step2: 这里就做一个循环 寻找异步消息} while (msg ! null !msg.isAsynchronous());}//step3:当找到异步消息以后if (msg ! null) {//step4:判断是否到了要执行异步消息的时间if (now msg.when) {//如果还没到就等nextPollTimeoutMillisnextPollTimeoutMillis (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {//如果到了执行时间 从链表中移除他mBlocked false;if (prevMsg ! null) {prevMsg.next msg.next;} else {mMessages msg.next;}msg.next null;if (DEBUG) Log.v(TAG, Returning message: msg);msg.markInUse();return msg;}}
}取消同步屏障
取消同步屏障以后会唤醒线程去处理之前未被处理的同步消息。
//MessageQueue
public void removeSyncBarrier(int token) {synchronized (this) {Message prev null;Message p mMessages;//step1:通过token 寻找设置的同步屏障while (p ! null (p.target ! null || p.arg1 ! token)) {prev p;p p.next;}if (p null) {throw new IllegalStateException(The specified message queue synchronization barrier token has not been posted or has already been removed.);}final boolean needWake;//step2:从链表中移除if (prev ! null) {prev.next p.next;needWake false;} else {mMessages p.next;needWake mMessages null || mMessages.target ! null;}//step3:将Message清空p.recycleUnchecked();if (needWake !mQuitting) {//step4:唤醒线程nativeWake(mPtr);}}
}GIF演示
下面以一个简单的示例更佳直观的表现,示例分成3中情况
没有启动同步屏障发送同步消息 发送异步消息开启同步屏障发送同步消息 发送异步消息开启同步屏障发送同步消息 发送异步消息 在取消同步屏障 没有启动同步屏障发送同步消息 发送异步消息 可以看到如果不开启同步屏障对于Handler 来说 消息都是会被发送出去 开启同步屏障发送同步消息 发送异步消息 通过对比能够发现当开启同步屏障以后发送的同步消息并没有打印只有异步消息打印了说明同步屏障确实只能够允许异步消息通过 开启同步屏障发送同步消息 发送异步消息 在取消同步屏障 当我们移除同步屏障以后之前没有收到的同步消息会立马同步过来
演示代码 class HandlerAct : AppCompatActivity() {companion object {const val TAG handler-tagconst val MESSAGE_TYPE_SYNC 0x01const val MESSAGE_TYPE_ASYN 0x02}private var index 0private lateinit var handler :Handlerprivate var token: Int? nullRequiresApi(Build.VERSION_CODES.M)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_handler)initHandler()linear.addView(MaterialButton(this).apply {text 插入同步屏障setOnClickListener {sendSyncBarrier()}})linear.addView(MaterialButton(this).apply {text 移除屏障setOnClickListener {removeSyncBarrier()}})linear.addView(MaterialButton(this).apply {text 发送同步消息setOnClickListener {sendSyncMessage()}})linear.addView(MaterialButton(this).apply {text 发送异步消息setOnClickListener {sendAsynMessage()}})}private fun initHandler() {Thread {Looper.prepare()handler Handler(){when(it.what){MESSAGE_TYPE_SYNC - {Log.i(TAG, 收到同步消息 index:${it.arg1})}MESSAGE_TYPE_ASYN - {Log.i(TAG, 收到异步消息 index:${it.arg1})}}true}Looper.loop()}.start()}private fun sendSyncMessage() {indexLog.i(TAG, 插入同步消息 index:$index)val message Message.obtain()message.what MESSAGE_TYPE_SYNCmessage.arg1 indexhandler.sendMessageDelayed(message, 1000)}//往消息队列插入异步消息RequiresApi(api Build.VERSION_CODES.LOLLIPOP_MR1)private fun sendAsynMessage() {indexLog.i(TAG, 插入异步消息 index:$index)val message Message.obtain()message.what MESSAGE_TYPE_ASYNmessage.arg1 indexmessage.isAsynchronous truehandler.sendMessageDelayed(message, 1000)}RequiresApi(Build.VERSION_CODES.M)SuppressLint(DiscouragedPrivateApi)fun sendSyncBarrier() {try {Log.d(TAG, 插入同步屏障)val queue: MessageQueue handler.looper.queueval method: Method MessageQueue::class.java.getDeclaredMethod(postSyncBarrier)method.isAccessible truetoken method.invoke(queue) as IntLog.d(TAG, token:$token)} catch (e: Exception) {e.printStackTrace()}}//移除屏障SuppressLint(DiscouragedPrivateApi)RequiresApi(api Build.VERSION_CODES.M)fun removeSyncBarrier() {Log.i(TAG, 移除屏障)try {val queue: MessageQueue handler.looper.queueval method: Method MessageQueue::class.java.getDeclaredMethod(removeSyncBarrier,Int::class.javaPrimitiveType)method.isAccessible truemethod.invoke(queue, token)} catch (e: Exception) {e.printStackTrace()}}}总结
在面试工作中还要许多的小细节需要我们去注意上面这些面试题目是我在之前网上收集整理的一小部分由于文档的篇幅长度限制。就在下面用图片展现给大家看了如果有需要这些面试题参考内含参考答案https://qr18.cn/CgxrRy