Android Hanlder 揭密之路- 深入理解异步消息传递机制Looper、Handler、Message三者关系


在Android开发中,Handler作为实现线程间通信的桥梁,扮演着至关重要的角色。无论是在主线程执行UI操作,还是在子线程进行耗时任务,Handler都可以高效地将异步消息分派到对应的线程中执行。


本文将全方位解析Handler的工作原理及实现细节,从源码角度介绍Looper , Handler , Message的关系,让你记忆深刻。


一、Handler原理浅析

Handler实际是Android低层面向线程的消息循环机制MessageQueue的一层包装。


1、两个关键组成部分:

  • MessageQueue消息队列 - 用于存放所有通过Handler发送的消息
  • Looper消息循环器 - 负责不断从MessageQueue中取出消息,并按序执行

每个线程都可以通过Looper.prepare()方法创建自己的消息循环,并在循环体内通过Looper.loop()不断获取并执行消息。


Android 中的 Handler 机制是用于在不同线程之间进行通信和消息传递的重要机制。


2、工作流程如下

(1)、创建 Handler 实例,并重写 handleMessage() 方法,用于处理接收到的消息。

(2)、发送消息: 通过 HandlersendMessage() 等方法发送消息到消息队列。

(3)、消息循环: 消息队列会不断地从队列中取出消息,并分发给对应的 Handler 处理。


我们可以通过一个简单的示例来理解它的基本工作流程:

public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private Handler handler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 创建 Handler 对象handler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {// 处理收到的消息switch (msg.what) {case 0:Log.d(TAG"Received message: " + msg.obj);break;default:super.handleMessage(msg);}}};// 在子线程中发送消息new Thread(new Runnable() {@Overridepublic void run() {// 创建消息对象Message message = Message.obtain();message.what = 0;message.obj = "Hello from worker thread!";// 发送消息handler.sendMessage(message);}}).start();}
}

在这个示例中,我们做了以下操作:

  • onCreate 方法中,我们创建了一个 Handler 对象,并重写了 handleMessage 方法。这个方法会在收到消息时被调用,我们在这里处理收到的消息。
  • 我们创建了一个子线程,在这个线程中创建了一个 Message 对象,并通过 handler.sendMessage() 方法将其发送给 Handler。
  • 当 Handler 收到消息时,它会将消息添加到消息队列中,然后等待 Looper 取出并处理这个消息。Looper 会调用我们重写的 handleMessage 方法来处理这个消息。

实际上,Handler 的实现机制要复杂得多,涉及到 MessageQueue、Looper 等多个组件的协作。但是理解了这个基本示例,就可以对 Handler 的工作原理有一个基本的认知了。


二、Handler 源码解析


下面我们来深入解析 Handler 的源码实现。

1、 Handler 使用回顾

我们先来回顾下Handler 使用流程

