Android Framework通信:Handler

文章目录

  • 前言
  • 一、Handler源码分析
    • 1、创建Handler
    • 2、发送消息
    • 3、取消息
    • 4、消息处理
    • 5、线程切换的方法(Handler异步消息处理机制流程)
      • handler.sendMessage()
      • handler.post()
      • View.post()
      • Activity中的runOnUiThread()
  • 二、Handler高频面试题
    • 1、为什么要有Handler?
    • 2、为什么要有MessageQueue?
    • 3、为什么要有Looper?
    • 4、主线程的Looper和子线程Looper有什么不同?
    • 5、一个线程可以有几个Handler,几个looper?
    • 6、主线程会为什么会一直阻塞?
    • 7、ANR是什么,发生条件
    • 8、Looper的死循环为什么不会让主线程卡死(或ANR)?:
    • 9、为什么Handler会造成内存泄露?
    • 10、内存抖动如何解决?
    • 11、安卓中的Looper.loop()阻塞为什么不会有问题?

前言

线程间的通信,两个线程使用公共的变量或者公共的其他东西都可以进行通信,但是这种方式不是自主的,不能够自主切换线程执行,所以Handler的最终目的是为了线程间的切换,线程异步消息处理

Android UI操作并不是线程安全的,并且这些操作必须在UI线程执行。
如果非要在子线程中更新UI,那会出现什么情况呢?

android.view.ViewRoot$CalledFromWrongThreadException: 
Only the original thread that created a view hierarchy can touch its views.

很容易抛一个CalledFromWrongThreadException异常。
如果在子线程访问UI线程,Android提供了以下的方式:

Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
Handler

一、Handler源码分析

1、创建Handler

创建两个Handler对象,一个在主线程中创建,一个在子线程中创建,代码如下所示:

public class MainActivity extends AppCompatActivity {private Handler handler1;private Handler handler2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler1 = new Handler();new Thread(new Runnable() {@Overridepublic void run() {handler2 = new Handler();}}).start();}
}

运行程序,你会发现,在子线程中创建的Handler(Handler2)是会导致程序崩溃的:
在这里插入图片描述
我们尝试在子线程中先调用一下Looper.prepare(),运行程序成功,不再报运行时异常

那这加上Looper.prepare()运行成功是为什么呢?此时我们分析一下Handler(基于android13 API 33)源码:
在这里插入图片描述
在第224行调用了Looper.myLooper()方法获取了一个Looper对象,如果Looper对象为空,则会抛出一个运行时异常。也就是我们上述出现的异常。

什么时候Looper对象会为空呢?接着看Looper.myLooper()中的代码:

在这里插入图片描述

sThreadLocal是一个关于Looper的ThreadLocal类

在这里插入图片描述

接着查找sThreadLocal查看是在哪里给sThreadLocal设置Looper,发现是Looper.prepare()方法

在这里插入图片描述

可以看到,首先判断sThreadLocal中是否存在Looper,如果没有则创建一个新的Looper设置进去。这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。创建一个Looper对象会创建相应的MessageQueue,并且获取当前线程,故:Thread——>Looper——>MessageQueue是唯一对应的

在这里插入图片描述

所以Looper.prepare()的作用是创建一个新的Looper对象并设置到sThreadLocal中

Q:主线程中的Handler也没有调用Looper.prepare()方法,为什么就没有崩溃呢?

这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法,代码如下所示:

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// Install selective syscall interceptionAndroidOs.install();// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);// Call per-process mainline module initialization.initializeMainlineModules();Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.// It will be in the format "seq=114"long startSeq = 0;if (args != null) {for (int i = args.length - 1; i >= 0; --i) {if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {startSeq = Long.parseLong(args[i].substring(PROC_START_SEQ_IDENT.length()));}}}ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}

可以看到,在第20行调用了Looper.prepareMainLooper()方法,而这个方法又会再去调用Looper.prepare()方法,代码如下所示:

在这里插入图片描述

我们应用程序的主线程开启的时候就会创建一个Looper对象,从而不需要再手动去调用Looper.prepare()方法了。

这样基本就将Handler的创建过程完全搞明白了,总结一下就是在主线程中可以直接创建Handler对象,而在子线程中需要先调用ooper.prepare()才能创建Handler对象。

2、发送消息

