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

handler解析(2) -Handler源码解析

目录

基础了解:

相关概念解释

整体流程图:

源码解析

Looper

总结:

sendMessage

总结:

ThreadLocal


基础了解:

Handler是一套 Android 消息传递机制,主要用于线程间通信。实际上handler其实就是主线程在起了一个子线程,子线程运行并生成Message,Looper获取message并传递给Handler,Handler逐个获取子线程中的Message,在这个机制下中包括了Looper、MessageQueue,ThreadLocal等

相关概念解释


Handler、Message、Message Queue、Looper

Message :代表一个行为what或者一串动作Runnable, 每一个消息在加入消息队列时,都有明确的目标Handler

ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。ThreadLocal的作用是提供线程内的局部变量TLS,这种变量在线程的生命周期内起作用,每一个线程有他自己所属的值(线程隔离)

MessageQueue (C层与Java层都有实现) :以队列的形式对外提供插入和删除的工作, 其内部结构是以双向链表的形式存储消息的

Looper (C层与Java层都有实现) :Looper是循环的意思,它负责从消息队列中循环的取出消息然后把消息交给Handler处理

Handler :消息的真正处理者, 具备获取消息、发送消息、处理消息、移除消息等功能

整体流程图:

源码解析

分析源码:API 31

Looper

首先从new Handler点进去分析

 可以看到,handler的构造方法里先判断当前创建的handler是否为static的,如果不是会弹log,

The following Handler class should be static or leaks might occur: ,这块后面在解释为啥会这么设置

同时声明了需要个looper,然后这个looper是Looper.myLooper中获取的,然后如果looper如果为空的话,则会抛出异常:

Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()

可以看出这个looper还是挺重要的,然后发现消息队列也是在looper中声明的,那么 我们就来看这个looper是怎么获取的,点

Looper.myLooper()方法进去看

发现是sThreadLocal中获取的 ,然后全局搜这块的实现

发现是在prepare(boolean)中赋值的,这块主要是创建一个looper,然后再looper的构造方法中创建了消息队列message queue,最后添加到sThreadLocal中

同时发现这个set方法有点熟悉呀,点进去看,我们发现

 主要是通过ThreadLocalMap的set值实现的,这块是只进行一次set值,从而保证了一个looper的存在。

同时回到了之前的prepare()方法中,我们ctrl看看,发现有个方法特别熟悉

prepareMainLooper()

点进去看发现这块已经在

 ActivitThread中调用了,这块之前通过其他大佬了解到,其实当我们点击了app图标后,根据启动流程会执行到ActivityThread中,这块也是通俗意义上说的主线程,所以说如果是在主线程中使用handler的话是不需要在调用Looper.loop()方法的,因为已经创建好了。

 同时,我们注意到ActivityThread中的另外一个方法

点击进去看,发现 

/*** Poll and deliver single message, return true if the outer loop should continue.*/@SuppressWarnings("AndroidFrameworkBinderIdentity")private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return false;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " "+ msg.callback + ": " + msg.what);}// Make sure the observer won't change while processing a transaction.final Observer observer = sObserver;final long traceTag = me.mTraceTag;long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;if (thresholdOverride > 0) {slowDispatchThresholdMs = thresholdOverride;slowDeliveryThresholdMs = thresholdOverride;}final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);final boolean needStartTime = logSlowDelivery || logSlowDispatch;final boolean needEndTime = logSlowDispatch;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;final long dispatchEnd;Object token = null;if (observer != null) {token = observer.messageDispatchStarting();}long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);try {msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logSlowDelivery) {if (me.mSlowDeliveryDetected) {if ((dispatchStart - msg.when) <= 10) {Slog.w(TAG, "Drained");me.mSlowDeliveryDetected = false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",msg)) {// Once we write a slow delivery log, suppress until the queue drains.me.mSlowDeliveryDetected = true;}}}if (logSlowDispatch) {showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// Make sure that during the course of dispatching the// identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);}msg.recycleUnchecked();return true;}

