Android消息机制(Handler、Looper、MessageQueue)
一、ThreadLocal
1、什么是ThreadLocal
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。
2、ThreadLocal 使用
1)、创建ThreadLocal
ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
通过泛型指定ThreadLocal中存储的数据类型
2)、ThreadLocal保存参数
threadLocal.set(true);
threadLocal中保存参数true
3)、获取参数
threadLocal.get()
下面是一个使用ThreadLocal的例子:
final ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
threadLocal.set(true);
Log.d(TAG, "main thread:" + threadLocal.get());new Thread() {@Overridepublic void run() {super.run();// 这里打印的值不是false是null,因为ThreadLocal保存的值在不同线程是两个不同的对象Log.d(TAG, "child thread:" + threadLocal.get());}
}.start();
打印出日志结果如下:
D/ThreadLoalActivity: main thread:true
D/ThreadLoalActivity: child thread:null
在主线程中把ThreadLocal设置为true,但是在子线程获取的值是null。因为ThreadLocal的作用域是线程,在不同线程中相当于两个对象,所以子线程中没有赋值就是null。
3、ThreadLocal的工作原理
1)set方法
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
首先获取当前线程,通过getMap获取当前线程中保存的ThreadLocalMap ,getMap源码如下:
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}
实际上是获取Thread中的threadLocals对象,即每个线程中有一个ThreadLocalMap (我们可以简单理解为HashMap),ThreadLocalMap 用于保存数据。因为可能会定义多个ThreadLocal(注意在Android中Looper中的ThreadLocal是一个静态变量,因此Looper中只有唯一一个ThreadLocal),所以用Map保存。 Thread 中的threadLocals定义如下:
class Thread implements Runnable {ThreadLocal.ThreadLocalMap threadLocals = null;}
ThreadLocalMap是ThreadLocal的内部类,ThreadLocalMap源码如下:
static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}private Entry[] table;
}
在 ThreadLocalMap 内部有一个数组,private Entry[] table,ThrealLocal 的值就存在这个 table 数组中。Entry的key保存ThreadLocal对象,value保存ThrealLocal 的值。
2)、get方法
public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();
}
取出当前线程的 ThreadLocalMap 对象,然后取出它的 table 数组,然后获取保存在ThreadLocal中的值。
二、消息队列MessageQueue的工作原理
MessageQueue主要包含两个操作:插入和读取。读取本身伴随着删除操作,对应着两个方法分别是enqueueMessage()和next()。enqueueMessage是插入一条消息到消息队列中,next是取出一条信息并将其从队列移除。看他的方法源码可以看到enqueueMessage主要是进行单链表的插入操作。next是一个无线循环,如果么有消息,next会一直阻塞在这里,如果有消息到来,next会返回这个消息并将其从队列移除。
enqueueMessage源码如下:
boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}synchronized (this) {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;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;
}
next源码如下:
Message next() {...int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}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;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.nextPollTimeoutMillis = -1;}// Process the quit message now that all pending messages have been handled.if (mQuitting) {dispose();return null;}}...
}
第27行nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);计算距离下一个消息执行的时间。第10行nativePollOnce(ptr, nextPollTimeoutMillis);是本地方法,C语言实现。会阻塞当前线程,直到时间达到nextPollTimeoutMillis 。
上面使用synchronized 对插入和取出操作进行加锁,因为Handler sendMsg时可能在不同的线程中。
三、Looper的工作原理
Looper会不停的从MessageQueue中查看是否有新消息,有的话立刻处理,没有就会一直阻塞。Looper在Android消息机制中扮演消息循环的角色。
1、怎么创建一个Looper
1)、Looper.prepare()
Looper.prepare()源码如下:
public static void prepare() {prepare(true);
}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));
}
在prepare中新建了一个Looper对象,并且将这个对象保存在ThreadLocal中。
2)、Looper.prepareMainLooper()
这里是创建主线程的Looper,注意这个方法是系统调用的,不需要我们调用,系统默认就初始化了主线程的Looper(详见第下面五节)。所以我们在主线程可以直接new Handler()使用,如果在子线程还必须调用Looper.prepare()和Looper.loop()(详见下面第六节)。
Looper.prepareMainLooper()源码如下;
public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}
}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));
}
先调用prepare(),在prepare中新建了一个Looper对象,并且将这个对象保存在ThreadLocal中。注意这里prepare传入的参数是false,表示消息队列不允许退出。然后将Looper保存在变量sMainLooper中。
2、开启循环
新建Looper后并不能正常工作,还需要调用Looper.loop()启动消息循环。Loop源码如下:
public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;// 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();for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// 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);}final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;final long traceTag = me.mTraceTag;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();final long end;try {msg.target.dispatchMessage(msg);end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (slowDispatchThresholdMs > 0) {final long time = end - start;if (time > slowDispatchThresholdMs) {Slog.w(TAG, "Dispatch took " + time + "ms on "+ Thread.currentThread().getName() + ", h=" +msg.target + " cb=" + msg.callback + " msg=" + msg.what);}}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();}
}
调用Message msg = queue.next()检测消息队列中是否有消息,有消息就继续向下执行,没有就阻塞。有消息时就会执行到msg.target.dispatchMessage(msg),target就是Handler。相当于调用的是Handler.dispatchMessage(msg)。Handler的sendMessage方法最终会调用enqueueMessage方法,enqueueMessage方法源码如下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}
msg.target = this;就是把当前Handler的实例保存到 msg.target中。
什么时候会退出这个loop死循环呢?
Looper.loop()是一个死循环,唯一跳出的方式就是MessageQueue的next方法返回null。当Looper的quit方法被调用时,Looper会调用MessageQueue的quit或者quitSafely方法通知消息队列退出,当消息队列被标记为退出状态时,它的next方法返回null。也就是说Looper必须退出,否则loop方法就会无限循环下去。
四、Handler的工作原理
Handler的内部实现其实就是Looper和MessageQueue。创建Handler的构造方法最终都会执行如下方法,源码如下:
public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;
}
mLooper = Looper.myLooper();获取了当前线程的Looper, mQueue = mLooper.mQueue;获取了Looper对应的消息队列。注意主线程系统自动初始化Looper,子线程需要自己初始化Looper。
Handler发送消息的方法最终都会执行如下方法:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);
}private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}
实际就是向消息队列中添加消息,然后在Looper中会去处理。
Looper怎么处理,如何回调到Handler中的handleMessage见“3.2 开启循环”的分析。
五、主线程的消息循环
当我们启动一个程序(即点击Launch中的按钮)时,zygote会fork 一个进程,会给每个应用分配独立的JVM。Java程序都是从main函数开始启动,所以Android应用最先执行的也是main函数。而这个main函数在ActivityTread中。
Android的主线程就是ActivityTread,入口方法是main,在main方法中会通过Looper.prepareMainLooper()来创建主线程的Looper和MessageQueue,然后通过Looper.loop()开启消息循环,源码如下:
public static void main(String[] args) {...Looper.prepareMainLooper();ActivityThread thread = new ActivityThread(); //在attach方法中会完成Application对象的初始化,然后调用Application的onCreate()方法thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}...Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}
主线程消息循环开始以后还需要一个Handler来和消息队列进行交互,这个Handler就是ActivityThread.H,H定义的消息类型如下:
private class H extends Handler {public static final int LAUNCH_ACTIVITY = 100;public static final int PAUSE_ACTIVITY = 101;public static final int PAUSE_ACTIVITY_FINISHING= 102;public static final int STOP_ACTIVITY_SHOW = 103;public static final int STOP_ACTIVITY_HIDE = 104;public static final int SHOW_WINDOW = 105;public static final int HIDE_WINDOW = 106;public static final int RESUME_ACTIVITY = 107;public static final int SEND_RESULT = 108;public static final int DESTROY_ACTIVITY = 109;public static final int BIND_APPLICATION = 110;public static final int EXIT_APPLICATION = 111;public static final int NEW_INTENT = 112;public static final int RECEIVER = 113;public static final int CREATE_SERVICE = 114;public static final int SERVICE_ARGS = 115;public static final int STOP_SERVICE = 116;public static final int CONFIGURATION_CHANGED = 118;public static final int CLEAN_UP_CONTEXT = 119;public static final int GC_WHEN_IDLE = 120;public static final int BIND_SERVICE = 121;public static final int UNBIND_SERVICE = 122;public static final int DUMP_SERVICE = 123;public static final int LOW_MEMORY = 124;public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;public static final int RELAUNCH_ACTIVITY = 126;public static final int PROFILER_CONTROL = 127;public static final int CREATE_BACKUP_AGENT = 128;public static final int DESTROY_BACKUP_AGENT = 129;public static final int SUICIDE = 130;public static final int REMOVE_PROVIDER = 131;public static final int ENABLE_JIT = 132;public static final int DISPATCH_PACKAGE_BROADCAST = 133;public static final int SCHEDULE_CRASH = 134;public static final int DUMP_HEAP = 135;public static final int DUMP_ACTIVITY = 136;public static final int SLEEPING = 137;public static final int SET_CORE_SETTINGS = 138;public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;public static final int TRIM_MEMORY = 140;public static final int DUMP_PROVIDER = 141;public static final int UNSTABLE_PROVIDER_DIED = 142;public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143;public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;public static final int INSTALL_PROVIDER = 145;public static final int ON_NEW_ACTIVITY_OPTIONS = 146;public static final int CANCEL_VISIBLE_BEHIND = 147;public static final int BACKGROUND_VISIBLE_BEHIND_CHANGED = 148;public static final int ENTER_ANIMATION_COMPLETE = 149;
}
H中处理消息的handMeassge
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {case LAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");final ActivityClientRecord r = (ActivityClientRecord) msg.obj;r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);handleLaunchActivity(r, null);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;case RELAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");ActivityClientRecord r = (ActivityClientRecord)msg.obj;handleRelaunchActivity(r);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;case PAUSE_ACTIVITY:Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,(msg.arg1&2) != 0);maybeSnapshot();Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;case PAUSE_ACTIVITY_FINISHING:Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) != 0, msg.arg2,(msg.arg1&1) != 0);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;case STOP_ACTIVITY_SHOW:Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");handleStopActivity((IBinder)msg.obj, true, msg.arg2);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;case STOP_ACTIVITY_HIDE:Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");handleStopActivity((IBinder)msg.obj, false, msg.arg2);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;...}
可看到有启动Activity,暂停Activity,停止Activity。
ActivityThread的内部类H继承于Handler,通过handler消息机制,简单说Handler机制用于同一个进程的线程间通信。Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。
六、子线程如何创建消息循环
主线程消息循环的创建是系统自动完成的,子线程的消息循环的创建需要我们自己手动完成 。子线程创建消息循环需要3步,具体步骤见下面代码注释。
代码如下:
new Thread() {@Overridepublic void run() {// 1、为当前线程新建LooperLooper.prepare();// 2、新建Handler// handler = new Handler(new Handler.Callback() { 这个等价于下面这行代码handler = new Handler(Looper.myLooper(), new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {switch (msg.what) {case MSG_ONE:Log.d(TAG, "handleMessage>>MSG_ONE");break;case MSG_TWO:Log.d(TAG, "handleMessage>>MSG_TWO");break;}return false;}});// 3、开启循环Looper.loop();// 4、使用完后调用quitSafely结束Looper的死循环,这样子线程才能结束,子线程才能回收Looper.myLooper().quitSafely();}
}.start();
注意上面Handler的工作线程是子线程。
总结
1、ThreadLocal会根据当前线程取出该线程对应的ThreadLocalMap(类似HashMap),ThreadLocal.set调用ThreadLocalMap.set(当前的ThreadLocal, value),即ThreadLocalMap使用ThreadLocal作为key。ThreadLocalMap中是把key、value封装成Entry放在一个数组中。
2、Android的消息机制主要就是Handler、Looper、MessageQueue三个类。
3、MessageQueue使用next读取数据。next是一个无线循环,如果没有消息,next会一直阻塞在这里,如果有消息到来,next会返回这个消息并将其从队列移除。
4、我们在new Handler()前必须先调用Looper.prepare(),否则会因为当前线程没有Looper报异常。线程最后还要调用Looper.loop()启动循环。在主线程中我们不用手动调用Looper.prepare()和loop(),因为应用启动时系统帮我们调用了Looper.prepare()和loop()。
5、Handler中包含Looper和MessageQueue,Handler发一个消息实际就是向MessageQueue中添加一个消息,Looper.loop()中循环调用MessageQueue.next()检测是否有新消息,有就处理,没有就一直检测。
6、Activity的生命周期都是在主线程的Looper.loop中收到不同Message时则采用相应措施:在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。
7、创建Message不使用new,使用Message.obtain()。Message中会使用单链表维护一个池,保存new出的Message对象。这样的好处时避免频繁创建对象,造成内存抖动。因为Message会系统很多地方都会使用,触摸屏幕然后响应都会使用Message,即系统每时每刻都在新建Message的对象。如果使用new,用完会释放,这样会造成很多内存碎片。其他对象分配时可能不够分配空间就会造成OOM。这中使用单链表复用就是享元模式。
相关文章:

Android消息机制(Handler、Looper、MessageQueue)
一、ThreadLocal 1、什么是ThreadLocal ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。 一般来说…...

Pikachu漏洞练习平台之XXE(XML外部实体注入)
目录 什么是 XML? 什么是DTD? 什么是XEE? 常见payload 什么是 XML? XML 指可扩展标记语言(EXtensible Markup Language); XML 不会做任何事情,而是用来结构化、存储以及传输信息…...

ubuntu中/etc/rc.local和/etc/init.d/rc.local的区别是什么
在早期版本的Ubuntu中,通常会使用 /etc/rc.local 或 /etc/init.d/rc.local 文件执行在系统启动时需要运行的自定义脚本或命令。然而,随着Ubuntu的版本升级,这两者的使用方式有了一些变化。 /etc/rc.local: 功能: /etc/…...

vue项目中 commonJS转es6
背景:项目中需要使用一个插件,但是插件底层是commonJS语法 项目结构:webpackvue2.x 转换准备工作 安装插件: 以下插件如已安装请忽略 npm install babel/preset-env vue/cli-plugin-babel/preset babel/plugin-transform-runt…...

【C++】AVL树(动图详解)
文章目录 一、前言二、AVL树的概念(引入bf)三、AVL节点树的定义四、AVL树的基本框架五、AVL树的旋转5.1左单旋(新节点插入较高右子树的右侧---右右:左单旋)例一(h0)例二(h1ÿ…...

「Verilog学习笔记」用3-8译码器实现全减器
专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点,刷题网站用的是牛客网 分析 首先列出3-8译码器和全减器的真值表 全减器真值表如下 3-8译码器真值表如下 timescale 1ns/1nsmodule decoder_38(input E ,input A0 …...

rocketmq: MQClientException: No route info of this topic
可能是broker没连接到(或配置)name server有关...

【Vue全家桶 合集 关注收藏】
【Vue全家桶】全面了解学习并实践总结Vue必备知识点 写在前面 🤗 这里是SuperYi Vue全家桶合集站! 🌻 人海茫茫,感谢这一秒你看到这里。希望我的文章对你的有所帮助! 🌟 愿你在未来的日子,保持…...

react+video.js h5自定义视频暂停图标
目录 参考网址 效果图,暂停时显示暂停图标,播放时隐藏暂停图标 代码说明,代码传入url后,可直接复制使用 VideoPausedIcon.ts 组件 VideoCom.tsx Video.module.less 参考网址 在Video.js播放器中定制自己的组件 - acgtofe 效…...

CentOS和Ubuntu中防火墙相关命令
CentOS和Ubuntu中防火墙相关命令 1、CentOS7中防火墙相关命令2、Ubuntu中防火墙相关命令 1、CentOS7中防火墙相关命令 在CentOS 7中,与防火墙相关的命令主要包括firewalld命令。以下是一些常用的firewalld命令: 查看firewalld服务状态: syst…...

学习笔记5——对象、直接内存、执行引擎,string
学习笔记系列开头惯例发布一些寻亲消息 链接:https://baobeihuijia.com/bbhj/contents/3/192486.html 创建对象的步骤 对象对应的类是否被加载,链接(链接到真实的内存地址),初始化(类初始化)…...

【node】如何在打包前进行请求等操作npm run build
举例,在运行 npm run build 之前将路由表传递给后端,可以采取以下步骤: 创建一个脚本文件,例如 generateRoutes.js,用于生成路由表文件。 在该脚本文件中,导入路由配置文件和后端要接收路由表的接口。 使…...

鸿蒙4.0真机调试踩坑
传言鸿蒙next版本将不再兼容Android,所以领导安排做下鸿蒙开发的调研工作。 鸿蒙开发指南其实已经非常的友好了。但是鸿蒙开发本身还是有些坑要踩,这篇文章主要讲了鸿蒙真机调试问题。 目前手上的真机为华为 nova6,处理器为麒麟990.鸿蒙系统…...

中文撰稿好用软件推荐TexPage(似于Overleaf)
由于本人用惯了overleaf所以找到了一个与他功相似的也同样是利用tex写文章。唯一的区别可能也就是overleaf只支持英文,而TexPage中英文都支持。关键是不花钱,好用好用好用,用起来! 平台网址:https://www.texpage.com/…...

AD教程 (十七)3D模型的创建和导入
AD教程 (十七)3D模型的创建和导入 对于设计者来讲,现在3DPCB比较流行,3DPCB,除了美观之外,做3D的最终的一个目的,是为了去核对结构,就是我们去做了这么一个PCB之后,如果说…...

企业微信获取第三方应用凭证
上一篇介绍了如何配置通用开发参数及通过url回调验证, 本篇将通过服务商后台配置关联小程序应用配置和获取第三方凭证及如何配置企业可信IP。 当然上篇配置的回调设置也不会白费,在下方的指令和数据回调会用到。 第三方应用开发流程 官方企业微信第三方…...

增删改查mysql
查询 -- 查询表结果-- 查看 当前数据库下的表show tables;-- 查看指定的表desc tb_emp; -- td_emp 是表名-- 查看 数据库的见表语句show create table tb_emp; 修改 -- 修改表结构 -- 修改 为表 tb_emp 添加字段 qq varchar(11) alter table tb_emp add qq varchar(11) …...

【vue】下载导出excel
下载导出excel 首先使用的tdesign框架,要导出后端返回的数据流excel 遇见的问题 下载的文件,里边的内容是undefined 观察报错 一看就知道并不是后端的报错,后端不可能是undefined 在强烈的好奇心驱动下,看了下接口࿰…...

c#正则表达式
using System.Text.RegularExpressions; namespace demo1 {/// <summary>/// 正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a~z的字母)和特殊字符(称为“…...

C#密封类和密封成员
密封类和密封成员需要使用 sealed 修饰符,他可以防止当前类被继承或者防止派生类在继承的过程中重写某个方法。 与abstract抽象修饰符类似,sealed 修饰符不仅可用来修饰class,同样也可以修饰类成员。如果sealed关键词用在class上,…...

三、Eureka注册中心
目录 一、作用及调用方式 二、搭建eureka注册中心 三、注册user-service和order-service 四、新增实例 五、服务拉取 六、总结 一、作用及调用方式 在服务提供者启动时,它会向eureka注册中心提供自己的信息,并每30秒进行一次刷新eureka注册中心保存…...

java线程池动态调节功能实现
java线程池动态调节功能实现 功能背景ThreadPoolExecutor配置自定义可变容LinkedBlockingQueuecontroller接口测试结果 功能背景 由于线程池的参数配置是一个比较难准确配置好, 如果需要进行配置修改, 就会对配置进行修改,再进行部署,影响效率, 或者应用场景的变化,导致固定的…...

KT148A语音芯片使用串口uart本控制的完整说明_包含硬件和指令举例
一、功能简介 KT148A肯定是支持串口的,有客户反馈使用一线还是不方便,比如一些大型的系统不适合有延时的操作,所以更加倾向于使用uart控制,这里我们也给出解决方案 延伸出来另外一个版本,KT158A 注意次版本芯片还是…...

kubectl 本地远程链接k8s多个集群,远程管控多集群,查看日志 部署服务(windows版)
文章目录 一、前言二、windows上安装kubectl和mobaxterm2.1 准备安装包2.2 安装kubectl2.3 链接k8s集群2.4 查看某一个pod的容器日志2.5 切换context 上下文配置,实现在多个k8s集群间动态切换 一、前言 现如今是一个万物皆上云 的时代,各种云层出不穷&am…...

wireshark打开tcpdump抓的包 vwr: Invalid data length runs past the end of the record
tcpdump -i any -n -s0 > t.pcap 使用此命令在Debian系统上抓包,下载到PC,用wireshark打开时报错: 后来发现写入文件时使用 -w 是没问题的,原因还不清楚。 tcpdump -i any -n -s0 -w t.pcap...

Python爬虫教程:从入门到实战
更多Python学习内容:ipengtao.com 大家好,我是涛哥,今天为大家分享 Python爬虫教程:从入门到实战,文章3800字,阅读大约15分钟,大家enjoy~~ 网络上的信息浩如烟海,而爬虫(…...

C++实现高频设计模式
面试能说出这几种常用的设计模式即可 1.策略模式 1.1 业务场景 大数据系统把文件推送过来,根据不同类型采取不同的解析方式。多数的小伙伴就会写出以下的代码: if(type"A"){//按照A格式解析 }else if(type"B"){//按照B格式解析 …...

opencv(2): 视频采集和录制
视频采集 相关API VideoCapture()cap.read(): 返回两个值,第一个参数,如果读到frame,返回 True. 第二个参数为相应的图像帧。cap.release() VideoCapture cv2.VideoCapture(0) 0 表示自动检测,如果在笔记本上运行&…...

SpringBoot+EasyExcel设置excel样式
方式一:使用注解方式设置样式 模板可通过HeadFontStyle、HeadStyle、ContentFontStyle、ContentStyle、HeadRowHeight ContentRowHeight等注解设置excel单元格样式; //字体样式及字体大小 HeadFontStyle(fontName "宋体",fontHeightInPoints…...

自定义View之Measure(二)
measure 用来测量 View 的宽和高,它的流程分为 View 的 measure 流程和 ViewGroup 的measure流程,只不过ViewGroup的measure流程除了要完成自己的测量,还要遍历地调用子元素的measure()方法。 上一回说到performMeasur…...