// 在主线程中创建 Handler 来处理子线程发送的消息
private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case 0://TODO: 处理消息break;}}
};// 使用方式一:在子线程中发送消息
new Thread(new Runnable() {@Overridepublic void run() {Message message = new Message();message.what = 0;message.obj = "测试消息";// 子线程中发送消息handler.sendMessage(message);}
}).start();// 使用方式二:handler.post()
handler.post(new Runnable() {@Overridepublic void run() {// 运行在子线程中...}
});

通过上面代码可以看到,在使用 Handler 时首先需要创建 Handler 对象。

接下来,我们看下 Handler 的构造方法。


2、Handler构造方法源码分析

//frameworks/base/core/java/android/os/Handler.java/* 构造方法一 */
public Handler() {this(null, false);
}
/* 构造方法二 */
public Handler(Callback callback) {this(callback, false);
}
/* 构造方法三 */
public Handler(Looper looper) {this(looper, null, false);
}
/* 构造方法四 */
public Handler(Looper looper, Callback callback) {this(looper, callback, false);
}
/* 构造方法五 */
public Handler(boolean async) {this(null, async);
}
/* 构造方法六 */
public Handler(Callback callback, boolean async) {// ...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;
}
/* 构造方法七 */
public Handler(Looper looper, Callback callback, boolean async) {mLooper = looper;mQueue = looper.mQueue;mCallback = callback;mAsynchronous = async;
}

可以看到 Handler 有很多构造方法,常用的是构造方法一(实际也是调用的 :构造方法六)。

我们再来分析下构造方法六的源码:

//frameworks/base/core/java/android/os/Handler.java/* 构造方法六 */
public Handler(Callback callback, boolean async) {// ...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;
}

可以看到,这里调用了 Looper.myLooper() 方法,当 mLooper 为空时会抛出异常,提示我们需要先调用 Looper.prepare() 方法,我接下来看下 Looper 中的这两个方法。


3、Looper源码分析

  • Looper 是 Handler 的核心组成部分之一。它是一个消息循环器,负责管理消息队列,并按照先进先出的顺序处理消息。
  • 每个线程都可以创建自己的 Looper,并且主线程(UI 线程)默认就会创建一个 Looper。
  • Looper 通过 Looper.prepare() 方法初始化,然后调用 Looper.loop() 方法开启消息循环。

(1)、Looper.java

//frameworks/base/core/java/android/os/Looper.javastatic final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;final MessageQueue mQueue;
final Thread mThread;

从上面源码中可以看到 Looper 有 4 个成员变量:

  • sThreadLocal:保存的是当前线程的 Looper。
  • sMainLooper:Application 中主线程中的 Looper。
  • mQueue:当前线程中的 MessageQueue。
  • mThread:创建 Looper 的线程。

(2)、myLooper

//frameworks/base/core/java/android/os/Looper.java/* Handler 构造方法六中调用的方法 */
public static Looper myLooper() {// 返回当前线程中的 looperreturn sThreadLocal.get();
}

从上面的源码可见 myLooper() 逻辑很简单,调用了 ThreadLocal 的 get() 方法。ThreadLocal 我们稍后再分析。


(3)、prepare

在 Handler 构造方法六中可以看到,如果 myLoop() 的结果为空会直接抛出异常,提示需要先调用 prepare() 方法,接下来分析下 prepare() 方法。

/* Handler 构造方法六中调用的方法 */
public static void prepare() {prepare(true);
}
/* 带参数的 prepare 方法 */
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 构造方法 */
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();
}

prepare() 方法中调用了 prepare(quitAllowed) 方法,这里判断了 Looper 是否为空。

如果当前线程已经创建了 Looper 直接抛出异常,也就是说一个线程中只能创建一个 Looper,经常使用 Handler 的小伙伴应该对这个异常很熟悉。

如果当前线程没有创建 Looper 会直接调用 Looper(quitAllowed) 的构造方法,创建一个 Looper 并创建一个 MessageQueue,然后保存一下当前线程的信息。


4、MessageQueue源码分析

  • MessageQueue 是 Looper 的另一个核心组成部分,它是一个消息队列,负责存储和管理 Message 对象。
  • MessageQueue 提供了 enqueueMessage()next() 等方法来实现消息的入队和出队操作。

我们先看下 MessageQueue 的具体实现:

//frameworks/base/core/java/android/os/Looper.javafinal MessageQueue mQueue;/* Looper 构造方法 */
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();
}

再看下MessageQueue 的构造方法:

//frameworks/base/core/java/android/os/MessageQueue.javaprivate native static long nativeInit();MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;mPtr = nativeInit();
}

MessageQueue 的构造方法逻辑比较简单。

这里调用了一个 native 方法 nativeInit() 在 native 层进行了初始化。

感兴趣的朋友可以去查看 native 源码,文件如下:

