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

从源码分析Handler面试问题

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 ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

他是一个静态的 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所有的主要事件,比如广播,service,Activity生命周期等都是在这里进行处理,所以不能把主线程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 don't 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-tag"const val MESSAGE_TYPE_SYNC = 0x01const val MESSAGE_TYPE_ASYN = 0x02}private var index = 0private lateinit var handler :Handlerprivate var token: Int? = null@RequiresApi(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() {index++Log.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() {index++Log.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

相关文章:

从源码分析Handler面试问题

Handler 老生常谈的问题了&#xff0c;非常建议看一下Handler 的源码。刚入行的时候&#xff0c;大佬们就说 阅读源码 是进步很快的方式。 Handler的基本原理 Handler 的 重要组成部分 Message 消息MessageQueue 消息队列Lopper 负责处理MessageQueue中的消息 消息是如何添加…...

shell编程 变量作用域

变量 变量赋值不用$&#xff0c;访问值时用$,赋值时两边不留空格&#xff0c;双引号括起来的变量被值替换{}标记变量开始和结束,变量名区分大小写&#xff0c;所有bash变量的值变量不区分类型&#xff0c;统一为字符串 变量类型 环境变量&#xff0c;子进程可以继承父进程环境…...

华为eNSP:isis的配置

一、拓扑图 二、路由器的配置 配置接口IP AR1&#xff1a; <Huawei>system-view [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip add 1.1.1.1 24 [Huawei-GigabitEthernet0/0/0]qu AR2: <Huawei>system-view [Huawei]int g0/0/0 [Huawei-GigabitEthe…...

FS.05-SAS-UP-Methodology

FS.05-SAS-UP-Methodology-v9.2.pdf 附录 D 数据处理审核 作为现场数据处理系统和支持流程审核的一部分&#xff0c;受审核方最好在审核日期之前准备一些 SAS 特定的测试数据文件。 本文件提供了建议的方法&#xff1b; 受审核方和审核团队将就每次审核的具体方法达成一致。 …...

Jmeter并发测试

基本步骤 1、新建线程组 测试计划右键——>添加——>线程&#xff08;用户&#xff09;——>线程组 2、 添加HTTP请求 线程组右键——>添加——>取样器——>HTTP请求 3、 添加HTTP信息头管理器 线程组右键——>添加——>配置元件——>HTTP信息头…...

【JVM】浅看JVM的运行流程和垃圾回收

1.JVM是什么 JVM&#xff08; Java Virtual Machine&#xff09;就是Java虚拟机。 Java的程序都运行在JVM中。 2.JVM的运行流程 JVM的执行流程&#xff1a; 程序在执行之前先要把java代码转换成字节码&#xff08;class文件&#xff09;&#xff0c;JVM 首先需要把字节码通过…...

使用低代码开发,需要注意哪些?

低代码平台的历史相对较短&#xff0c;大约始于 2000 年初&#xff0c;源于快速应用程序开发工具。随着低代码平台和工具的日益普及和优势&#xff0c;它不断发展以满足各种领域和角色的需求。 本文将研究各种低代码和无代码应用程序开发方法、业务用例、挑战和未来预测等。 一…...

面试总结-Redis篇章(八)——Redis分布式锁

JAVA 面试总结-Redis分布式锁 模拟抢券场景通过下面方法添加Synchronized锁来防止上述情况&#xff0c;如果上面是单体服务没有问题&#xff0c;但是如果项目是集群部署&#xff0c;会出现下面的问题&#xff0c;因为Synchronized是属于本地的锁端口8080和8081同时访问&#xf…...

压力测试-商场项目

1.压力测试 压力测试是给软件不断加压&#xff0c;强制其在极限的情况下运行&#xff0c;观察它可以运行到何种程度&#xff0c;从而发现性能缺陷&#xff0c;是通过搭建与实际环境相似的测试环境&#xff0c;通过测试程序在同一时间内或某一段时间内&#xff0c;向系统发送预…...

IDEA中文UT方法执行报错问题、wps默认保存格式

wps默认保存格式、IDEA中文UT方法执行报错问题 背景 1、wps修改文件后&#xff0c;编码格式从UTF-8-bom变成UTF-8&#xff08;notepad可以查看&#xff09;&#xff1b; 2、IDEA中文UT执行报错&#xff1a; 解决方案 1、语言设置中不要勾选 “Beta版。。。。” 2、cmd中执…...

Vue如何实现编程式导航声明方法,前进和后退导航

编程式导航声明方法&#xff0c;前进和后退导航 在router中设置路由导航跳转函数 只要发生跳转 导航的声明函数 访问控制系统如何形成 就这三种 导航守卫的案例&#xff0c;写一个Main.Vue 和login .Vue 后台主页 如果想要展示后台主页&#xff0c;就用这种方法 想实现路由跳转…...

torch.load 报错 ModuleNotFoundError 或 AttributeError