根据注释发现这个方法是在当前线程中允许一个message queue进行for死循环从消息队列中取出消息,其中next方法会在下面分析,然后分发给msg.target中的dispatchMessage方法(其中msg.target是handler发送消息的时候赋值上去的,将在下面会说到),其中msg.target对应的就是handler,然后dispatchMessage源码为:

 嗯,这块代码不需要解释了吧,如果没设置callback的话,则会直接回调发送消息的handler所在线程的handlerMessage方法了

总结:

1.一个线程中只能有一个looper,这块是由ThreadLocal决定的,因为ThreadLocal中包含了一个ThreadLocalMap,在调用looper.prepare时将looper对象set进去ThreadLocal中

2.UI线程,就是主线程是不需要在调用looper.prepare跟looper.loop()的,因为这2个在ActivityThread中的main方法中已经调用过了,其中looper.prepare调用的为looper.prepareMainLooper()

3.looper.prepare方法主要做的事就是赋值looper对象到ThreadLocal中,其中looper对象实例化时,创建了消息队列message queue,Looper.loop方法主要做的事就是调用message queue.next方法循环拿消息,如果有消息的话,则将消息进行分发到msg.target上对应的handler.dispatchMessage中,其中handler.dispatchMessage就是熟悉的handlerMessage

sendMessage

其实这块我们常见的handler的那些操作,本质上都是调用sendMessage的,只是换了个说法而已,常见的handler操作handler.sendMessage(msg)、handler.sendEmptyMessage(1)

handler.postDelay()、handler.post(),下面我们一个个点进去看

1.handler.sendMessage(msg)

 

 

 2、sendEmptyMessage

 

 3.handler.postDelay(Runable,time)

 

 4.handler.post()

。是不是觉得跟postDelay一样,Runable通过getPostMessage方法,将传入的Runable变为 message的callback方法

 可以发现,这几个handler的方法,都会调用sendsendMessageDelayed,然后调用sendMessageAtTime,最后调用enqueueMessage,进行消息处理,同时我们发现sendMessageAtTime中会设定个时间SystemClock.uptimeMillis(),这块时间为手机开机后系统非深度休眠时间,而不是手机时间。所以手机上修改当前时间是不会影响Message执行的。一般我们也可以通过这个值来获取手机开机多久。

然后我们来看enqueueMessage,其他那些looper、message queue已经在handler初始化的时候获取到了,其中在handler初始化的时候,通过looper.prepare已经创建好了

 这块是跳转到message queue的enqueueMessage方法中

boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}//保证多线程入队先加锁synchronized (this) {//....msg.markInUse(); //标记正在使用msg.when = when; // when 属性Message p = mMessages; //拿到链表头部的消息boolean needWake;// 满足以下3种情况之一就把msg插入到链表头部//1.队列为null//2.当前时间没有延迟0//3.插入的时间比链表的头节点的when时间早if (p == null || when == 0 || when < p.when) {msg.next = p;mMessages = msg;needWake = mBlocked; //如果处于阻塞状态,需要唤醒} else {//唤醒标识 ,//4.如果p != null且msg并不是最早触发的,就在链表中找一个位置把msg插进去//5.如果处于阻塞状态,并且链表头部是一个同步屏障(target为null的Message),并且插入消息是最早的异步消息,需要唤醒(   //队列为空,或者队列头消息还未到执行时间,且当前消息待执行时间小于队列头消息,此时才需要唤醒)needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;//当遍历到队尾、或者是  msg的时间比当前时间更早 if (p == null || when < p.when) {break;}//发现了异步消息的存在,不需要唤醒if (needWake && p.isAsynchronous()) {needWake = false;}}//单链表、插入msg信息msg.next = p; // invariant: p == prev.nextprev.next = msg;}// 如果looper阻塞/休眠中,则唤醒looper循环机制处理消息if (needWake) {nativeWake(mPtr);//唤醒}}return true;
}

可以看到MessageQueue queue是通过链表形式对Message 进行存储,并通过when 的大小对 Message 进行排序。

其中,遍历插入过程如图:
遍历队列,当某个数据的时间戳优先级低于插入数据时,把数据插入;否则把数据放在队列尾部。


 

 
注意
Handler可以无限插入数据,没有大小限制。

然后我们回到之前分析过的looper中的loop方法