//frameworks/base/core/jni/android_os_MessageQueue.cppstatic jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();if (!nativeMessageQueue) {jniThrowRuntimeException(env, "Unable to allocate native queue");return 0;}nativeMessageQueue->incStrong(env);return reinterpret_cast<jlong>(nativeMessageQueue);
}

至此, Handler 的创建流程已经分析完了,可以看到 Handler 创建流程如下图所示:

在这里插入图片描述


在创建 Handler 时:

第一步需要先调用 Looper.prepare(),该方法会初始化 Looper,创建 MessageQueue 和 ThreadLocal。

第二步会调用 Looper 中的 myLoop() 方法获取到 Looper 和 MessageQueue 保存到 Handler 中。


5、ThreadLocal 源码分析


如上,我们看见 第一步时,创建了 ThreadLocal 和 MessageQueue 。

(1)、ThreadLocal 到底是做什么的呢?

我们来分析下 ThreadLocal 的作用,源码如下:

ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
sThreadLocal.set(new Looper(quitAllowed)); // 设置变量信息
sThreadLocal.get(); // 读取变量信息

我们可以看到ThreadLocal在Android的消息循环机制中扮演了重要角色。

在Android中,每个线程都有自己的Looper对象,用于从MessageQueue中获取消息并执行。由于Looper对象是线程私有的,因此Android通过ThreadLocal来为每个线程维护自己的Looper实例。

具体来说:

  • ThreadLocal<Looper> sThreadLocal定义了一个线程本地存储对象,用于存放当前线程的Looper实例。

  • sThreadLocal.set(new Looper(quitAllowed))在当前线程中创建一个Looper对象,并通过set方法将其关联到当前线程。

  • sThreadLocal.get()则可以在当前线程中获取之前设置的Looper对象。

通过这种方式,Android就实现了每个线程拥有自己的Looper实例,可以独立地从MessageQueue获取和处理消息,避免了线程间数据混乱和竞争的问题。

ThreadLocal的这种线程隔离机制,使得Android的消息循环模型可以在多线程环境下高效、安全地运行,同时也体现了ThreadLocal在实现线程数据隔离方面的优秀作用。


(2)、ThreadLocal 的具体实现

接下来分析下 ThreadLocal 的具体实现。

//java/lang/ThreadLocal.javaprivate final int threadLocalHashCode = nextHashCode();private static AtomicInteger nextHashCode =new AtomicInteger();private static final int HASH_INCREMENT = 0x61c88647;private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);
}

ThreadLocal 通过 threadLocalHashCode 来标识每一个 ThreadLocal 的唯一性。

threadLocalHashCode 通过 CAS 操作进行更新,每次 hash 操作的增量为 0x61c88647。

我们来看看 ThreadLocal 的 set() 方法。

//java/lang/ThreadLocal.java
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

可以看到通过 Thread.currentThread() 方法获取了当前的线程引用,并传给了 getMap(Thread) 方法获取一个 ThreadLocalMap 的实例。

getMap(Thread) 方法中直接返回 Thread 实例的成员变量 threadLocals。它的定义在 Thread 内部,访问级别为 package 级别:

//java/lang/Thread.javaThreadLocal.ThreadLocalMap threadLocals = null;

到了这里,可以看出,每个 Thread 里面都有一个 ThreadLocal.ThreadLocalMap 成员变量,也就是说每个线程通过 ThreadLocal.ThreadLocalMapThreadLocal 相绑定,这样可以确保每个线程访问到的 ThreadLocal 变量都是本线程的。

获取了 ThreadLocalMap 实例以后,如果它不为空则调用 ThreadLocalMap.ThreadLocalMap.set() 方法设值;

若为空则调用 ThreadLocal.createMap() 方法 new 一个 ThreadLocalMap 实例并赋给 Thread.threadLocals。


(3)、ThreadLocalMap的源码分析

下面我们分析一下 ThreadLocalMap 的实现,可以看到 ThreadLocalMap 有一个常量和三个成员变量:

//java/lang/ThreadLocal.ThreadLocalMapprivate static final int INITIAL_CAPACITY = 16;private Entry[] table;private int size = 0;private int threshold; // Default to 0

其中 INITIAL_CAPACITY 代表这个 Map 的初始容量;table 是一个 Entry 类型的数组,用于存储数据;size 代表表中的存储数目; threshold 代表需要扩容时对应 size 的阈值。

Entry 类是 ThreadLocalMap 的静态内部类,用于存储数据。它的源码如下:

//java/lang/ThreadLocal.ThreadLocalMapstatic class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}

Entry 类继承了 WeakReference<ThreadLocal<?>>,即每个 Entry 对象都有一个 ThreadLocal 的弱引用(作为 key),这是为了防止内存泄露。一旦线程结束,key 变为一个不可达的对象,这个 Entry 就可以被 GC 回收了。

ThreadLocalMap 类有两个构造函数,其中常用的是 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue):

//java/lang/ThreadLocal.ThreadLocalMapThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);
}

构造函数的第一个参数就是本 ThreadLocal 实例(this),第二个参数就是要保存的线程本地变量。构造函数首先创建一个长度为 16 的 Entry 数组,然后计算出 firstKey 对应的哈希值,然后存储到 table 中,并设置 size 和 threshold。


通过上面分析可以看到 ThreadLocal 的工作原理如下:

在这里插入图片描述

如图所示,ThreadLocal 中有一个 ThreadLocalMap 其中以 ThreadLocal 作为 Key,以需要保存的值作为 Value。这样不同的线程访问同一个 ThreadLocal 时,获取到的值也就是各个线程存储时对应的值了。


分析了 ThreadLocal ,接下来,我们再来看看MessageQueue 。

6、MessageQueue 源码分析

(1)、Handler.sendMessage()

我们常用的发消息的方法如下:

//frameworks/base/core/java/android/os/Handler.javapublic final boolean sendMessage(Message msg) {return sendMessageDelayed(msg, 0);
}public final boolean sendEmptyMessage(int what) {return sendEmptyMessageDelayed(what, 0);
}public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {Message msg = Message.obtain();msg.what = what;return sendMessageDelayed(msg, delayMillis);
}

上面不管哪种发消息的方式,最后都调用了 sendMessageDelayed() 方法。

//frameworks/base/core/java/android/os/Handler.javapublic final boolean sendMessageDelayed(Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}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);
}

sendMessageDelayed() 方法最后调用了 MessageQueue.enqueueMessage()


(2)、MessageQueue.enqueueMessage()

我们接着来看 enqueueMessage() 方法的实现:

//frameworks/base/core/java/android/os/MessageQueue.javaboolean 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) {// 如果消息队列里面没有消息,或者消息的执行时间比里面的消息早,// 就把这条消息设置成第一条消息;// 一般不会出现这种情况,因为系统一定会有很多消息。msg.next = p;mMessages = msg;needWake = mBlocked;} else {// 如果消息队列里面有消息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; // 把消息添加到最后}if (needWake) {nativeWake(mPtr);}}return true;
}

分析到这里可以看到,我们通过调用 Handler.sendMessage() 最后将 Message 添加到了 MessageQueue 的消息队列中。

在前面 Looper.loop() 方法中分析过,loop() 方法中有一个死循环一直在读取消息,当读取到刚才添加的消息后会回调到 Handler.dispatchMessage() 方法。


到这里, Handler 的工作流程大家应该已经很清楚了。

如下图所示,假设在 Thread 1 中创建了 Handler,那么 Thread 2 向 Thread 1 发送消息的过程。

在这里插入图片描述


Handler 机制就像是一个传送机器,Looper 就是传送轮一直在不停的旋转,MessageQueue 就是传送带跟着Looper 旋转来运输 Message,Handler 就是机械手在 Thread 2 中将 Message 放到传送带 MessageQueue 上,传送到 Thread 1 后再将 Message 拿下来通知 Thread 1 进行处理。


