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。这中使用单链表复用就是享元模式。

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

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

相关文章

竞赛 题目:基于深度学习的中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分&#xff1a;4.2 损失函数&#xff1a;4.3 搭建seq2seq框架&#xff1a;4.4 测试部分&#xff1a;4.5 评价NLP测试效果&#xff1a;4.6 梯度截断…

说一说HTTP1.0、1.1、2.0版本区别和优化

说一说HTTP1.0、1.1、2.0版本区别和优化 HTTP&#xff08;Hypertext Transfer Protocol&#xff09;是一种用于传输超文本的应用层协议。 在不同的版本中&#xff0c;HTTP经历了一系列的演进和改进&#xff0c;主要包括HTTP 1.0、HTTP 1.1和HTTP 2.0。 下面详细解释它们之间…

亚马逊第二个大语言模型 Olympus 即将上线

据外媒爆料&#xff0c;亚马逊正在训练他的第二个大语言模型——Olympus&#xff0c;很有可能在今年12月份上线。亚马逊计划将Olympus接入在线零售商店、Echo等设备上的Alexa语音助手&#xff0c;并为AWS平台提供新的功能。据说这个大语言模型规模达到2万亿&#xff08;2000B&a…

C++ 删除无头链上所有指定值为x的节点。

C 删除无头链上所有指定值为x的节点。 #include<stdio.h> #include<ctype.h> #include<stdlib.h> typedef struct app {int data;struct app* next; }APP; int main() {int n;int node;int x;while (scanf("%d", &n) ! EOF){APP* head NULL, …

读《Segment Anything in Defect Detection》

摘要 (好像只是说把SAM应用到了红外缺陷分割领域) 引言 无损检测得到红外图像&#xff0c;根据热能观察异常 贡献&#xff1a; •从两个光学脉冲热成像系统构建广泛的缺陷热数据库&#xff0c;包括各种材料并释放它们。 • 开发DefectSAM&#xff0c;这是第一个用于缺陷检测…

maven升级版本后报错:Blocked mirror for repositories

出现问题的场景&#xff1a; 当 Maven 升级到3.8.1 之后&#xff0c;执行maven就会出现如下报错信息&#xff1a; maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories 原因&#xff1a; maven在3.8.1 的默认配置文件中增加了一组标签&#xff0…

MAC地址注册的网络安全影响和措施分析

MAC地址注册对网络安全具有重要影响&#xff0c;同时也需要采取相应的措施来应对潜在的安全风险。以下是有关MAC地址注册的网络安全影响和应对措施的分析&#xff1a; 影响&#xff1a; 1. 身份验证&#xff1a;MAC地址注册可用于设备的身份验证&#xff0c;但MAC地址本身并不…

033-从零搭建微服务-日志插件(一)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff08;后端&#xff09;&#xff1a;mingyue: &#x1f389; 基于 Spring Boot、Spring Cloud & Alibaba 的分布式微服务架构基础服务中心 源…

【C/PTA】数组进阶练习(一)

本文结合PTA专项练习带领读者掌握数组&#xff0c;刷题为主注释为辅&#xff0c;在代码中理解思路&#xff0c;其它不做过多叙述。 目录 7-1 矩阵运算7-2 方阵循环右移7-3 螺旋方阵7-4 数组-杨辉三角7-5 数组-对角线求和7-6 数组-矩阵最小值 7-1 矩阵运算 给定一个nn的方阵&am…

大一,小小练习题--含答案

测试题 1.(1). 对于int类型的变量&#xff0c;Java编译器和大多数C编译器都是分配4个字节的内存&#xff0c;4个字节有32个二进制位即总共可以拥有多少种状态&#xff0c;其中一半的状态用来存储负数&#xff0c;一半的状态用来存储0和正数。因此&#xff0c;int类型的变量可以…

不标年份的葡萄酒质量好吗?

我们在葡萄酒标上经常看到生产年份&#xff0c;也就是指全部葡萄采摘的年份。旧世界葡萄酒产国认为葡萄酒年份对他们的影响较大&#xff0c;而新世界葡萄酒&#xff0c;年份的意义就稍微小些。甚至有一部分葡萄酒酒标上没有年份。在酒标上没有标注年份的葡萄酒&#xff0c;被称…

WSL2安装ubuntu及修改安装位置,设置Ubuntu开机启动链接ssh服务

1. WSL2安装ubuntu及修改安装位置 2. 设置Ubuntu开机启动链接ssh服务

学习无人机代码框架【第一天】---VMware 安装Ubuntu16.04时显示不全的解决方法

ros环境配置篇 环境配置在vmware上安装ubantu16.04操作系统安装完成后显示界面太小解决办法其他遇到的一些ubantu问题最后一步是在ubantu16上安装ros-kinetic其他很重要的一个工具是安装vmware-tool&#xff0c;可以支持把外部的文件或文字传入到虚拟机中管理不同的终端的软件代…

[C/C++] 数据结构 LeetCode:用队列实现栈

题目描述: 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#xff09;。 实现 MyStack 类&#xff1a; void push(int x) 将元素 x 压入栈顶。int pop() 移除并返回栈顶元…

基于Springboot+Vue家电维修预约系统

需求&#xff1a; 登录后用户选择所在地区 1.日常事务工作人员预约&#xff08;进行分类筛选 如清洁、烹饪等&#xff0c;至少6个&#xff09;每天晚上22:00前预约 第二天起可上门 需要有时段的选择。 2.家电维修预约&#xff08;分类筛选 如&#xff1a;冰箱、空调、网络、电饭…

为什么 Intent 不能传递大数据

Intent 传递不同大小数据时的问题&#xff1a; 传 512k 以下的数据是可以正常传递的&#xff1b;传 512k&#xff5e;1024k 的数据会出错&#xff0c;闪退&#xff1b;传 1024k 以上的数据会报错&#xff1a;TransactionTooLargeException&#xff1b;考虑到 Intent 还包括要启…

kubernetes集群编排——etcd

备份 从镜像中拷贝etcdctl二进制命令 [rootk8s1 ~]# docker run -it --rm reg.westos.org/k8s/etcd:3.5.6-0 sh 输入ctrlpq快捷键&#xff0c;把容器打入后台 获取容器id [rootk8s1 ~]# docker ps 从容器拷贝命令到本机 docker container cp c7e28b381f07:/usr/local/bin/etcdc…

使用人工智能自动测试 Flutter 应用程序

移动应用程序开发的增长速度比以往任何时候都快。几乎每个企业都需要移动应用程序来保持市场竞争力。由于像 React Native 这样的跨平台移动应用程序开发框架允许公司使用单一源代码和单一编程语言构建 iOS 和 Android 应用程序&#xff0c; Flutter是 Google 支持的另一个热门…

【前端知识】Node——使用fs模块对文件、文件夹的操作

一、fs的三种读取文件内容的方式 const fs require(fs);// 1.同步读取 const res1 fs.readFileSync(../test.txt, {encoding: utf-8 }); console.log(res1);// 2.异步读取&#xff1a;回调函数 fs.readFile(../test.txt, {encoding: utf-8 }, (err, data) > {if(err){con…

二十三种设计模式全面解析-深入解析模板方法模式的奇妙世界

在软件设计的奇妙宇宙中&#xff0c;有一种设计模式如一颗流星般划过&#xff0c;留下绚丽的光芒&#xff0c;它就是——模板方法模式&#xff08;Template Method Pattern&#xff09;。这个模式不仅令代码更加灵活&#xff0c;而且蕴含了一种设计哲学&#xff0c;本文将深入研…