@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler1 = new Handler();new Thread(new Runnable() {@Overridepublic void run() {Message message = Message.obtain();message.arg1 = 1;Bundle bundle = new Bundle();bundle.putString("data", "data");message.setData(bundle);handler1.sendMessage(message);}}).start();
}

这里Handler到底是把Message发送到哪里去了呢?为什么之后又可以在Handler的handleMessage()方法中重新得到这条Message呢?看来又需要通过阅读源码才能解除我们心中的疑惑了:

在这里插入图片描述

调用了sendMessageDelayed:

在这里插入图片描述

接着调用了sendMessageAtTime,这个方法的源码如下所示:

在这里插入图片描述

sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。

boolean enqueueMessage(Message msg, long when) {···synchronized (this) {···msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = 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();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;
}

MessageQueue并没有使用一个集合把所有的消息都保存起来,它只使用了一个mMessages对象表示当前待处理的消息。然后观察上面的代码我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间就是msg.when。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的这条消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。

3、取消息

入队操作我们就已经看明白了,那出队操作是在哪里进行的呢?这个就需要看一看Looper.loop()方法的源码了,如下所示:

在这里插入图片描述
此方法最后进入了一个死循环,然后不断地调用loopOnce()方法,这个方法作用为

Poll and deliver single message, return true if the outer loop should continue.

轮询并传递单个消息,如果外部循环应该继续,则返回true。

在这里插入图片描述

它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler,查看Message类源码:
在这里插入图片描述

PS:这里msg.target通过target将Handler存入Message,是为了解决在多个Hander的情况无法找到处理当前消息的Handler问题。实际上是一种架构设计上的妥协,我们常见的Hander内存泄漏问题也是源于此。最终导致Activity无法及时回收:
Thread–>Looper–>MessageQue–>Message.target–>mHandler–>Activity

4、消息处理

那么发送消息后,最终又是怎么调用到handleMessage的呢?接下来看一下Handler中dispatchMessage()方法的源码,如下所示:

在这里插入图片描述

在第101行进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧!

5、线程切换的方法(Handler异步消息处理机制流程)

handler.sendMessage()

我们接下来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。整个异步消息处理流程的示意图如下图所示:

在这里插入图片描述

在这里插入图片描述

handler.post()

在这里插入图片描述

还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息,我们来看下这个方法的源码:

在这里插入图片描述

在这个方法中将消息的callback字段的值指定为传入的Runnable对象。咦?这个callback字段看起来有些眼熟啊,在Handler的dispatchMessage()方法中原来有做一个检查,如果Message的callback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法。那我们快来看下handleCallback()方法中的代码吧:

在这里插入图片描述

竟然就是直接调用了一开始传入的Runnable对象的run()方法。因此在子线程中通过Handler的post()方法进行UI操作就可以这么写:

public class MainActivity extends Activity {private Handler handler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler = new Handler();new Thread(new Runnable() {@Overridepublic void run() {handler.post(new Runnable() {@Overridepublic void run() {// 在这里进行UI操作}});}}).start();}
}

虽然写法上简洁很多,但是原理是完全一样的,我们在Runnable对象的run()方法里更新UI,效果完全等同于在handleMessage()方法中更新UI。

View.post()

在这里插入图片描述

原来就是调用了Handler中的post()方法

Activity中的runOnUiThread()

在这里插入图片描述

如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法

二、Handler高频面试题

1、为什么要有Handler?

主要目的是要解决线程切换问题,handler里的Message机制解决了线程间通信

2、为什么要有MessageQueue?

MessageQueue是一个单向链表,next()调用nativePollOnce->lunx的epoll_wait()等待,实现阻塞时队列;

队列的出现解决了"处理消息"阻塞到"发送消息"的问题,由于队列是生产者消费者模式,而要使用队列需要至少两个线程与一个死循环;

  1. 一个线程负责生产消息;
  2. 一个线程消费消息;
  3. 死循环需要取出放入队列里的消息;

3、为什么要有Looper?

为了循环取出队列里的消息

4、主线程的Looper和子线程Looper有什么不同?

子线程Looper是可以退出的,主线程不行

5、一个线程可以有几个Handler,几个looper?

多个handler,每个handler都会配一个MessageQueue

Lopper和MessageQueue绑定,为防止创建多个messageQueue,Looper创建也只能被调用一次;

一个Looper,放在ThreadLocalMap中;
假如Looper对象由Handler创建,每创建一个Handler就有一个Looper,那么调用Looper.loop()时开启死循环;在外边调用Looper的地方就会阻塞

一个线程可以有多个Handler,并且每一个Handler都可以处理消息队列中的消息。每个Handler在创建时会与当前线程的消息队列相关联,因此可以通过Handler向该线程的消息队列发送消息。

需要注意的是,不同的Handler可能会被关联到相同的Looper(消息循环器)上,也可能不同的Handler使用各自独立的Looper来实现消息处理。例如,一个Activity可能会创建多个Handler对象,其中一些Handler会在主线程上执行,而另一些Handler则会在新建的子线程上执行,它们分别使用了不同的Looper来处理消息队列中的消息。

因此,可以说一个线程可以拥有多个Handler,这取决于应用程序设计的具体情况和需要。但是,由于在Android中每个线程都只有一个消息队列,因此多个Handler之间处理消息时可能会存在竞争和同步问题,需要开发者进行合理的规划和处理,以避免出现不必要的问题。

looper的生命周期是当前线程的生命周期长度,如何保证一个线程中只有一个Looper,可以通过线程ThreadLocal,ThreadLocal中会有一个ThreadLocalMap保存一个Looper,通过调用ThreadLocal的get()来判断是否能获取到Looper,如果能得到说明已经有了Looper直接返回一个异常通知已经有了Looper

Looper.prepare():保证只有一个Looper。存入Looper,存Looper时ThreadLocalMap的key为ThreadLocal,value为Looper;

在这里插入图片描述

sThreadLocal为ThreadLocal类
在这里插入图片描述

进入ThreadLocal类:获取当前线程:Thread.currentThread()

在这里插入图片描述
在这里插入图片描述

进入Thread类
ThreadLocalMap:类似于HashMap;每个Thread对象都有一个对应的ThreadLocalMap;

在这里插入图片描述

Looper.loop():循环提取消息并最终调用handlerMessage()去处理;

在这里插入图片描述
在这里插入图片描述

6、主线程会为什么会一直阻塞?

是的,如果主线程不进行looper.loop()阻塞,一下子执行完成,整个程序就直接结束了,不可能有机会去执行其他的任务了。

Android是事件为驱动的操作系统,事件过来就去handler里执行,没有事件就阻塞在那里显示界面;

sendMessage是生产者,handlerMessage是消费者;消息在队列中排队(MessageQueue),这样解决大量的消息过来的问题,不会造成主线程sendMessage阻塞,所有消息都会直接放在队列中排队等候执行;

7、ANR是什么,发生条件

ANR:Application Not Responding指的是应用程序无响应的错误,它表示应用程序在执行某个操作时长时间没有响应。在Android系统中,如果一个应用程序在主线程中执行了耗时的操作而导致主线程被阻塞,那么系统就会弹出一个对话框警告用户当前应用程序出现了ANR错误,并提示用户选择“等待”或“关闭应用程序”。

ANR通常是由于一些长时间的I/O操作、耗时的计算或者其他阻塞主线程的原因引起的。当主线程被阻塞时,应用程序的用户界面就会无响应,用户无法与应用程序进行交互,这就给用户带来了不好的体验。

ANR发生条件是:
Activity:应用在 5 秒内未响应用户的输入事件(如按键或者触摸)
BroadCastReceiver :BroadcastReceiver 未在 10 秒内完成相关的处理
Service:20 秒(均为前台)。Service 在20 秒内无法处理完成如果Handler收到以上三个相应事件在规定时间内完成了,则移除消息,不会ANR;若没完成则会超时处理,弹出ANR对话框;

为了避免ANR错误,开发人员可以采取以下措施:

  • 将耗时的操作放在子线程中执行,避免在主线程中执行。
  • 使用异步任务或线程池等机制来执行耗时的操作,从而避免阻塞主线程。
  • 在主线程中使用Handler或者AsyncTask等机制来更新UI界面。
  • 优化应用程序的代码,减少不必要的计算和I/O操作。

8、Looper的死循环为什么不会让主线程卡死(或ANR)?:

我们的UI线程(主线程)其实是ActivityThread所在的线程,而一个线程只会有一个Looper;

ActivityThread.java的main函数是一个APP进程的入口,如果不一直循环,则在main函数执行完最后一行代码后整个应用进程就会退出;

android是以事件为驱动的操作系统,当有事件来时,就去做对应的处理,没有时就显示静态界面;


App进程的入口为ActivityThread.java的main()函数,注意ActivityThread不是一个线程;

应用的UI主线程实际是调用ActivityThread.java的main()函数执行时所在的线程,而这个线程对我们不可见,但是这就是主线程:

在ActivityThread.java的main()函数中,会调用Looper.prepareMainLooper()Looper.prepareMainLooper()会创建一个Looper并放到主线程的变量threadLocals中进行绑定,threadLocals是一个ThreadLocal.ThreadLocalMap在ActivityThread.java的main()函数结尾,开启Looper.loop()进行死循环,不让main函数结束,从而让App进程不会结束;Android系统是以事件作为驱动的操作系统,当有事件来时,就去做对应处理,没有事件时,就显示当前界面,不做其他多余操作(浪费资源)在Looper.loop()的死循环中,不仅要取用户发的事件,还要取系统内核发的事件(如屏幕亮度改变等等)在调用Looper.loop()时,从MessageQueue.next()中获取事件,若没有则阻塞,有则分发MessageQueue其实不是一个队列,用epoll机制实现了阻塞。

在Looper.prepareMainLooper()时,会调用c++函数:

epoll_create()将App注册进epoll机制的红黑树中得到fd的值,
epoll_ctl()给每个App注册事件类型并监听fd值是否改变Linux中事件都会被写入文件中,如触摸屏幕事件会写入到:dev/input/event0文件中),fd有改变时唤醒epoll_wait,
epoll_wait()有事件时就分发,没事件就阻塞

在这里插入图片描述

9、为什么Handler会造成内存泄露?

内存泄漏:由于疏忽或错误造成程序未能释放已经不再使用的内存的情况

内存泄漏的原因:在Activity中,将Handler声明成非静态内部类或匿名内部类,这样Handle默认持有外部类Activity的引用。如果Activity在销毁时,Handler还有未执行完或者正在执行的Message,而Handler又持有Activity的引用,导致GC无法回收Activity,导致内存泄漏。如以下两种情形可能导致内存泄漏

1、在Activity内将Handler声明成匿名内部类

	//匿名内部类private Handler mHandler = new Handler() {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);}};
   new Handler().postDelayed(new Runnable() {@Overridepublic void run() {//大量的操作,activity要销毁时还没结束}},1000);

2、在Activity内将Handler声明成非静态内部类:

	//非静态内部类private class MyHandler extends Handler{@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);}}private MyHandler mHandler = new MyHandler();

内存泄露的本质:长生命周期持有短生命周期,造成短生命周期得不到释放就会造成内存泄露;线程的生命周期长,Activity的生命周期短,Activity运行完Handler得不到释放;

1.Activity中实例化Handler导致了,Activity中持有handler对象;2.Message和Handler的持有是由于在Lopper中进行循环遍历的时候,Message需要被执行,所以要使用handler的handleMessage()3.MessageQueue是Message的集合对象,所以造成持有关系;4.Looper又和MessageQueue进行了绑定,造成了LooperMessageQueue的持有;最终:线程----->Looper----->MessageQueue----->Message----->Handler------>Activity,一系列持有造成的内存泄露

内存泄露两大解决方案:
1、静态内部类 + 弱引用

	private static class MyHandler extends Handler {//弱引用,在垃圾回收时,activity可被回收private WeakReference<MainActivity> mWeakReference;public MyHandler(MainActivity activity) {mWeakReference = new WeakReference<>(activity);}@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);}}

2、在Activity销毁时,清空Handler中未执行或正在执行的Callback以及Message

    @Overrideprotected void onDestroy() {super.onDestroy();//清空handler管道和队列mHandler.removeCallbacksAndMessages(null);}

10、内存抖动如何解决?

内存抖动根本的解决方式是复用handler.obtainMessage();
Message的创建方式有两种:

1.new Message()

2.obtainMessage()

内存抖动是为啥?因为短时间创建大量的对象并销毁。

使用obtainMessage创建一个Message,会有复用的作用,涉及到一个回收池,回收池中存放的是Message,会有一定的数量,使用单项链表MessageQueue来存放这些Message。每个message对象指向下一个Message对象

obtainMessage()创建对象是从回收池中获取,没有的才会进行创建,回收池中获取一个Message需要将管理回收池的列表同这个取出来的Message的关联进行切断,所以需要将此获取的Message的next引用置为空。并且sPool变量的引用将会变成下一个Message,同时单向列表的size-1

public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flag sPoolSize--; return m; } return new Message();}
}

new Message()产生的对象是不会进回收池的。

从Looper的回收池中取Message;MessageQueue是一个单向链表,MessageQueue不是一个单纯的对象,而是一个链表集合,最大长度固定50个

11、安卓中的Looper.loop()阻塞为什么不会有问题?

Android是事件为驱动的操作系统,事件过来就去handler里执行(handler处理包括了创建服务,创建广播,结束服务,等等事件处理),如果没有事件过来就阻塞在那里,显示静止界面,有事件就去执行事件;

利用epoll机制可以定位到是哪个app接受这个事件,运用到红黑树,事件过来之后查找app来执行这个事件,事件带有一个标记,查找对应的app。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/159653.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React TreeSelect设置默认展开项的方法

需要实现TreeSelect组件的onTreeExpand、treeExpandedKeys方法。 代码样例如下&#xff1a; 1.TreeSelect标签部分 render() {const {codeselect} this.props;const {treeExpandedKeys} this.state ................<TreeSelectshowSearch{false}dropdownStyle{{ maxHei…

Java架构师缓存架构设计解决方案

目录 1 缓存常见的三大问题1.1 缓存雪崩1.2 缓存穿透1.3 缓存击穿2 缓存key的生成策略3 热点数据集中失效的问题4 如何提高缓存的命中率5 缓存和数据库双写不一致的问题6 如何对缓存数据进行分片想学习架构师构建流程请跳转:Java架构师系统架构设计 1 缓存常见的三大问题 缓…

分布式事务协调中间件---seata快速入门

分布式事务 Seata&#xff0c;之前叫做Fescar&#xff0c;是一个开源的分布式事务解决方案&#xff0c;它主要致力于提供高效和简单的分布式事务服务。Seata主要用于解决微服务架构下的数据一致性问题。 Seata 的基本原理是基于两阶段提交 (2PC) 以及三阶段提交 (3PC)&#xff…

私域社群团购直播活动报名小程序开发

新零售SaaS系统&#xff0c;一款超级好用的私域社交团购小程序。支持团购、直播&#xff0c;有统计、收款、发货等功能。直播配合开团转化率更高&#xff0c;一款真正的私域卖货神器。 社交化电商用户踊跃参与&#xff1a;在卖货的同时&#xff0c;体现众多消费者的参与动态更…

C语言---预处理详解

1.预定义符号 在C语言中有一些内置的预定义符号 __FILE__ __LINE__ __DATE__ __TIME__ __STDC__//进行编译的源文件 //文件当前的行号 //文件被编译的日期 //文件被编译的时间 //如果编译器遵循ANSI C&#xff0c;其值为1&#xff0c;否则未定义 编译器在__STDC__报错,说明,v…

回归预测 | MATLAB实现CNN-LSSVM基于卷积神经网络-最小二乘支持向量机的数据回归预测(多指标,多图)

回归预测 | MATLAB实现CNN-LSSVM基于卷积神经网络-最小二乘支持向量机的数据回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现CNN-LSSVM基于卷积神经网络-最小二乘支持向量机的数据回归预测&#xff08;多指标&#xff0c;多图&#xff09;…

小程序框架语法详解以及页面生命周期的代码预演

目录 一、框架简介 二、视图层 2.1 简介 2.2 WXML语法演示 2.2.1 数据绑定 2.2.2 列表渲染 2.2.3 条件渲染 2.2.4 模板 2.3 事件系统 2.4 页面一级菜单展示及切换 2.5 a页面跳b页面界面内部按钮演示 2.6 a页面跳c页面&#xff08;不在一级菜单内的页面&#xff09;…

Webpack和JShaman相比有什么不同?

Webpack和JShaman相比有什么不同&#xff1f; Webpack的功能是打包&#xff0c;可以将多个JS文件打包成一个JS文件。 JShaman专门用于对JS代码混淆加密&#xff0c;目的是让JavaScript代码变的不可读、混淆功能逻辑、加密代码中的隐秘数据或字符&#xff0c;是用于代码保护的…

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆 前言一. 大小根堆二. 数据流的中位数1.1 初始化1.2 插入操作1.3 完整代码 三. 滑动窗口中位数3.1 在第一题的基础上改造3.2 栈的remove操作 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 大小根堆 先来说下大小根堆是什…

NPM 常用命令(十二)

目录 1、npm unpublish 1.1 使用语法 1.2 描述 2、npm unstar 2.1 使用语法 3、npm update 3.1 使用语法 3.2 描述 3.3 示例 插入符号依赖 波浪号依赖 低于 1.0.0 的插入符号依赖 子依赖 更新全局安装的包 4、npm version 4.1 使用语法 5、npm view 5.1 使用语…

LLMs的终局是通用人工智能AGI总结 生成式AI和大语言模型 Generative AI LLMs

终于学完了 生成式AI和大语言模型 Generative AI & LLMs. LLMs 解决了如下问题&#xff1a; 对NLP的不能够理解长句子&#xff0c;解决方案 自注意力机制Transformers architecture Attention is all you need大模型算力不够&#xff0c;解决方案 LLMs 缩放法则和计算最…

电商爬虫API快速入门指南

​电子商务爬虫API​是一个公共数据爬虫API&#xff0c;旨在通过大多数电子商务网站收集大量实时本地化数据并搜索信息。这个数据收集工具作为一个值得信赖的解决方案&#xff0c;实现通过最复杂的电子商务网站收集公共信息。电子商务爬虫API适用于商业用例&#xff0c;诸如价格…

数据结构 - 2(顺序表10000字详解)

一&#xff1a;List 1.1 什么是List 在集合框架中&#xff0c;List是一个接口&#xff0c;继承自Collection。 Collection也是一个接口&#xff0c;该接口中规范了后序容器中常用的一些方法&#xff0c;具体如下所示&#xff1a; Iterable也是一个接口&#xff0c;Iterabl…

加入鲲鹏HPC训练营,一起引领高性能计算新潮流

随着科学技术的迅猛发展&#xff0c;高性能计算&#xff08;HPC&#xff09;已经成为各行各业的核心竞争力之一。在这个数字化时代&#xff0c;高性能计算对于解决大数据分析、人工智能、模拟计算等领域的复杂问题至关重要。 所谓HPC&#xff0c;就是一个计算机集群系统&#x…

安全典型配置(三)使用ACL禁止特定用户上网案例

【微|信|公|众|号&#xff1a;厦门微思网络】 安全典型配置&#xff08;一&#xff09;使用ACL限制FTP访问权限案例_厦门微思网络的博客-CSDN博客本例中配置的本地用户登录密码方式为irreversible-cipher&#xff0c;表示对用户密码采用不可逆算法进行加密&#xff0c;非法用…

android studio检测不到真机

我的情况是&#xff1a; 以前能检测到&#xff0c;有一天我使用无线调试&#xff0c;发现调试有问题&#xff0c;想改为USB调试&#xff0c;但是半天没反应&#xff0c;我就点了手机上的撤销USB调试授权&#xff0c;然后就G了。 解决办法&#xff1a; 我这个情况比较简单&…

女性用品经营商城小程序的作用是什么

女性悦己消费增强&#xff0c;围绕女性产生的商品&#xff0c;品牌多且样式足&#xff0c;消费者可以随时购买到&#xff0c;但随着线上互联网深入人们生活&#xff0c;电商近些年发展迅速&#xff0c;传统女性用品线下经销商或品牌在实际经营中面临着痛点。 线上卖货是各商家…

【C++】:string用法详解

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux的基础知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数…

TCP/IP(十)TCP的连接管理(七)CLOSE_WAIT和TCP保活机制

一 CLOSE_WAIT探究 CLOSE_WAIT 状态出现在被动关闭方,当收到对端FIN以后回复ACK,但是自身没有发送FIN包之前 ① 服务器出现大量 CLOSE_WAIT 状态的原因有哪些? 1、通常来讲,CLOSE_WAIT状态的持续时间应该很短,正如SYN_RCVD状态2、但是在一些特殊情况下,就会出现大量连接长…

word误删除的文件怎么恢复?恢复办法分享

在日常工作和学习中&#xff0c;我们常常会使用到Word来撰写文章、毕业论文、方案等。然而&#xff0c;我们可能会遇到Word误删文件的情况&#xff0c;令我们陷入恐慌&#xff0c;特别是这个文件很重要时。幸运的是&#xff0c;有办法找回。下面一起来看下word误删除的文件怎么…