(3)、Handler.post() 源码

了解了 Handler 工作流程,我们继续来分析下另一种使用方式 Handler.post()

//frameworks/base/core/java/android/os/Handler.javapublic final boolean post(Runnable r) {return  sendMessageDelayed(getPostMessage(r), 0);
}

可以看到 post() 也是调用了 sendMessageDelayed() 方法。


(4)、getPostMessage 源码

我们再来看下 getPostMessage(r) 方法的实现。

//frameworks/base/core/java/android/os/Handler.javaprivate static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;
}

原来这里创建了一个 Message,将 Runnable 放入了 Message 的 callback 上。


(5)、dispatchMessage 源码

那 Message 最后怎么处理的呢?

Looper.loop() 方法中有这么一句 msg.target.dispatchMessage(msg)

//frameworks/base/core/java/android/os/Handler.java
public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg); // 处理 post 消息,稍后再分析} else {if (mCallback != null) {// 回调到 Handler.handleMessage() 方法if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}

handleCallback() 就是处理 Handler.post() 发送的消息:

//frameworks/base/core/java/android/os/Handler.javaprivate static void handleCallback(Message message) {message.callback.run();
}

如此简单,就是拿到 Runnable 调用了 run() 方法。

至此, 关于Handler,Looper ,Message 这三者关系上面已经叙述的非常清楚了。


让我们首先总结一下:

  1. Looper.prepare()方法会为当前线程创建一个Looper实例,其内部含有与之关联的MessageQueue对象。同一线程内只能调用一次prepare(),因此MessageQueue在线程内是单例的。

  2. Looper.loop()会让当前线程进入无限循环模式,不断从MessageQueue中读取消息,并通过msg.target.dispatchMessage(msg)将消息分发给相应的Handler进行处理。

  3. 在构造Handler实例时,会获取当前线程的Looper,并将Handler的MessageQueue与Looper内部的MessageQueue建立关联。

  4. 调用Handler的sendMessage()方法时,会给Message设置target为当前Handler实例,并将Message加入关联的MessageQueue中等待分发。

  5. 在Handler的handleMessage()方法中,我们可以重写自定义的消息处理逻辑,这个方法最终会由Looper.loop()中的msg.target.dispatchMessage(msg)回调执行。


我们再来张图一目了然 :

在这里插入图片描述


五、结语

伴随着Android版本不断迭代,Handler机制也在持续优化和完善,Android 12中引入了高效模式MessageQueue和SyncQueueRemover工具等新特性。离开Handler,消息驱动架构也延伸出各种优秀的开源替代品,比如EventBus、RxJava。

未来Handler如何发展,我们拭目以待,不过这些基础底层原理的理解对开发者而言永远都是重中之重,期待您在实践中有更多思考和总结。


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

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

相关文章

易图讯智慧公安警用三维电子沙盘系统

智慧公安警用三维电子沙盘系统是一个结合现代科技手段&#xff0c;为公安部门提供全面、高效、智能的警务管理解决方案的系统。该系统以“情报大数据、指挥扁平化、勤务可视化、情指勤一体化”为设计思想&#xff0c;整合了多台设备、有无线通讯、短信平台、天网、交通平台、治…

云动态摘要 2024-05-12

给您带来云厂商的最新动态&#xff0c;最新产品资讯和最新优惠更新。 最新优惠与活动 [免费试用]即刻畅享自研SaaS产品 腾讯云 2024-04-25 涵盖办公协同、营销拓客、上云安全保障、数据分析处理等多场景 云服务器ECS试用产品续用 阿里云 2024-04-14 云服务器ECS试用产品续用…

第四届上海理工大学程序设计全国挑战赛 J.上学 题解 DFS 容斥

