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

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

二、消息分发的优先级

  1. Message的回调方法:message.callback.run()  优先级最高
  2. Handler的回调方法:Handler.mCallback.handleMessage(msg)
  3. 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&#xff0c;Looper四个类支撑&#xff0c;撑起了Android的消息通讯机制&#xff0c;Android是一个消息驱动系统&#xff0c;由这几个类来驱动消息与事件的执行 Handler&#xff1a; 用来发送消息和处…...

获取用户信息与token理解

获取用户信息和token是在开发Web应用程序时常见的需求&#xff0c;可以通过以下步骤来实现&#xff1a; 用户登录&#xff1a;用户在应用程序中输入用户名和密码进行登录验证。一旦验证成功&#xff0c;应用程序会生成一个唯一的token&#xff0c;并将其返回给客户端。存储tok…...

网络设备和网络软件

文章目录 网络设备和网络软件网卡交换机交换机的三个主要功能交换机的工作原理第二层交换和第三层交换交换机的堆叠和级联 路由器路由器工作原理 网关网关的分类 无线接入点(AP)调制解调器网络软件 网络设备和网络软件 网卡 网络接口卡又称网络适配器&#xff0c;简称网卡。网…...

全连接层是什么

个人浅显的看法: 当前层的每一个神经元&#xff0c;都和下一层的每一个神经元有连接&#xff0c;叫全连接层。 当前层有n个神经元&#xff0c;下一层有m个神经元&#xff0c;则全连接层&#xff0c;当前层的n个神经元和下一层m个神经元都有连接...

JAVA工程师面试专题-《Redis》篇

目录 一、基础 1、Redis 是什么 2、说一下你对redis的理解 3、Redis 为什么这么快&#xff1f; 4、项目中如何使用缓存&#xff1f; 5、为什么使用缓存&#xff1f; 6、Redis key 和value 可以存储最大值分别多是多少&#xff1f; 7、Redis和memcache有什么区别&#xf…...

JavaScript BOM

BOM&#xff1a;浏览器对象模型&#xff0c;可以让我们通过js来操作浏览器 window 代表整个浏览器窗口 同时也是页面中的全局对象 Location 代表浏览器地址栏信息 Navigator 代表浏览器信息 可以获取不同的浏览器信息 History 代表浏览器的历史记录 Screen 代表用户的屏幕信…...

uniapp微信小程序-项目实战修改密码

图标是使用uview里面的图标&#xff0c;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 主…...

就业的二三事

先说一下当前本人的情况&#xff1a;双非本一&#xff0c;研二在读&#xff0c;一篇图像处理方面的sci一区&#xff08;二作&#xff09;&#xff0c;日常工作语言为python&#xff0c;有过一段开源实习。要开始准备实习了&#xff0c;发个帖子记录一下自己所收集的信息。 前几…...

Go语言必知必会100问题-05 接口污染

接口污染 在Go语言中&#xff0c;接口是我们设计和编写代码的基石。然而&#xff0c;像很多概念一样&#xff0c;滥用它是不好的。接口污染是指用不必要的抽象来编写代码&#xff08;刻意使用接口&#xff09;&#xff0c;使得代码更难以理解。这是具有不同习惯&#xff0c;特…...

FastBee商业版本源码获取下载

一、系统功能 系统功能功能说明开源版本商业版本产品管理产品详情、产品物模型、产品分类、设备授权、产品固件支持支持设备管理设备详情、设备分组、设备日志、设备分享、设备实时控制、实时状态、数据监测支持支持物模型管理属性&#xff08;设备状态和监测数据&#xff09;…...

Java实战:Spring Boot集成Elasticsearch全文搜索引擎

本文将详细介绍如何在Spring Boot应用程序中集成Elasticsearch全文搜索引擎。我们将探讨Elasticsearch的基本概念&#xff0c;以及如何使用Spring Boot和Spring Data Elasticsearch模块来实现全文搜索功能。此外&#xff0c;我们将通过具体的示例来展示如何在Spring Boot应用程…...

python 进程笔记二(通讯) (概念+示例代码)