Python 3.11.3 (main, Apr 7 2023, 19:25:52) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin Type "help", "copyright", "credits" or "license" for more information.正常情况下&#xff0c;我们会使用 torch.save 保存模型的 …...

前端,js , Error in created hook: TypeError ,有bug了

怎么兄弟&#xff0c;遇到bug了&#xff1f;&#xff1f;&#xff1f;你开心吗&#xff0c;哈哈哈哈...

百度文心千帆大模型平台:企业级大模型服务的新航标

随着人工智能和大数据的快速发展&#xff0c;大模型平台正越来越受到各大企业和个人开发者的青睐。本文将以百度最新推出的文心千帆大模型平台为例&#xff0c;深入分析其在国家战略布局&#xff0c;经济发展趋势&#xff0c;市场变化动向和技术研发周期等方面的影响和应用。同…...

uniApp低功耗蓝牙一键开门、多对多查找、数组匹配数组、开锁

文章目录 htmlJavaScript坑 html <view class"m_t_36"><view class"w_50_ h_100 lh_100 m_l_a m_r_a bc_409eff radius_10 color_fff ta_c" click"openBluetoothAdapter()">一键开门</view> </view>JavaScript export…...

类和对象|六个默认成员函数|const成员函数|运算符重载

文章目录 默认成员构造函数1. 构造函数1.1 概念1.2 特性 2. 析构函数2.1 概念2.2 特性 3. 拷贝构造函数3.1 概念3.2 特性 4. 运算符重载4.1 赋值重载4.2 自增自减重载4.3 取地址操作符重载 5. const成员函数6. 取地址重载 默认成员构造函数 上一节我们说过&#xff0c;空类的大…...

从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

ExceptionHandler的作用 ExceptionHandler是Spring框架提供的一个注解&#xff0c;用于处理应用程序中的异常。当应用程序中发生异常时&#xff0c;ExceptionHandler将优先地拦截异常并处理它&#xff0c;然后将处理结果返回到前端。该注解可用于类级别和方法级别&#xff0c;…...

04mysql查询语句之查询与分页02

1. 所有有门派的人员信息 &#xff08; A、B两表共有&#xff09; INSERT INTO t_dept(deptName,address) VALUES(华山,华山); INSERT INTO t_dept(deptName,address) VALUES(丐帮,洛阳); INSERT INTO t_dept(deptName,address) VALUES(峨眉,峨眉山); INSERT INTO t_dept(deptN…...

原型模式——对象的克隆

1、简介 1.1、概述 可以通过一个原型对象克隆出多个一模一样的对象&#xff0c;该模式被称为原型模式。 在使用原型模式时&#xff0c;需要首先创建一个原型对象&#xff0c;再通过复制这个原型对象来创建更多同类型的对象。 1.2、定义 原型模式&#xff08;Prototype Patt…...

[SQL挖掘机] - 多表连接

介绍: 在 SQL 中&#xff0c;多表连接是指将多个表根据某些条件进行联接&#xff0c;以获取相关联的数据。这允许我们跨多个表进行查询&#xff0c;并且根据表之间的关系获取所需的结果。 作用: 当在多个表中存储相关数据时&#xff0c;使用多表连接可以将这些表组合起来以获…...

Day 14 C++ 对象的初始化和清理

目录 为什么要进行对象的初始化和清理 构造函数和析构函数 构造函数&#xff08;Constructor&#xff09; 构造函数语法 调用时机 构造函数的调用方式 括号法 显式法 隐式转换法 构造函数分类 分类方式 按参数分为有参构造和无参构造 按类型分为普通构造和拷贝构造…...

Delphi7最佳登录窗体设计

Delphi7我们这里用登录窗体来做演示。输入正确用户名和密码后&#xff0c;登录窗体释放&#xff0c;显示主窗体。 方法/步骤 1.打开Delphi7集成开发环境&#xff0c;在默认工程的Form1窗体放置一个Label1控件&#xff0c;拖动控件边界调整大小&#xff0c;并将Object Inspect…...

动脑学院Jetpack Compose学习笔记

最近b站学习了一下Compose相关内容&#xff0c;整理了相关笔记&#xff0c;仅供大家参考。 资源链接如下&#xff0c;象征性收取1个积分 https://download.csdn.net/download/juliantem/88125198...

Qt中线程的使用

Qt中线程的使用 在qt中线程的使用有两种方式&#xff0c;第一种就是创建一个类继承QObject类&#xff0c;之后使用moveToThread函数将线程添加到类中。另一种就是创建一个类继承QThread类&#xff0c;在类中实现run函数。 第一种方式&#xff1a; 1、首先创建一个自定义的类…...

基于YOLOv8开发构建蝴蝶目标检测识别系统