上学 题目描述 usst 小学里有 n 名学生&#xff0c;他们分别居住在 n 个地点&#xff0c;第 i 名学生居住在第 i 个地点&#xff0c;这些地点由 n−1 条双向道路连接&#xff0c;保证任意两个地点之间可以通过若干条双向道路抵达。学校则位于另外的第 0 个地点&#xff0c;第…

Imitation Learning学习记录(理论例程)

前言 最近还是衔接着之前的学习记录&#xff0c;这次打算开始学习模仿学习的相关原理&#xff0c;参考的开源资料为 TeaPearce/Counter-Strike_Behavioural_Cloning: IEEE CoG & NeurIPS workshop paper ‘Counter-Strike Deathmatch with Large-Scale Behavioural Clonin…

强化学习在一致性模型中的应用与实验验证

在人工智能领域&#xff0c;文本到图像的生成任务一直是研究的热点。近年来&#xff0c;扩散模型和一致性模型因其在图像生成中的卓越性能而受到广泛关注。然而&#xff0c;这些模型在生成速度和微调灵活性上存在局限。为了解决这些问题&#xff0c;康奈尔大学的研究团队提出了…

LabVIEW的MEMS电容式压力传感器测试系统

LabVIEW的MEMS电容式压力传感器测试系统 针对传统微惯性测量单元(MIMU)标定方法存在的过程繁琐、标定周期长及设备复杂等问题&#xff0c;提出了一种基于LabVIEW软件的MIMU误差参数快速标定方法。通过软件上位机控制小型三轴转台&#xff0c;配合卡尔曼滤波器技术&#xff0c;…

表的创建与操作表

1. 创建表 创建表有两种方式 : 一种是白手起家自己添&#xff0c;一种是富二代直接继承. 2. 创建方式1 (1). 必须具备条件 CREATE TABLE权限存储空间 (2). 语法格式 CREATE TABLE IF NOT EXISTS 表名(字段1, 数据类型 [约束条件] [默认值],字段2, 数据类型 [约束条件] [默…

【Java】:方法重写、动态绑定和多态

目录 一个生动形象的例子 场景设定 1. 方法重写&#xff08;Method Overriding&#xff09; 2. 动态绑定&#xff08;Dynamic Binding&#xff09; 3. 多态&#xff08;Polymorphism&#xff09; 归纳关系&#xff1a; 重写 概念 条件 重写的示例 重载与重写的区别 …

2023.5.12 第43周周报

学习时间&#xff1a;2023.5.5-2023.5.12 学习内容&#xff1a; 1、answer question: img&#xff1a; 看到有论文说应该让图像和文本的潜在嵌入具有相似和合理的数值范围【-2&#xff0c;2】 调试发现模型的文本图像的潜在嵌入虽然符合&#xff0c;但相差较大。 在将文本和…

C++/Qt 小知识记录6

工作中遇到的一些小问题&#xff0c;总结的小知识记录&#xff1a;C/Qt 小知识6 dumpbin工具查看库导出符号OSGEarth使用编出的protobuf库&#xff0c;报错问题解决VS2022使用cpl模板后&#xff0c;提示会乱码的修改设置QProcess调用cmd.exe执行脚本QPainterPath对线段描边处理…

Linux基础命令(续)

17&#xff0c;wc命令 作用&#xff1a;统计行数、单词数、字符个数 格式&#xff1a; wc 选项 文件 wc passwd 26 36 1159 passwd26&#xff1a;行数 36&#xff1a;单词数 1159&#xff1a;字符数 passwd&#xff1a;文件名wc autofs.conf 426 2604 15137 autofs.conf426…

第⼀个SpringBoot程序

Spring Boot介绍 Spring让Java程序更加快速, 简单和安全. Spring对于速度、简单性和⽣产⼒的关注使其成为 世界上最流⾏的Java框架。 Spring Boot 的诞⽣是为了简化 Spring 项目而诞生的 创建Spring Boot项目 File->New Project->Spring Initializr 选择2.多的版本 创建…