1、为什么要掌握进程间通信 Python代码效率由于受制于GIL全局锁限制&#xff0c;多线程不能利用多核CPU来加速&#xff0c;而多进程方式却可以绕过GIL限制, 发挥多CPU加速的优势&#xff0c;达到提高程序的性能的目的。 然而进程间通信却是不得不考虑的问题。 进程不同于线程&a…...

电机控制-----电机极对数,相电感,相电阻以及磁链常数的测量

电机控制-----电机极对数&#xff0c;相电感&#xff0c;相电阻以及磁链常数的测量 我们在做电机控制的时候&#xff0c;拿到一个电机首先要知道它的参数&#xff0c;然后才能进行相应的开发&#xff0c;我这里介绍的是通过平常常用的手段去获得电机的参数&#xff1a;极对数&…...

SQL注入之oracle注入+SQLbypass+sqlmap实战

学习路还很长&#xff0c;切莫轻言放弃&#xff01; 目录 Oracle数据库介绍 Oracle数据库和MySQL数据库的差别 Oracle数据库注入 SQLbypass姿势 sqlmap工具实战(kali自带) Oracle数据库介绍 Oracle数据库是全球最知名的关系型数据库管理系统&#xff08;RDBMS&#xff09…...

【GPTs分享】GPTs分享之Write For Me

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

css4浮动+清除浮动

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

外包干了3个月,技术倒退明显...

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

STM32控制数码管从0显示到99

首先 先画电路图吧&#xff01;打开proteus&#xff0c;导入相关器件&#xff0c;绘制电路图。如下&#xff1a;&#xff08;记得要保存啊&#xff01;发现模拟一遍程序就自动退出了&#xff0c;有bug&#xff0c;我是解决不了&#xff0c;所以就是要及时保存&#xff0c;自己重…...

【机器学习算法】KNN鸢尾花种类预测案例和特征预处理。全md文档笔记(已分享,附代码)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论机器学习算法相关知识。机器学习算法文章笔记以算法、案例为驱动的学习&#xff0c;伴随浅显易懂的数学知识&#xff0c;让大家掌握机器学习常见算法原理&#xff0c;应用Scikit-learn实现机器学习算法的应用&#xff0…...

Windows 自带的 Linux 子系统(WSL)安装与使用

WSL官网安装教程&#xff1a; https://learn.microsoft.com/zh-cn/windows/wsl/install Windows 自带的Linux子系统&#xff0c;比用VM什么的香太多了。可以自己看官方教程&#xff0c;也可以以下步骤完成。 如果中间遇到我没遇到的问题百度&#xff0c;可以在评论区评论&#…...

C语言--贪吃蛇

目录 1. 实现目标2. 需掌握的技术3. Win32 API介绍控制台程序控制台屏幕上的坐标COORDGetStdHandleGetConsoleCursorinfoCONSOLE_CURSOR_INFOSetConsoleCursorInfoSetConsoleCursorPositionGetAsyncKeyState 4. 贪吃蛇游戏设计与分析地图<locale.h>本地化类项setlocale函…...

原型设计工具Axure RP

Axure RP是一款专业的快速原型设计工具。Axure&#xff08;发音&#xff1a;Ack-sure&#xff09;&#xff0c;代表美国Axure公司&#xff1b;RP则是Rapid Prototyping&#xff08;快速原型&#xff09;的缩写。 下载链接&#xff1a;https://www.axure.com/ 下载 可以免费试用…...

HeadFirst读书笔记

一、设计模式入门 1、使用模式最好的方式“把模式装进脑子里&#xff0c;然后在你的设计和已有的应用中&#xff0c;寻找何处可以使用它们”。以往是代码复用&#xff0c;现在是经验复用。 2、软件开发的一个不变的真理就是变化。 二、设计原则 1、找出应用中可能需要变化之…...

【C++】---内存管理new和delete详解

一、C/C内存分布 C/C内存被分为6个区域&#xff1a; &#xff08;1&#xff09; 内核空间&#xff1a;存放内核代码和环境变量。 &#xff08;2&#xff09;栈区&#xff1a;向下增长&#xff08;存放非静态局部变量&#xff0c;函数参数&#xff0c;返回值等等&#xff09; …...

