Android的消息机制--Handler
一、四大组件概述
Android的消息机制是由Handler、Message、MessageQueue,Looper四个类支撑,撑起了Android的消息通讯机制,Android是一个消息驱动系统,由这几个类来驱动消息与事件的执行
Handler:
- 用来发送消息和处理消息
- 无论使用的post ,还是send,都会执行enqueueMessage 方法,将消息加到队列中
- 发送的消息并不是立刻得到执行的,所以必须有个地方把它存起来,也就是MessageQueue
MessageQueue
- 基于单向链表,以触发时间的顺序排放在队列中,链表头部信息被触发的时间是最接近的
- 队列中的消息怎样让它在必要的时间得到执行,就需要依靠Lopper
有三种消息类型:
BarrierMessage:屏障消息
AsyncMessage:异步消息
Message:同步消息
Lopper
- 无限循环驱动器
- 在它内部有个loop 方法,开启无限循环,从头遍历这个队列,检查满足条件的消息,有就把它取出来进行分发执行,没有或者消息为空时,当前线程就会进入阻塞状态,从而释放掉CPU 的资源占用,当有新消息进来的时候就会唤醒当前线程,从而继续遍历队列中是否有满足条件的消息,所以Looper并不会真的一直无限循环下去
Message
消息
- long when:该消息被执行的时间戳,这个时间戳是它在队列中排队的唯一依据
- Message next:消息队列是一条单向链表,每一条消息都会包含下一条消息的引用关系,从而形成单向链表
- Handler target:代表着该message是由哪一个handler发送的,在消费这条消息时也由这个target来消费
- 一个线程最多存在一个Lopper
- 一个Looper对应一个MessageQueue
- 一个MessageQueue中可以存在多个Message对象
- 一个MessageQueue 或者一个Looper 对应多个handler
二、消息分发的优先级
- Message的回调方法:message.callback.run() 优先级最高
- Handler的回调方法:Handler.mCallback.handleMessage(msg)
- Handler的默认方法:handler.handleMessage(msg)
//1.直接在Runnable中处理任务handler.post(runable = Runnable {//这条消息在消费时,首先回调给Message中的callback,也就是runable对象})//2.使用Handler.callback来接收处理消息val handler = object :Handler(callback{//在创建handler的时候是可以传递一个callback 的,在消息分发的时候首先把消息分发给callback 来处理return@callback true})//3.最常用的handler的handlerMessage方法
三、疑问点
1.在使用handler的时候并没有指定Looper ,这三个类是怎么关联起来的
在创建实例对象时虽然没有传递Looper对象,但是在构造函数的重载里会调用Looper.myLooper来获取当前线程绑定的Looper对象
2.主线程的Looper是在哪里创建的?
在ActivityThread的main方法中调用Looper.prepareMainLooper()创建了主线程的Looper对象,然后调用loop()开启消息队列循环,所以在主线程中创建Handler不用给他创建Looper
如果一个线程的Looper对象没有调用prepare 方法,它的Looper是为空的
3.Looper.myLooper是如何保证获取到的Looper是当前线程的Looper 对象?
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));}
无论是主线程的Looper 还是子线程的Looper都只能调用prepare()方法或者prepareMain()方法,在prepare()方法中首先判断在这个当前线程这个Looper对象是否已经被创建过了,如果是,再次调用该方法就会报错。
这就是一个线程最多只能存在一个Looper 对象的保证
接下来把Looper 对象保存到了ThreadLocal中,在获取Looper 时也是从ThreadLocal 这个对象中得到的
ThreadLocal:
是用来存储数据的,使其成为线程的私有局部变量,通过它提供的get、set来访问
好处:可以在线程的任意地方去访问这个局部变量,不用传来传去
4.如何让子线程拥有消息分发的能力?如何在子线程中弹出Toast?
默认子线程是没有Looper 对象的,要主动调用Looper.prepare 方法以及 Looper.loop方法,在这两个方法中间就可以弹出Toast 了,在接着给它创建一个handler,在主线程中拿到这个handler对象,就可以处理在子线程中处理主线程发送过来的消息了
Toast在没有显示出来之前,它还没有被添加到窗口上,对它的操作就不会触发线程的检查
实现方式跟ActivityThread方式一样
需要注意的是一旦给子线程执行了这两个方法,要在必要的时候调用Looper.quit 方法,让Looper 退出循环,否则会一直循环下去,这个线程就不会被销毁,不会被回收
四、消息入队
一条消息被插入到MessageQueue时做了哪些事情?
发送一条消息时主要有两种方法,一种是post开头的,一种是send开头的,无论使用的哪种方式发送,都会以Message的形式插入到队列中
对于post,发送一条消息时,都会通过getPostMessage,把runnable对象包装成Message对象,再次调用sendMessageDelayd方法把它加入到队列中
享元设计模式,共用已创建的对象 避免重复创建对象
getPostMessage方法中在获取Message对象时,使用的是Message.obtain()方法
Message提供了消息复用池的能力,最多会缓存50条Message对象,通过.obtain()方法来获取 Message对象是可以复用的,不需要每次都去创建一个新的
采用了链表的形式来管理消息池,链表的插入和删除比ArrayList快
还提供了消息回收能力,当消息被分发完了之后就会调用recycleUnchecked,将Message的对象进行重置,情况数据,并将其插入到链表的头节点中,它是一个队头复用机制
通过new Message出来的对象会出现大量的临时的Message对象,会导致内存占用率过高
消息入队,按照消息被执行的时间戳when插入队列
- 无论是post、还是send,最终都会执行enqueueMessage,把消息插入到队列中
- postSyncBarrier:发送一条屏障消息
新消息插入时按照消息插入的时间插入到队列中
enqueueMessage
Handler.enqueueMessage
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {msg.target = this;……return queue.enqueueMessage(msg, uptimeMillis);}
将Message的target赋值成当前的Handler,在下面会对这个target进行判空,空的话直接抛出异常
消息被消费的时候会通过handlerDispatchMessahe方法来完成
MessageQueue.enqueueMessage
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.");}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;//mMessage始终是队头消息,拿到队头,这个队列才能执行增删改查的操作Message p = mMessages;boolean needWake;//p == null 说明队头为null,则队列为空//when == 0 或者新消息触发的时间戳为0// when < p.when 或者消息被触发的时间小于队头消息被触发的时间if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.//满足条件就把新消息插入到队列的头部//把新消息的next节点指向原来的头结点,然后将新信息赋值给头消息msg.next = p;mMessages = msg;//如果当前Looper处于休眠状态,则本次插入消息之后需要唤醒//mBlocked:是否处于阻塞状态,只有对列为空,队列当中没有可处理消息的时候,线程才会进入阻塞状态,mBlocked才会为trueneedWake = 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.//needWake:要不要唤醒线程,目的是让异步消息尽早的执行//mBlocked:当前线程是否处于休眠状态//p.target == null:队头消息是否为空,队头消息是异步同步屏障消息//msg.isAsynchronous:新消息是异步消息needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;//for循环:找到一个合适的位置来插入这条新消息for (;;) {prev = p;p = p.next;//找到合适的位置退出for循环if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}//调整链表中节点的指向位置,实现消息插入队列的目的//msg:新消息 p:下一条消息 prev:上一条消息msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.//Looper被唤醒if (needWake) {//线程被唤醒,使用nativeWake native方法nativeWake(mPtr);}}return true;}
enqueueMessage在插入一条新消息时,主要是检查消息是否都具备target对象,否则消息是无法被处理的,还会选择性的决定唤醒当前的线程,继续轮询队列是否有符合条件的消息拿出来处理
postSyncBarrier 同步屏障消息
message.target == null ,这类消息不会真的执行,起到标记作用
MessageQueqe在遍历消息队列时,如果队头是同步屏障消息,那么会忽略同步消息,优先让异步消息得到执行
一般异步消息和同步屏障消息会一同使用
异步消息 & 同步屏障
使用场景:
- ViewRootImpl接收屏幕垂直同步信息事件用于驱动UI测绘
- ActivityThread接收AMS的事件驱动生命周期
- InputMethodMessage分发软键盘输入事件
- PhoneWindowManager分发电话页面各种事件
目的:
让重要的消息尽可能早的得到执行
注意:
- 开发过程中无法使用,只能系统源码使用
- 必须使用MessageQueue来发生,Handler是无法发送同步屏障消息的,只能用来发送异步消息和同步消息
MessageQueue#postSyncBarrier
public int postSyncBarrier() {//使用uptimeMillis,意思是:发送了这条消息,在这期间如果设备进入休眠状态,那么消息是不会执行的,设备被唤醒之后才会执行return postSyncBarrier(SystemClock.uptimeMillis());}
- currentTimeMillis() 系统当前时间,即日期时间,可以被系统设置修改,如果设置系统时间,时间值也会发生改变
- uptimeMillis() 自开机后,经过的时间,不包括深度休眠的时间
sendMessageDelay.postDelay 也都使用了这个时间戳
问题:
如果使用handler发送一条消息,然后让设备进入休眠,也就是先熄屏,然后长时间不操作手机,这条消息会不会得到执行,为什么?
进入休眠之后,消息是不会被触发的,因为设备休眠之后uptimeMillis是不会被累加的
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赋值//区分是不是同步屏障,就看target是否等于null,等于null,就是同步屏障消息msg.when = when;msg.arg1 = token;Message prev = null;Message p = mMessages;if (when != 0) {//遍历队列所有消息,直到找到一个message.when > msg.when 的消息,决定新消息插入的位置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;}return token;}}
屏障消息在插入队列时是没有主动唤醒线程的,因为屏障消息并不需要得到执行,也不需要唤醒这个线程去轮询它
屏障消息的移除,谁添加的就由谁来移除
比如ViewRootImpl,在接收到垂直同步信号的到达,发送一条异步消息,并发送了一条屏障消息,当接收到异步消息时,ViewRootImpl就会把同步屏障消息从队列中移除
问题:
ViewRootImpl是如何在UI测绘的工作优先得到执行的?
发送了同步屏障和异步消息
五、消息分发
Looper#loop()
队列中的消息之所以能得到分发,是由于Looper中的loop方法,会开启一个无限for循环消息的驱动器,在这个无限for循环中会调用MessageQueue的next方法,去获取一个可执行的msg对象
这个next方法可能使得当前线程进入一个阻塞的状态,此时这个方法不会有返回值,下面的分发代码就不会得到执行,所以这个无限for循环并不会一直空轮询下去,
目的:只是不让这个线程退出,因为一个线程任务执行完成,就会自动退出,如果想让它不退出,开启一个while循环等待一段时间,这里也是一样的道理
直到队列中有可处理的消息才会返回
for(;;){//取出队列中的消息Message msg = me.mQueue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return false;}……//分发消息msg.target.dispatchMessage(msg);……//回收Messagemsg.recycleUnchecked();
}
拿到消息之后调用msg.target.dispatchMessage(msg) 去分发消息
当消息处理完成之后调用msg.recycleUnchecked()方法回收Message,留着复用
总结:
loop方法的作用是从队列中去取消息,然后分发,然后回收
MessageQueue#next()
首先也是开启一个for循环,在消息分发时可能存在消息的插入、删除,而且队列是一个单向链表无法确切知道消息插入时是有多少的,当这个next方法找到一个合适的消息时就会退出for循环
int nextPollTimeoutMillis = 0;for (;;) {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) {// 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.nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// Got a message.mBlocked = false;//prevMsg不为空说明需要删除的是中间的消息,只需要上一条消息的next指向msg的next,为空说明要删除的这条消息是队头消息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;}} else {// No more messages.//msg对象为空,说明队头对象为空,也就是当前队列为空,此时把nextPollTimeoutMillis置为-1,looper将进入永久休眠,直到有新消息到达nextPollTimeoutMillis = -1;}}
通过nativePollOnce这个native方法进行阻塞,传递的nextPollTimeoutMillis,如果这个值是大于0的,就会使得当前线程进入阻塞,并且释放掉对CPU资源的使用权,尽管Looper中有个无限for循环,但是不会造成CPU资源的过多占用
由于nextPollTimeoutMillis第一次for循环时是等于0的,所以第一次不会使得这个线程进入阻塞的,但是如果在接下来的循环中没有找到一个合适的、需要处理的消息,这个nextPollTimeoutMillis会被更新,在第二次循环的时候如果这个值不等于0,这个线程就会进入阻塞状态,如果这个值等于-1,当前线程就会进入无限阻塞状态,直到有新消息插入时,才会被唤醒
nextPollTimeoutMillis等于多少,这个线程就会被阻塞多少ms,超时之后就会继续执行以下代码
延迟消息是怎么得到保证的?
首先是通过uptimeMillis去计算出应该被执行的时间戳,然后借助nativePoll阻塞一段时间,超时之后自动恢复 ,然后继续往下检查是否有满足条件的消息,如果有就拿出来去执行
如何去取消息?
把队头消息赋值给一个临时变量msg,防止插入消息,队头可能被改变,然后判断msg是否是同步屏障消息(也就是判断msg.target == null),如果是通过do-while循环,在while中判断这个msg 不是一个异步消息,也就是说如果这个消息是异步消息,就会退出do-while循环
这个屏障消息唯一的作用就是:当它处于队头时,next方法在检索消息时会跳过同步消息,会优先检索出所有的异步消息,让它们优先执行
msg.target对象如果不为空,不会执行do-while循环,就会按照时间顺序来检索分发消息
判断消息msg是否为空,说明队头对象为空,也就是当前队列为空,此时把nextPollTimeoutMillis置为-1,looper将进入永久休眠,线程进入无限阻塞状态,直到有新消息到达
不为空时,也就是找到了一条消息,检查是否到达需要执行的时机
如果没有,去更新nextPollTimeoutMillis值,等于应该执行的时间 - 当前时间 ,计算出还应该延迟多久
否则说明找到了这条需要处理的消息,首先需要从队列中移除掉
preMsg不等于null,说明被删除的这条消息是队列中间的这条消息,preMsg代表被删除消息的前一条消息,删除这条消息,只需要将preMsg的next节点指向这个消息的next节点
preMsg等于null,说明被删除的这条消息是队头消息,需要将mMessage指向要删除消息的下一个节点
最后把msg对象返回回去,让它去分发,去执行
idler.queueIdle()
if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) {// No idle handlers to run. Loop and wait some more.mBlocked = true;continue;}
判断队头Message是否为空,也就是队列中没有任务了,或者队头消息时间还没有到达可执行的时机,在第二次for循环中,线程即将进入阻塞状态,在进入阻塞状态之前,称之为空闲状态,判断有没有向MessageQueue中注册IdleHandler,用于监听这个状态
IdleHandler:
可以监听当前线程是否即将进入空闲状态,也就是说通过事件的监听来做一些延迟的初始化,以及数据加载、日志上报等工作,而不是有任务就提交,从而避免抢占重要的资源
如果pendingIdleHandlerCount大于0,就会去调用idler.queueIdle()方法
for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}
最后会将nextPollTimeoutMillis延迟时间置为0,既然业务层监听了线程的空闲状态,在queueIdle这个方法里,就有可能再次产生新的消息,为了让新消息尽可能早的得到执行,此时不需要让线程进入休眠了
nextPollTimeoutMillis = 0;
在Android中存在两套消息机制,一套是Java的,一套是C++的,本质上是独立的,在Java中MessageQueue调用nativePollOnce的主要原因是借助native消息机制所实现的线程阻塞能力
六、问题解惑
http://t.csdnimg.cn/ZBfg7
相关文章:
Android的消息机制--Handler
一、四大组件概述 Android的消息机制是由Handler、Message、MessageQueue,Looper四个类支撑,撑起了Android的消息通讯机制,Android是一个消息驱动系统,由这几个类来驱动消息与事件的执行 Handler: 用来发送消息和处…...
获取用户信息与token理解
获取用户信息和token是在开发Web应用程序时常见的需求,可以通过以下步骤来实现: 用户登录:用户在应用程序中输入用户名和密码进行登录验证。一旦验证成功,应用程序会生成一个唯一的token,并将其返回给客户端。存储tok…...

网络设备和网络软件
文章目录 网络设备和网络软件网卡交换机交换机的三个主要功能交换机的工作原理第二层交换和第三层交换交换机的堆叠和级联 路由器路由器工作原理 网关网关的分类 无线接入点(AP)调制解调器网络软件 网络设备和网络软件 网卡 网络接口卡又称网络适配器,简称网卡。网…...
全连接层是什么
个人浅显的看法: 当前层的每一个神经元,都和下一层的每一个神经元有连接,叫全连接层。 当前层有n个神经元,下一层有m个神经元,则全连接层,当前层的n个神经元和下一层m个神经元都有连接...

JAVA工程师面试专题-《Redis》篇
目录 一、基础 1、Redis 是什么 2、说一下你对redis的理解 3、Redis 为什么这么快? 4、项目中如何使用缓存? 5、为什么使用缓存? 6、Redis key 和value 可以存储最大值分别多是多少? 7、Redis和memcache有什么区别…...
JavaScript BOM
BOM:浏览器对象模型,可以让我们通过js来操作浏览器 window 代表整个浏览器窗口 同时也是页面中的全局对象 Location 代表浏览器地址栏信息 Navigator 代表浏览器信息 可以获取不同的浏览器信息 History 代表浏览器的历史记录 Screen 代表用户的屏幕信…...

uniapp微信小程序-项目实战修改密码
图标是使用uview里面的图标,icfont也可以 以下是所有代码 <template><view><!-- 密码三个 --><view class"password" v-for"(item,index) in userList"><view class"contentuser"><view class&qu…...

linux系统---防火墙拓展
目录 一、iptables 1.基本语法 2.四表五链——重点记忆 2.1四表 2.2五链 2.3总结 3.iptables选项示例 3.1 -Z 清空流量计数 3.2 -P 修改默认规则 3.3 -D 删除规则 3.4 -R 指定编号替换规则 4.白名单 5.通用匹配 6.示例 6.1添加回环网卡 6.2可以访问端口 6.3 主…...
就业的二三事
先说一下当前本人的情况:双非本一,研二在读,一篇图像处理方面的sci一区(二作),日常工作语言为python,有过一段开源实习。要开始准备实习了,发个帖子记录一下自己所收集的信息。 前几…...

Go语言必知必会100问题-05 接口污染
接口污染 在Go语言中,接口是我们设计和编写代码的基石。然而,像很多概念一样,滥用它是不好的。接口污染是指用不必要的抽象来编写代码(刻意使用接口),使得代码更难以理解。这是具有不同习惯,特…...
FastBee商业版本源码获取下载
一、系统功能 系统功能功能说明开源版本商业版本产品管理产品详情、产品物模型、产品分类、设备授权、产品固件支持支持设备管理设备详情、设备分组、设备日志、设备分享、设备实时控制、实时状态、数据监测支持支持物模型管理属性(设备状态和监测数据)…...
Java实战:Spring Boot集成Elasticsearch全文搜索引擎
本文将详细介绍如何在Spring Boot应用程序中集成Elasticsearch全文搜索引擎。我们将探讨Elasticsearch的基本概念,以及如何使用Spring Boot和Spring Data Elasticsearch模块来实现全文搜索功能。此外,我们将通过具体的示例来展示如何在Spring Boot应用程…...

python 进程笔记二(通讯) (概念+示例代码)
1、为什么要掌握进程间通信 Python代码效率由于受制于GIL全局锁限制,多线程不能利用多核CPU来加速,而多进程方式却可以绕过GIL限制, 发挥多CPU加速的优势,达到提高程序的性能的目的。 然而进程间通信却是不得不考虑的问题。 进程不同于线程&a…...
电机控制-----电机极对数,相电感,相电阻以及磁链常数的测量
电机控制-----电机极对数,相电感,相电阻以及磁链常数的测量 我们在做电机控制的时候,拿到一个电机首先要知道它的参数,然后才能进行相应的开发,我这里介绍的是通过平常常用的手段去获得电机的参数:极对数&…...
SQL注入之oracle注入+SQLbypass+sqlmap实战
学习路还很长,切莫轻言放弃! 目录 Oracle数据库介绍 Oracle数据库和MySQL数据库的差别 Oracle数据库注入 SQLbypass姿势 sqlmap工具实战(kali自带) Oracle数据库介绍 Oracle数据库是全球最知名的关系型数据库管理系统(RDBMS)…...

【GPTs分享】GPTs分享之Write For Me
Write For Me 是一个专门定制的GPT版本,旨在为用户提供高质量的文本内容创作服务。它适用于各种写作需求,从商业计划、学术文章到创意故事等。下面是从简介、主要功能、使用案例、优点和局限性几个方面对Write For Me 的详细介绍。 简介 Write For Me …...

css4浮动+清除浮动
浮动 一.常见网页布局1.三种布局方式2.布局准则 二.浮动(float)1.好处2.概念3.三大特性4.使用5.常见网页布局模板6.注意点 三.清除浮动1.why2.本质3.语法4.四种way(后三个都是给父级添加)清除浮动总结 一.常见网页布局 1.三种布局…...

外包干了3个月,技术倒退明显...
先说情况,大专毕业,18年通过校招进入湖南某软件公司,干了接近6年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…...

STM32控制数码管从0显示到99
首先 先画电路图吧!打开proteus,导入相关器件,绘制电路图。如下:(记得要保存啊!发现模拟一遍程序就自动退出了,有bug,我是解决不了,所以就是要及时保存,自己重…...

【机器学习算法】KNN鸢尾花种类预测案例和特征预处理。全md文档笔记(已分享,附代码)
本系列文章md笔记(已分享)主要讨论机器学习算法相关知识。机器学习算法文章笔记以算法、案例为驱动的学习,伴随浅显易懂的数学知识,让大家掌握机器学习常见算法原理,应用Scikit-learn实现机器学习算法的应用࿰…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...