在前面的一篇博文中已经很详细地描述了如何基于YOLOv8开发构建自己的个性化目标检测模型&#xff0c;感兴趣的话可以看下&#xff1a; 《基于YOLOv8开发构建目标检测模型超详细教程【以焊缝质量检测数据场景为例】》 本文的主要目的就是基于YOLOv8来开发构建细粒度的蝴蝶目标…...

【已解决】电脑连上网线但无法上网

文章目录 案例情况解决方案必要的解决方法简要概括详细步骤1、打开控制面板2、打开更改适配器设置3、 找Internet协议版本44、修改配置 可能有用的解决方法 问题解决原理Internet 协议版本 4&#xff08;TCP/IPv4&#xff09;确保IP地址和DNS服务器设置为自动获取 案例情况 网…...

Linux 学习记录57(ARM篇)

Linux 学习记录57(ARM篇) 本文目录 Linux 学习记录57(ARM篇)一、外部中断1. 概念2. 流程图框 二、相关寄存器1. GIC CPU Interface (GICC)2. GIC distributor (GICD)3. EXTI registers 三、EXTI 寄存器1. 概述2. 内部框图3. 寄存器功能描述4. EXTI选择框图5. EXTI_EXTICR1 &…...

Doris注意事项,Doris部署在阿里云,写不进去数据

1.Doris官网 Doris官网https://doris.apache.org/ 2.根本原因 本地idea访问FE&#xff0c;FE会返回BE的地址&#xff0c;但是在服务器上通过ip addr查看&#xff0c;发现只有局域网IP&#xff0c;所以FE返回了局域网的IP&#xff0c;导致idea连接不上BE 3.解决办法 重写Ba…...

502 Bad GateWay报错的解决方法

什么是502 bad gateway 报错 简单来说 502 是报错类型代码 bad gateway 错误的网关。是Web服务器作为网关或代理服务器时收到无效的响应。 用我们的口语说就是运行网站的服务器暂时挂了(不响应)。 产生错误的原因 1.连接超时 我们向服务器发送请求 由于服务器当前链接太多&am…...

openpnp - ReferenceStripFeeder 改版零件

文章目录 openpnp - ReferenceStripFeeder 改版零件概述笔记整体效果散料飞达主体磁铁仓盖板飞达编带中间压条飞达编带两边压条装配体用的8mm编带模型END openpnp - ReferenceStripFeeder 改版零件 概述 官方推荐了ReferenceStripFeeder的模型smd_strip_feeders_mod_tray.zip…...

广州网站开发费用/互联网营销策略有哪些

乱码有时候是一个非常让人头疼的问题&#xff0c;这里就总结一下常用的解决乱码的方法。只知道的用法&#xff0c;却不明白为什么这么用……一、在Java代码中&#xff1a;1 request.setCharacterEncoding("UTF-8");用在哪里&#xff0c;为什么这么用……二、String r…...

游学做的好的网站/哪里有免费的网站推广

本文转载自&#xff1a;灵雀云公众号3月12日&#xff0c;Linux 基金会旗下CDF&#xff08;持续交付基金会&#xff09;宣告成立3月12日&#xff0c;在加州Half Moon Bay举行的开源领导者峰会&#xff08;Open Leadership Summit 2019 &#xff09;上&#xff0c;CDF&#xff08…...

263企业邮箱下载客户端/企业网站seo方案案例

其实计算机就是个巨大的状态机&#xff01;计算机不能读懂机器码&#xff01;它只会执行&#xff01;机器码只是些0和1的组合&#xff0c;这些组合对应着各种寄存器或者某些逻辑电路的状态。设置这种状态的机器码叫做CPU指令&#xff0c;不过也许叫做opcode更精确。但是值得注意…...

如何用电子邮箱做网站/正规的网店培训机构有哪些

以下内容有待细化&#xff0c;并用于考察员工的水平&#xff01; 从低的往高级的说。 初级 1.掌握java语法 oopio网络 2.基本的数据结构 3.基本的算法-例如排序&#xff0c;合并 4.基本的几个javaee框架 springmvcspringboot mybatis 5.知道如何使用tomcat等容器 6.会使用linux…...

wordpress获取文章url地址/北京今日重大新闻

Axis: 自动生成WSDL的服务描述&#xff1b; OWL-S Api: 可将WSDL与OWL-S File互转&#xff08;通过WSDL2OWL-S 等类&#xff09;&#xff0c;提取 OWL-S File中的ServiceProfile, ServiceModel, and ServiceGrounding用于匹配、组合、调用服务&#xff1b; ProtegeOWL-S Editor…...

切实加强政府网站建设与管理/搜索百度指数

2019独角兽企业重金招聘Python工程师标准>>> 格式&#xff1a;IF(Condition,A,B) condition条件成立则执行A&#xff0c;否则执行B。 例如&#xff1a;IF(ret1,from_unixtime(rpttime),) 转载于:https://my.oschina.net/guoying1024/blog/698900...