/*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the loop.*/@SuppressWarnings("AndroidFrameworkBinderIdentity")public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}me.mInLoop = true;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();// Allow overriding a threshold with a system prop. e.g.// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'final int thresholdOverride =SystemProperties.getInt("log.looper."+ Process.myUid() + "."+ Thread.currentThread().getName()+ ".slow", 0);me.mSlowDeliveryDetected = false;for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {return;}}}
@SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {// 从Looper中取出MessageQueue进行轮询获取消息msgMessage msg = me.mQueue.next(); if (msg == null) {// No message indicates that the message queue is quitting.return false;}//....try {// Message.target 就是对应的Handler.dispatchMessage回调消息msg.target.dispatchMessage(msg);}//....//回收消息msg.recycleUnchecked();return true;
}

 核心就是这个next方法了,这个方法主要是将存在message queue中的消息取出来的

@UnsupportedAppUsageMessage next() {// 如果消息循环已经退出并被处理,请返回此处。// 如果应用程序尝试退出后不支持的循环程序,则会发生这种情况。final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;//判断消息队列中是否有消息for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}//就是在这里根据nextPollTimeoutMillis判断是否要阻塞// 阻塞方法,主要是通过 native 层的 epoll 监听文件描述符的写入事件来实现的。// 如果 nextPollTimeoutMillis = -1,一直阻塞不会超时。// 如果 nextPollTimeoutMillis = 0,不会阻塞,立即返回。// 如果 nextPollTimeoutMillis > 0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// 尝试检索下一条消息。 如果找到则返回。final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {// 被障碍挡住了。 在队列中查找下一条异步消息。do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {//队列中拿到的消息不为nullif (now < msg.when) {// 下一条消息尚未准备好。 设置超时以使其在准备就绪时醒来。nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 正常返回处理...} else {// 队列中没有消息,标记阻塞looper循环进入休眠nextPollTimeoutMillis = -1;}// 现在已处理所有挂起的消息,处理退出消息。if (mQuitting) {dispose();return null;}// If first time idle, then get the number of idlers to run.// 空闲句柄仅在队列为空或将来要处理队列中的第一条消息(可能是屏障)时才运行。if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}...}...// 将空闲处理程序计数重置为0,这样我们就不会再次运行它们。pendingIdleHandlerCount = 0;// 在调用空闲处理程序时,可能已经传递了一条新消息,//因此返回并再次查找未处理消息,而无需等待。nextPollTimeoutMillis = 0;}}

 然后找到后返回loopOnce,将message分发给msg.target就是handler,然后就是如果有消息的话,则将消息进行分发到msg.target上对应的handler.dispatchMessage中,其中handler.dispatchMessage就是熟悉的handlerMessage,然后一切做完后会调用    msg.recycleUnchecked()进行消息的回收

具体流程如下:

总结:

1.handler.sendMessage、handler.sendEmptyMessage、handler.post、handler.postDelay本质上都是调用handler.sendsendMessageDelayed,然后调用sendMessageAtTime,最后调用enqueueMessage,进行添加消息处理

2.sendMessageAtTime中会设定个时间SystemClock.uptimeMillis(),这块时间为手机开机后系统非深度休眠时间,而不是手机时间。所以手机上修改当前时间是不会影响Message执行的。一般我们也可以通过这个值来获取手机开机多久

3.MessageQueue queue是通过链表形式对Message 进行存储,并通过when 的大小对 Message 进行排序。

4.Looper属于某个线程,而MessageQueue存储在Looper中,所以MessageQueue通过Looper特定的线程上关联,而Handler在构造中又与LooperMessageQueue相互关联,通过Handler发送消息的时候,消息就会被插入到Handler关联的MessageQueue中,而Looper会不断的轮询消息,从MessageQueue中取出消息给相应的Handler处理,所有最终通过Handler发送的消息就会被执行到Looper所在的线程上,这就是 Handler线程切换的原理,无论发送消息Handler对象处于什么线程,最终处理消息都是在Looper所在的线程。

5.handler 阻塞、唤醒

阻塞条件:

分析MessageQueue的next函数,发生阻塞只会存在以上两种情况

  1. 队列中消息个数为0,且没有可处理的IdleHandler,此时会一直阻塞
  2. 队列中消息个数不为0,但是队列头消息还未到执行时间,且没有IdleHandler要执行,此时会阻塞时间n,n表示队列头消息距离执行的时间

以上两种阻塞情况,mBlock都为true.只有这两种情况需要唤醒。

一言以蔽之,没有IdleHandle要执行,且队列中没有立即要可执行的消息时,会阻塞

唤醒条件:

  1. 队列为空,或者队列头消息还未到执行时间,且当前消息待执行时间小于队列头消息,此时才需要唤醒。一言以蔽之,也就是当前消息插入到队列头部时,才需要唤醒。

  2. mBlocked为true,也就是没有Idle消息要处理,且当前队列头消息时同步屏障消息,且当前消息时异步消息,此时需要立即处理,因为该异步消息对响应及时性要求比较高。

其实关于这个还涉及到一个面试题:“为啥looper.loop内部消息队列死循环不会导致应用卡死?”通过这个总结就很清晰了吧,

这里涉及线程,先说说说进程/线程,进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。

线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。

有了这么准备,再说说死循环问题:

对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。

真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

ThreadLocal

 

 通过get和set方法不难发现,其中频繁出现了一个ThreadLocalMap对象,这个变量跟map有点类似,是按键值对的方式对数据进行存储的,其中

key:指的当前ThreadLocal变量

value:T ,当前要存储的值

然后在点getMap方法看ThreadLocalMap是如何获取的

然后我们发现原来每个线程的threadLocals就是ThreadLocalMap来着,然后这个变量存储着ThreadLocal和对应的保存对象,这样的话,在不同的线程,访问同一个ThreadLocal对象,但是获取到的值是不一样的 ,这样就相对于用一个map存储所有线程的方式,会好很多了,因为那样的话管理也很混乱,每个线程有联系的话,也容易造成内存泄露

参考文章:

Handler源码分析 - 简书

Handler源码解析_Pioneer_Chang的博客-CSDN博客

Handler源码解析_handler messagequeue_醉饮千觞不知愁的博客-CSDN博客

Handler阻塞和唤醒条件 - 掘金

相关文章:

handler解析(2) -Handler源码解析

目录 基础了解&#xff1a; 相关概念解释 整体流程图&#xff1a; 源码解析 Looper 总结&#xff1a; sendMessage 总结&#xff1a; ThreadLocal 基础了解&#xff1a; Handler是一套 Android 消息传递机制,主要用于线程间通信。实际上handler其实就是主线程在起了一…...

【算法】kmp

KMP算法 名称由来 是由发明这个算法的三个科学家的名称首字母组成 作用 用于字符串的匹配问题 举例说明 字符串 aabaabaaf 模式串 aabaaf 传统匹配方法 第一步 aabaabaaf aabaaf 此时&#xff0c;b和f不一致&#xff0c;则把模式串从头和文本串的第二个字符开始比 第…...

git 常用命令之 git checkout

大家好&#xff0c;我是 17。 git checkout 是 git 中最重要最常用的命令之一&#xff0c;本文为大家详细解说一下。 恢复工作区 checkout 的用途之一是恢复工作区。 git checkout . checkout . 表示恢复工作区的所有更改,未跟踪的文件不会有变化。 恢复工作区的所有文件风…...

一些常见错误

500状态码: 代表服务器业务代码出错, 也就是执行controller里面的某个方法的过程中报错, 此时在IDEA的控制台中会显示具体的错误信息, 所以需要去看IDEA控制台的报错404状态码: 找不到资源找不到静态资源 检查请求地址是否拼写错误 检查静态资源的位置是否正确 如果以上都没有问…...

[单片机框架][调试功能] 回溯案发现场

程序莫名死机跑飞&#xff0c;不知道问题&#xff0c;那么下面教你回溯错误源 回溯案发现场一、修改HardFault_Handler1. xx.s 在启动文件&#xff0c;找到HardFault_Handler。并修改。2. 定义HardFault_Handler_C函数。&#xff08;主要是打印信息并存储Flash&#xff09;3. 根…...

MySQL主从同步-(二)搭建从机服务器

在docker中创建并启动MySQL从服务器&#xff1a;**端口3307docker run -d \-p 3307:3306 \-v /atguigu/mysql/slave1/conf:/etc/mysql/conf.d \-v /atguigu/mysql/slave1/data:/var/lib/mysql \-e MYSQL_ROOT_PASSWORD123456 \--name atguigu-mysql-slave1 \mysql:8.0.3创建MyS…...

Linux系列 备份与分享文档

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.备份与分享文档 1.使用压缩和解压缩工具 &#xff08;1&…...

SNI生效条件 - 补充nginx-host绕过实例复现中SNI绕过的先决条件

文章目录1.前置环境搭建2.测试SNI生效条件(时间)3. 证书对SNI的影响3.1 双方使用同一个证书&#xff1a;3.2 双方使用不同的证书与私钥4. 端口号区分测试4.1 端口号区分&#xff0c;证书区分&#xff1a;4.2 端口号区分,证书不区分&#xff1a;5.总结SNI运行机制6. SNI机制绕过…...

傻白探索Chiplet,Modular Routing Design for Chiplet-based Systems(十一)

阅读了Modular Routing Design for Chiplet-based Systems这篇论文&#xff0c;是关于多chiplet通信的&#xff0c;个人感觉核心贡献在于实现了 deadlock-freedom in multi-chiplet system&#xff0c;而不仅仅是考虑单个intra-chiplet的局部NoC可以通信&#xff0c;具体的一些…...

C语言静态库、动态库的封装和注意事项

1、动态库、静态库介绍 参考博客&#xff1a;《静态库和动态库介绍以及Makefile》&#xff1b; 2、代码目录结构和编译脚本 参考博客&#xff1a;《实际工作开发中C语言工程的目录结构分析》&#xff1b; 3、编写库的流程 (1)明确需求:需求是否合理、需求的使用场景、需求可能遇…...

MyBatis-Plus分页插件和MyBatisX插件

MyBatis-Plus分页插件和MyBatisX插件六、插件1、分页插件a>添加配置类b>测试八、代码生成器1、引入依赖2、快速生成十、MyBatisX插件1、新建spring boot工程a>引入依赖b>配置application.ymlc>连接MySQL数据库d>MybatisX逆向生成2、MyBatisX快速生成CRUD申明…...

年前无情被裁,面试大厂的这几个月…

2月份了&#xff0c;金三银四也即将来临&#xff0c;在这个招聘季&#xff0c;大厂也开始招人&#xff0c;但还是有很多人吐槽说投了很多简历&#xff0c;却迟迟没有回复… 另一面企业招人真的变得容易了吗&#xff1f;有企业HR吐槽&#xff0c;简历确实比以前多了好几倍&…...

基于Java的分片上传功能

起因&#xff1a;最近在工作中接到了一个大文件上传下载的需求&#xff0c;要求将文件上传到share盘中&#xff0c;下载的时候根据前端传的不同条件对单个或多个文件进行打包并设置目录下载。 一开始我想着就还是用老办法直接file.transferTo(newFile)就算是大文件&#xff0c…...

KDS安装步骤

KDS kinetis design studio 软件 第一步官网(https://www.nxp.com/ 注册账号下载set成功下载软件。 随着AI&#xff0c;大数据这些技术的快速发展&#xff0c;与此有关的知识也普及开来。如何在众多网站中寻找最有价值的信息&#xff0c;如何在最短的时间内获得最新的技…...

JavaSE-线程池(1)- 线程池概念

JavaSE-线程池&#xff08;1&#xff09;- 线程池概念 前提 使用多线程可以并发处理任务&#xff0c;提高程序执行效率。但同时创建和销毁线程会消耗操作系统资源&#xff0c;虽然java 使用线程的方式有多种&#xff0c;但是在实际使用过程中并不建议使用 new Thread 的方式手…...

开源代码的寿命为何只有1年?

说实话&#xff0c;如果古希腊的西西弗斯是一个在2016年编写开源代码的开发者&#xff0c;那他会有宾至如归的感觉。著名的西西弗斯处罚&#xff0c;是神话流传下来的&#xff0c;他被迫推一块巨大的石头上山&#xff0c;当登顶之后&#xff0c;只能眼睁睁看着它滚下去&#xf…...

完善登录功能--过滤器的使用

系列文章目录 Spring Boot读取配置文件内容的三种方式 Spring Boot自动配置–如何切换内置Web服务器 SpringBoot项目部署 上述为该系列部分文章&#xff0c;想了解更多可看我博客主页哦&#xff01; 文章目录系列文章目录前言一、创建自定义过滤器LoginCheckFilter二、在启动类…...

CSS基础:属性和关系选择器

字体属性 color 文本颜色 div{ color:red;} div{ color:#ff0000;} div{ color:rgb(255,0,0);} div{ color:rgba(255,0,0,.5);}font-size 文本大小 h1 {font-size:40px;} h2 {font-size:30px;} p {font-size:14px;}注意&#xff1a;chrome浏览器接受最小字体是12px font-we…...

设计模式:原型模式解决对象创建成本大问题

一、问题场景 现在有一只猫tom&#xff0c;姓名为: tom, 年龄为&#xff1a;1&#xff0c;颜色为&#xff1a;白色&#xff0c;请编写程序创建和tom猫属性完全相同的10只猫。 二、传统解决方案 public class Cat {private String name;private int age;private String color;…...

驱动开发(二)

一、驱动流程 驱动需要以下几个步骤才能完成对硬件的访问和操作&#xff1a; 模块加载函数 module_init注册主次设备号 <应用程序通过设备号找到设备>驱动设备文件 <应用程序访问驱动的方式> 1、手动创建 &#xff08;mknod&#xff09;2、程序自动创建file_oper…...

《狂飙》大结局,这22句经典台词值得细品

最近爆火的热播剧《狂飙》大家都看了吗&#xff1f; 剧情紧凑、演技炸裂、豆瓣评分9.0&#xff0c;可以说是开年评分最高的一部国产剧。 ​ 虽然大结局了。 里面有很多经典台词&#xff0c;值得每个人细细品味。 01 这世界不缺梦想 有本事你就去实现它 02 你这么善良 怎么跟坏…...

【计算机网络期末复习】第二章 物理层

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为想复习学校计算机网络课程的同学提供重点大纲&#xff0c;帮助大家渡过期末考~ &#x1f4da;专栏地址&#xff1a; ❤️如果有收获的话&#xff0c;欢迎点…...

多核异构核间通信-mailbox/RPMsg 介绍及实验

1. 多核异构核间通信 由于MP157是一款多核异构的芯片&#xff0c;其中既包含的高性能的A7核及实时性强的M4内核&#xff0c;那么这两种处理器在工作时&#xff0c;怎么互相协调配合呢&#xff1f; 这就涉及到了核间通信的概念了。 IPCC (inter-processor communication contr…...

【Rust日报】2023-02-11 从头开始构建云数据库 RisingWave - 为什么我们从 C++ 转向 Rust...

GTK4发布v0.60gtk4-rs代码库包含GTK4的Rust crates。还有个庞大的GObject库生态系统&#xff0c;其中许多库基于gtk-rs中包含的Rust绑定工具。 特别是&#xff1a;gtk-rs-core&#xff0c;一些核心库的绑定&#xff0c;例如 glib、gio、pango、graphenegstreamer-rs&#xff0c…...

Linux驱动开发(一)

linux驱动学习记录 一、背景 在开始学习我的linux驱动之旅之前&#xff0c;先提一下题外话&#xff0c;我是一个c语言应用层开发工作人员&#xff0c;在工作当中往往会和硬件直接进行数据的交互&#xff0c;往往遇到数据不通的情况&#xff0c;常常难以定位&#xff0c;而恰巧…...

Spring MVC 之返回数据(静态页面、非静态页面、JSON对象、请求转发与请求重定向)

文章目录1. 默认情况下返回静态页面2. 返回一个非静态页面的数据2.1 ResponseBody 返回页面内容2.2 RestController ResponseBody Controller3. 实现登录功能&#xff0c;返回 JSON 对象3.1 前端使⽤ ajax&#xff0c;后端返回 json 给前端3.2 前端发送 JSON 的标准格式4. 请…...

leetcode-每日一题-2335(简单,贪心)

自己打表看一下过程就可以发现&#xff0c;其实就是每次选两个大的进行--之后秒数加1即可现有一台饮水机&#xff0c;可以制备冷水、温水和热水。每秒钟&#xff0c;可以装满 2 杯 不同 类型的水或者 1 杯任意类型的水。给你一个下标从 0 开始、长度为 3 的整数数组 amount &am…...

Verilog语法之数学函数

Verilog-2005支持一些简单的数学函数&#xff0c;其参数的数据类型只能是integer和real型。 Integer型数学函数 $clog2是一个以2为底的对数函数&#xff0c;其结果向上取整&#xff0c;返回值典型的格式&#xff1a; integer result; result $clog2(n); 最典型的应用就是通过…...

【手撕面试题】JavaScript(高频知识点一)

目录 面试官&#xff1a;请你简述 var、let、const 三者之间的区别&#xff1f; 面试官&#xff1a;请你谈谈对深拷贝与浅拷贝的理解 面试官&#xff1a;输入URL的那一瞬间浏览器做了什么&#xff1f; 面试官&#xff1a;说一说cookie sessionStorage localStorage 区别&am…...

如何用PHP实现消息推送

什么是消息推送 通过服务器自动推送消息到客户端(浏览器&#xff0c;APP&#xff0c;微信)的应用技术。 2. 为什么要使用消息推送技术 通常情况下都是用户发送请求浏览器显示用户需要的信息。推送技术通过自动传送信息给用户&#xff0c;来减少用于网络上搜索的时间。它根据…...

浏阳做网站的公司价格/上海网站制作推广

每日分享时刻&#xff01;今天小编给你们带来了PS一键自动磨皮插件“Portraiture”对于在婚纱店或影楼工作的小伙伴们&#xff0c;最发愁的就是PS后期处理照片的时候苦苦磨皮了。费时不说&#xff0c;一个疏漏就可能返工&#xff0c;白熬夜加班所以小编把这款PS只能磨皮插件分享…...

推荐完善政府网站建设/重庆网站seo服务

1. 内省(Introspection)函数Introspection(内省)程序在运行时检查对象的类型或属性的能力&#xff0c;他允许对象类由程序员操纵。你将会发现introspection 相当有用当你不知道哪一个类后或者方法在设计时需要被执行.Introspection 在 PHP 提供非常有用的能力去检查类(classes)…...

东莞短视频推广方法/外贸seo软件

热点随笔&#xff1a; Visual Studio 必备神器&#xff08;stoneniqiu&#xff09; 用Go语言做产品半年的一些感觉&#xff08;AllenDang&#xff09; 踢爆IT劣书出版黑幕——由清华大学出版社之《C语言入门很简单》想到的&#xff08;1&#xff09;&#xff08;garbageMan&…...

PHP做网站的核心是什么/seo怎么优化网站排名

在 ActiveReports 中&#xff0c;可以固定报表每页显示的行数&#xff0c;当每页的数据不足固定的行数时&#xff0c;自动通过填补空白行来实现&#xff0c;当然这两种功能仅限于区域报表和页面报表中。 区域报表 在区域报表中&#xff0c;有很多方法来控制报表显示的行数 1. 在…...

哪里有网站建设服务/企业seo服务

MD5是message-digest algorithm 5&#xff08;信息-摘要算法&#xff09;的缩写&#xff0c; 被广泛用于加密和解密技术上&#xff0c;它可以说是文件的“数字指纹”。任何 一个文件&#xff0c;无论是可执行程序、图像文件、临时文件或者其他任何类型的文件&#xff0c;也不管…...

长春电商网站建设公司电话/网页模板免费html

oracle11g驱动jar包在安装oracle 11g程序会用到这些jar包&#xff0c;小编提供的oracle11g驱动包共有classes12.jar ojdbc5.jar ojdbc6.jar三个文件&#xff0c;有需要的朋友们欢迎前来下载使用。oracle11g是一款强大好用的数据库管理软件&#xff0c;一些大型企业或者网站的数…...