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实现机器学习算法的应用࿰…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文通过代码驱动的方式,系统讲解PyTorch核心概念和实战技巧,涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...