go-zero微服务入门教程

go-zero微服务入门教程 本教程主要模拟实现用户注册和用户信息查询两个接口。 准备工作 安装基础环境 安装etcd&#xff0c; mysql&#xff0c;redis&#xff0c;建议采用docker安装。 MySQL安装好之后&#xff0c;新建数据库dsms_admin&#xff0c;并新建表sys_user&#…...

蓝桥杯刷题--python-12

3768. 字符串删减 - AcWing题库 nint(input()) sinput() res0 i0 while(i<n): if s[i]x: ji1 while(j<n and s[j]x): j1 resmax(j-i-2,0) ij else: i1 print(res) 3777. 砖块 - AcWing题库 # https://www.a…...

LeetCode LCR 085.括号生成

正整数 n 代表生成括号的对数&#xff0c;请设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”] 示例 2&#xff1a; 输入&#x…...

抖音视频评论数据提取软件|抖音数据抓取工具

一、开发背景&#xff1a; 在业务需求中&#xff0c;我们经常需要下载抖音视频。然而&#xff0c;在网上找到的视频通常只能通过逐个复制链接的方式进行抓取和下载&#xff0c;这种操作非常耗时。我们希望能够通过关键词自动批量抓取并选择性地下载抖音视频。因此&#xff0c;为…...

【web】云导航项目部署及环境搭建(复杂)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、项目介绍1.1项目环境架构LNMP1.2项目代码说明 二、项目环境搭建2.1 Nginx安装2.2 php安装2.3 nginx配置和php配置2.3.1 修改nginx文件2.3.2 修改vim /etc/p…...

代理网页 在线/游戏优化

存储格式区别非常大&#xff0c;默认是textFile&#xff0c;这种格式&#xff0c;如果你存储的数据格式是其他&#xff0c;然后你生成代码的时候 写成了,create table as (select * from xxxx)。则会导致&#xff0c;你查询的表和你创建的表&#xff0c;数据量不一样大。 原因…...

做代练网站能备案/广西南宁市有公司网站设计

关于AVL树&#xff08;平衡二叉搜索树&#xff0c;高度为lgn&#xff09;的讲解&#xff0c;双手呈上某大佬博客&#xff1a;https://www.cnblogs.com/zhuwbox/p/3636783.html 我从这题get到一个新的结构体写法&#xff08;姿势&#xff09;&#xff1a; typedef struct treeNo…...

兰州市建设局网站/seo基础理论

1.创建用户名以及密码: 右键我的电脑 -》 管理-》本地用户和组-》右键用户-》新用户----设置用户名密码&#xff1b; 2.安装IIS 和FTP :控制面板-》程序-》打开或关闭windows功能-》勾选FTP WEB管理工具以及&#xff08;TFCP我不知道这个有没有用&#xff09; 3.建立FTP&#x…...

买了域名怎么做网站/百度集团公司简介

无悔入华夏怎么通关快呢?下面小编为大家带来无悔入华夏快速通关攻略,一起看看吧.名臣带的&#xff1a;墨子(墨守加防御)&#xff0c;李牧(神&#xff0c;前期点出据守加防御&#xff0c;中期点出另一个加兵&#xff0c;相当于子弟兵)&#xff0c;商鞅(拿来治国的)&#xff0c;…...

青岛网络公司/企业seo推广外包

在项目中需要进行Fragment的切换&#xff0c;一直都是用replace()方法来替换Fragment&#xff1a; public void switchContent(Fragment fragment) {if(mContent ! fragment) {mContent fragment;mFragmentMan.beginTransaction().setCustomAnimations(android.R.anim.fade_in…...

西安月子中心网站制作/seo数据是什么

asp.net教程:用ADO向Excel批量导入数据 和前面一篇用OleDB的方法类似&#xff0c;我们可以用ADO从RecordSet对象向Excel批量插入数据&#xff0c;这个方法无法自动复制字段名。 我们需要引用ADO和Excel的com对象 参考代码如下 usingSystem;usingSystem.Collections.Generic;us…...