学习 Android(十五)NDK进阶及性能优化

学习 Android(十五)NDK进阶及性能优化

对 NDK 相关知识有了初步的了解之后,我们可以更加深入的去学习 NDK 相关知识,接下来,我们将按照以下步骤进行深入学习:

  • 深入理解JNI调用过程和性能消耗
  • 常见 JNI 坑(比如频繁创建Java对象、内存泄漏)
  • 掌握 Native 内存管理,避免泄漏和崩溃
  • 学习 pthread 多线程和同步机制,和 Android 线程的配合
  • 多线程环境下调用 JNI 注意事项,跨线程回调技巧

1. 深入理解 JNI 调用过程和性能消耗

深入理解 JNI 调用过程和性能消耗,是掌握 Android NDK 开发的关键,有助于写出高效、稳定的混合代码。

1.1 JNI 调用过程详解

JNI(Java Native Interface) 是 Java虚拟机(JVM)与本地(Native)代码交互的桥梁,Android 上是 JVM 的子集 ———— ART(Android Runtime)。JNI 允许 Java 调用 C/C++,也允许 Native 调用 Java 方法。

1.1.1 Java 调用 Native 的典型流程
  • 声明 Native 方法

    Java 代码通过 native 关键字声明本地方法,并加载 native 库:

    class MyClass {static {System.loadLibrary("myLib");}public native int nativeMethod(int arg);
    }
    
  • Native 端实现(C/C++)

    使用 JNI 约定的函数签名实现:

    extern "C" JNIEXPORT jint JNICALL
    Java_com_example_MyClass_nativeMethod(JNIEnv* env, jobject thiz, jint arg) {
    }
    
  • 调用流程

    • Java 层调用 native 方法时,ART 会查找与方法签名匹配的本地实现

    • 通过 JNI 函数指针跳转到本地实现

    • 本地代码通过传入的 JNIEnv* 环境指针访问 JVM 提供的接口(操作对象、数组、调用 Java 方法)

    • 执行完毕返回结果,JNI 自动转换回 Java 层

1.1.2 Native 回调 Java
  • 本地代码通过 JNIEnv 指针调用 CallVoidMethodCallIntMethod 等 JNI 函数,访问 Java 对象。

  • 使用 FindClass 查找 Java 类,GetMethodID 获取方法 ID 等。

1.1.3 JNIEnv 和线程关联
  • 每个线程都必须有自己的 JNIEnv* 指针,不能跨线程使用。

  • Java 线程进入 Native 代码时,Java 虚拟机会传入 JNIEnv

  • Native 线程调用 Java 方法前,必须附加到 JVM(AttachCurrentThred)获取 JNIEnv


1.2 JNI 调用的性能消耗来源分析

虽然 JNI 是实现 Java 和 C/C++ 互操作的唯一通道,但调用代价较高,性能损耗主要来自以下方面:

1.2.1 调用开销

每次 Java 调用 Native 方法,都涉及 JNI 桥接、参数转换、堆栈切换等,成为跨语言调用开销。

  • 方法查找

    使用 GetMethodIDFindClass 等接口查找类/方法都会引发字符串查找和反射操作,建议提前缓存 ID。

  • 参数转换

    JNI 参数和返回值往往需要进行转换,比如 Java 数组转 native 数组(GetIntArrayElements),这会产生内存拷贝和映射。

  • 堆栈切换

    从 Java 虚拟机切换到 native 运行环境,也涉及上下文切换开销。

1.2.2 频繁调用和跨界面层传递大量数据
  • 若调用 JNI 设计不合理,频繁调用小粒度函数,开销累计显著。

  • 大量数据传递(如大数组、复杂对象)通过 JNI 参数传输,会产生内存复制,影响性能。

1.2.3 内部管理和局部引用开销
  • JNI 会在 native 层为 Java 对象创建局部引用,如果不及时释放会导致局部引用表溢出。

  • 使用 NewGlobalRef 增加全局引用也带来额外管理成本。

1.2.4 异常检测

每次 JNI 调用之后,JNI 环境会检测是否有 Java 异常,需要额外执行异常处理流程,若异常频发也影响性能。


1.3 JNI 性能优化使用技巧

1.3.1 减少 JNI 调用次数
  • 设计合理的接口,尽量减少 Java 和 Native 之间的频繁小函数调用,更倾向于批量调用。

  • 把一些需要循环调用的逻辑放到 Native 层一次处理完。

1.3.2 缓存方法ID和类引用
  • 缓存 jclass 和方法IDjmethodId,避免频繁使用 FindClassGetMethodID

  • 注意缓存的类引用要全局引用(NewGlobalRef),避免被 GC 回收。

1.3.3 优化数组和字符串操作
  • 对数组,优先使用 GetPrimitiveArrayCritical ,减少复制(但要注意对代码稳定性和互斥性的影响)。

  • 传递大数组时,尽量避免复制,改为操作指针/缓冲区。

  • 对于 String 类型,避免频繁转换,尽量在 native 一侧使用 UTF-8 编码(GetStringUTFChars)。

1.3.4 缩短本地代码运行时间/减少局部引用
  • 本地代码不要做耗时操作后立刻回到 Java,减少跨界调用压。

  • 使用DeleteLocalRef显式释放局部引用,防止泄漏;对于大循环内产生大量局部引用更要注意。

1.3.5 线程相关优化
  • 避免频繁调用AttachCurrentThreadDetachCurrentThread,一般线程周期内只调用一次。
1.3.6 异常判断与处理要有选择性
  • JNI 异常检测开销不算太大,但频繁触发异常检查会影响性能。
  • 合理判断并只在需要时检查异常,如无异常预期场景可优化。

2. 常见JNI坑(比如频繁创建Java对象、内存泄漏)

在 Android NDK 开发中,JNI 是 Java 与 Native 代码交互的桥梁,但不当使用很容易出现问题,导致性能问题、内存泄漏甚至程序崩溃。接下来我们分析一些常见的 JNI 坑,尤其是频繁创建 Java 对象、内存泄漏,并研究如何规避。

2.1 频繁创建 Java 对象的坑

2.1.1 现象与原因
  • JNI 代码中频繁通过 NewObjectNewStringUTFNewObjectArray 等接口创建 Java 对象,尤其是在循环内。

  • 这会导致:

    • JVM 频繁进行对象分配和 GC,严重影响性能。

    • 由于所有新建对象均为局部引用,未及时释放可能导致局部引用表溢出

2.1.2 典型示例
for (int i = 0; i < n; i++) {jstring str = env.NewStringUTF("hello");// 使用 str// 如果这里不调用 DeleteLocalRef, str 累积导致局部引用溢出
}
2.1.3 解决方案
  • 避免在循环中频繁创建 Java 对象,尽量批量创建或复用。

  • 及时释放局部引用

    JNI 代码中局部引用默认在函数返回时释放,但对于长时间运行的循环应手动调用:

    env.DeleteLocalRef(str);
    
  • 如果对象只在 Native 层使用,尽量用 Native 数据结构存储,减少Java对象转换

  • 使用全局引用缓存对象,但需注意手动释放,以避免全局内存泄漏。


2.2 内存泄漏问题

JNI 内存泄漏主要有两大来源:

2.2.1 局部引用不释放导致局部引用表溢出
  • 每个 JNI 本地方法有一个局部引用表,容量有限(一般512个引用)。
  • 如果 JNI 方法创建或获取大量局部引用,但不及时释放,且方法运行时间较长,局部引用表会溢出,导致崩溃。

解决方法:

  • 尽量缩短本地方法运行时长,分批处理任务。
  • 循环内显式调用 DeleteLocalRef 释放局部引用。
  • 对大批量 Java 对象操作时,使用PushLocalFrame 和 PopLocalFrame 管理局部引用。

示例:

for (int i = 0; i < bigNum; i++) {jstring str = env.NewStringUTF("test");// 业务逻辑env.DeleteLocalRef(str);
}
2.2.2 全局引用未释放导致全局内存泄漏
  • 使用 NewGlobalRef 创建的全局引用不会被 GC 自动回收。
  • 如果程序中全局引用被创建后没有被释放,导致内存泄漏。

解决方法:

  • 对不再使用的全局引用调用 DeleteGlobalRef 释放。

示例

jobject globalObj = env.NewGlobalRef(localObj);
// 业务使用
env.DeleteGlobalRef(globalObj);
2.2.3 字符串和数组 Get/Release 不匹配

JNI中很多接口都需要用户主动释放资源,如:

  • GetStringUTFChars 与 ReleaseStringUTFChars
  • GetIntArrayElements 与 ReleaseIntArrayElements

如果不调用释放接口,可能会导致内存泄漏或者数据未同步。

示例:

const char* nativeStr = env.GetStringUTFChars(jstr, 0);
// 使用 nativeStr,但忘了调用释放
// env.ReleaseStringUTFChars(jstr, nativeStr);

3. 掌握 Native 内存管理,避免泄漏和崩溃

在 Android NDK 及其他使用 C/C++ 开发的 Native 代码中,内存管理是开发稳定、高效应用的根本技能。相比 Java,Native 代码需要开发者手动管理内存,一旦失误可能导致内存泄漏、野指针、崩溃等严重问题。接下来我们进行全面理解和掌握 Native 内存管理,避免内存相关的坑。

3.1 内存泄漏的根本原因与规避策略

场景描述避免策略
未释放 malloc/new 的内存使用 malloc/new 分配后未 free/delete采用智能指针(C++)或显式成对调用;如 unique_ptr
分配的对象被提前返回/异常中断出现 early return 或异常路径,未释放使用 RAII 模式自动释放资源
JNI New* 函数未 Delete*创建局部/全局引用后未释放使用 DeleteLocalRef / DeleteGlobalRef
多线程共享对象未同步释放多线程访问同一对象导致重复释放/未释放加锁保护共享资源,避免野指针

3.2 JNI 资源管理核心规则

3.2.1 GetStringUTFChars / ReleaseStringUTFChars
  • 这两个 API 不会复制 Java 字符串内存,而是返回指针(有时会)。

    什么叫做有时会呢?

    关于 GetStringUTFChars 是否复制 Java 字符串内存的问题,确实存在「有时会,有时不会」的情况,这是由 JVM 的实现细节字符串内容 共同决定的。

    先看官方文档定义

    const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);

    • 返回一个指向 UTF-8 编码字符串的指针。

    • *isCopy 会被设置为:

      • JNI_TRUE:表示 JVM 复制了一份内存

      • JNI_FALSE:表示返回的是 JVM 内部的只读缓存指针不是拷贝)。

    什么时候会复制?

场景解释
Java 字符串包含 非 ASCII 字符JVM 需要将 UTF-16 编码转换为 UTF-8
JVM 无法保证返回区域是连续内存比如字符串被压缩存储时
字符串内容被压缩/混淆存储JVM 无法零拷贝转换
特定 JVM 实现本身策略就是安全第一比如 Android ART 通常直接复制
使用多线程共享字符串访问JVM 会返回副本保证线程安全

什么时候不会复制?

场景解释
字符串内容是 ASCII,且结构简单无需转换,JVM 可以提供只读指针
使用的是 HotSpot VM,且 JDK 版本较低在某些平台上,HotSpot 优化路径中可能避免复制
单线程访问,JVM 优化已缓存字符串JVM 内部可能已有 UTF-8 缓存区
  • 用完后必须 ReleaseStringUTFChars,否则会占用 JVM 内部缓存区。

    为什么必须 ReleaseStringUTFChars

    即使 JVM 没有复制,也要调用 ReleaseStringUTFChars,因为:

    1. 你不知道是否复制了(依赖运行时行为);

    2. JVM 可能会在你释放之前锁定该字符串区域

    3. 不释放可能导致 内存泄漏阻塞 JVM 垃圾回收

    4. 有些 JVM 会记录这个指针的使用情况,未释放可能造成崩溃。

3.2.2 NewLocalRefDeleteLocalRef
  • JNI 局部引用存在于调试栈帧中,方法退出自动释放

  • 若创建大量局部引用,应主动 DeleteLocalRef,避免 Local reference table overflow

3.2.3 全局引用需手动释放
jobject g_obj = (*env)->NewGlobalRef(env, obj);
// ...使用
(*env)->DeleteGlobalRef(env, g_obj);

3.3 调试与诊断工具

工具用途
Valgrind(Native)检查 C/C++ 内存泄漏、越界访问
ASan(AddressSanitizer)更适合 Android NDK,用于 native 崩溃和越界
Perfetto / systrace查找 native 层卡顿和资源滥用
Android Studio Profiler追踪 JNI 调用和内存泄漏情况
logcat 日志分析搭配 __android_log_print 分析生命周期

3.4 防止崩溃的工程实践

问题防范措施
空指针解引用严格检查 null,使用智能指针封装访问
野指针/重复释放避免裸指针,释放后设置为 nullptr
多线程并发访问线程同步+生命周期管理
Java 调用 native 后释放对象使用全局引用保护生命周期,或采用 WeakGlobalRef

4. 学习 pthread 多线程和同步机制,和 Android 线程的配合

pthread 在 NDK 中是绕不开的核心技术,接下来我们来快速的学习和了解 pthread多线程和同步机制,并且如何和 Android 线程的配合

4.1 pthread 在 Android 中的使用

Android 的 Native 层(C/C++)并不支持 Java 的 Thread ,因此如果需要多线程,就用 POSIX 线程库(pthread),它在 Android SDK 中完全可用:

  • pthread_create:创建线程

  • pthread_join:等待线程结束

  • pthread_mutex_t:互斥锁

  • pthread_cond_t:条件变量

  • pthread_rwlock_t:读写锁

  • pthread_once:单次初始化

在 Android 上,pthread 的 ABI 与 Linux 一样,因为 Android 本质也是基于 Linux 内核。


4.2 Android JAVA 线程与 pthread 的配合

Java 线程Native 线程 之间是可以共存的,但要注意几点:

  • Java 层启动的线程:如果在 Native 中执行,需要从 JNIEnv 传入,或者通过 AttachCurrentThread 重新附着(因为每个线程都有自己唯一的 JNIEnv)。

  • Native 启动的线程:用 pthread_create,如果需要调用 Java 方法,同样必须先 AttachCurrentThread,否则会崩溃。

示例

void* thread_func(void* arg) {JNIEnv* env;JavaVM* javaVm = (JavaVM*)arg;javaVm->AttachCurrentThread(&env, nullptr);// 这里就可以用 env 调用 Java 方法// ...javaVm->DetachCurrentThread();return nullptr;
}

这段代码是一个典型的在 Native 线程中通过 JavaVM 获取 JNIEnv 并调用 Java 方法的示例。我来分析一下关键点:

  • 函数原型:

    • 这是一个标准的 POSIX 线程函数,返回 void,接收 void 参数

    • 参数 arg 被强制转换为 JavaVM* 指针

  • 关键操作:

    • AttachCurrentThread():将当前 native 线程附加到 JVM,获取 JNIEnv 指针

    • DetachCurrentThread():线程结束时解除与 JVM 的关联

  • 重要细节:

    • 每个线程都需要通过 AttachCurrentThread 获取自己的 JNIEnv,不能跨线程使用

    • 必须成对调用 Attach/Detach,否则会导致内存泄漏

    • 在 Android 上,不 Detach 会导致 app 崩溃(DEBUG 模式下)

  • 使用场景:

    • 当在非 Java 创建的线程(如 pthread)中需要调用 Java 方法时

    • 常见于 Native 异步回调到 Java 层的场景


4.3 常用的同步原语

同步方式说明
pthread_mutex_t最常用的互斥锁
pthread_cond_t条件变量
pthread_rwlock_t读写锁
pthread_spinlock_t自旋锁
pthread_barrier_t屏障(同步多个线程)

在 Android Native 开发里,最常用的依然是互斥锁 + 条件变量。举个常见场景:

  • 一个生产者线程写数据

  • 一个消费者线程读取数据

  • 通过 pthread_mutex_tpthread_cond_t 同步


4.4 与 Java 层线程的区别

  • Java 的 Thread 实际上由 Android Runtime (ART) 或 Dalvik 管理
    pthread 实现,但对你透明

  • Java 线程有 Looper / Handler / MessageQueue 等机制
    Native 没有这些机制,需要你手动管理队列 + 锁


4.5 Android 中最佳实践

  • 避免在 Native 层大量启动线程,因为调试复杂

  • 如果需要高并发,优先考虑 Java 层线程池

  • 在确实需要硬件交互、实时音视频等高性能 Native 线程时,用 pthread
    并且记得:

    • AttachCurrentThread

    • 正确释放 DetachCurrentThread

    • JNIEnv 只能在当前线程使用


5. 多线程环境下调用 JNI 注意事项,跨线程回调技巧

在多线程环境下使用 JNI(Java Native Interface)时,必须非常小心,否则会导致 崩溃、内存泄漏、线程挂起 等严重问题。以下是实战经验总结与跨线程安全调用 Java 的技巧。

5.1 JNI 多线程环境下的基本准则

5.1.1 JNIEnv* 是线程私有的
  • 每个线程都必须使用自己绑定的 JNIEnv*

  • 不能跨线程传递 JNIEnv* 指针,否则会崩溃或产生不确定行为

5.1.2 子线程中使用 JNI 必须先附加线程
  • 使用 JavaVM* 中的 AttachCurrentThread()获取当前线程的中 JNIEnv*

  • 线程退出前必须执行 DetachCurrentThread(),否则 JVM 会泄漏线程资源

5.2 JNI 跨线程回调 Java 的正确方式

场景:Native 中开启一个线程,任务完成后回调 Java 的方法

步骤:

  1. 缓存 JavaVM* 和 Java 层对象的 jobject(用 NewGlobalRef() 防止被 GC)

  2. 在 Native 线程中通过 AttachCurrentThread() 获取 JNIEnv*

  3. 调用 Java 方法(例如回调)

  4. 调用完毕后 DetachCurrentThread()

示例代码:

Kotlin / Java

class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)startNativeTask()}external fun startNativeTask()companion object {// Used to load the 'hello' library on application startup.init {System.loadLibrary("hello")}}fun onNativeTaskComplete() {runOnUiThread {Toast.makeText(this, "任务完成", Toast.LENGTH_SHORT).show()}}}

Native


JavaVM *g_vm = nullptr;
std::atomic<jobject> g_callback_obj{nullptr};jint JNI_OnLoad(JavaVM *vm, void *) {g_vm = vm;return JNI_VERSION_1_6;
}void* thread_func(void*) {JNIEnv* env;if (g_vm->AttachCurrentThread(&env, nullptr) != JNI_OK) {__android_log_print(ANDROID_LOG_ERROR, "NativeThread", "Attach failed");return nullptr;}if (g_callback_obj == nullptr) {__android_log_print(ANDROID_LOG_ERROR, "NativeThread", "Callback object is null");g_vm->DetachCurrentThread();return nullptr;}jclass cls = env->GetObjectClass(g_callback_obj);if (cls == nullptr) {__android_log_print(ANDROID_LOG_ERROR, "NativeThread", "Class not found");g_vm->DetachCurrentThread();return nullptr;}jmethodID methodID = env->GetMethodID(cls, "onNativeTaskComplete", "()V");if (methodID == nullptr) {__android_log_print(ANDROID_LOG_ERROR, "NativeThread", "Method not found");env->DeleteLocalRef(cls);g_vm->DetachCurrentThread();return nullptr;}env->CallVoidMethod(g_callback_obj, methodID);if (env->ExceptionCheck()) {env->ExceptionDescribe();env->ExceptionClear();}env->DeleteLocalRef(cls);g_vm->DetachCurrentThread();return nullptr;
}extern "C"
JNIEXPORT void JNICALL
Java_com_example_hello_MainActivity_startNativeTask(JNIEnv *env, jobject thiz) {jobject old_ref = g_callback_obj.exchange(env->NewGlobalRef(thiz));if (old_ref != nullptr) {env->DeleteGlobalRef(old_ref);}pthread_t thread;pthread_create(&thread, nullptr, thread_func, nullptr);pthread_detach(thread); // 避免内存泄漏
}

示例代码分析:

关键组件解析:

变量/函数作用
g_vm全局 JavaVM*,用于跨线程 Attach/Detach JNIEnv
g_callback_obj全局引用(jobject),保存 Java 层的回调对象(MainActivity 实例)
JNI_OnLoad动态库加载时初始化 g_vm
thread_funcNative 线程函数,执行任务并回调 Java 方法
startNativeTaskJNI 入口,启动 Native 线程并设置回调对象

内存管理分析:

  1. 全局引用 (g_callback_obj)

    • 正确做法:

      • 使用 env->NewGlobalRef() 将局部引用提升为全局引用(避免被 GC 回收)。

      • 每次更新回调对象时,先删除旧引用(DeleteGlobalRef)。

    • 代码验证:

      jobject old_ref = g_callback_obj.exchange(env->NewGlobalRef(thiz));
      if (old_ref != nullptr) {env->DeleteGlobalRef(old_ref);  // 释放旧引用
      }
      

      优点:避免了全局引用泄漏。

  2. 局部引用(cls)

    • 正确做法:

      • GetObjectClass 返回的 jclass 是局部引用,需手动释放(DeleteLocalRef)。

      • 代码中在 DetachCurrentThread 前正确释放:

        env->DeleteLocalRef(cls);
        

线程安全设计

  1. g_callback_obj 的原子操作
  • 问题:多线程可能同时读写 g_callback_obj

  • 解决方案

    • 使用 std::atomic<jobject> 确保原子性。

    • 通过 exchange 方法安全更新引用:

      jobject old_ref = g_callback_obj.exchange(env->NewGlobalRef(thiz));
      
  1. JNIEnv 的线程隔离
  • 规则JNIEnv* 是线程局部的,不能跨线程共享。

  • 代码验证

    • 每个线程通过 AttachCurrentThread 获取自己的 env

    • 线程退出前调用 DetachCurrentThread(即使在异常情况下也通过 try-catch 保证执行)。

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

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

相关文章

QT5.12.8 QTabWidget 透明样式QSS

/* 设置QTabWidget本身 :不加也行*/ QTabWidget#aaa_tabwdt {background: transparent;border: none; /* 移除边框可能有助于透明效果 */ }/* 标签页内的容器部件 :必须加&#xff0c;标签也才会透明 */ QTabWidget#aaa_tabwdt QWidget, QTabWidget#aaa_tabwdt QFrame {backgro…

【FAQ】Script导出SharePoint 目录文件列表并统计大小

一、只导出文件列表的方法 1) 保存脚本&#xff08;建议名&#xff1a;D:\tmp\Export-SharePoint-FileList.ps1&#xff09; <# 导出 SharePoint 指定文件夹&#xff08;含子文件夹&#xff09;的文件列表到 CSV&#xff08;不统计大小&#xff09; 前提&#xff1a;已安…

《Thinking in Java》读书笔记---控制执行流程

就像有感知的生物一样&#xff0c;程序必须在执行过程中控制它的世界&#xff0c;并做出选择。在Java中&#xff0c;你要使用执行控制语句来作出选择。一、流程控制基础概念1.1 流程控制的重要性流程控制结构决定了程序执行的顺序和逻辑分支&#xff0c;是编程语言中最基础也是…

极验 G-star 人才特训营:为业务安全领域培养下一代新兴力量

本文导读 极验为什么要启动 G-star 实习生培养计划&#xff1f;50多位来自多所高校的同学&#xff0c;在极验经历了一场怎样的“非典型”实习&#xff1f;技术大咖亲授&#xff0c;先培训再实战&#xff0c;极验打造的是怎样的人才体系&#xff1f;同学有话说&#xff1a;培养计…

攻防世界-web-csaw-mfw

一.题目分析这边提示使用了Git&#xff0c;试着访问.git看是否存在.git泄露浏览了一下&#xff0c;很多都是乱码&#xff0c;想着用githack将git库克隆下看一下二.操作python2 GitHack.py http://url/.git访问了一下flag.php&#xff0c;没啥发现&#xff0c;在看一下index.php…

202506 电子学会青少年等级考试机器人四级实际操作真题

更多内容和历年真题请查看网站&#xff1a;【试卷中心 -----> 电子学会 ----> 机器人技术 ----> 四级】 网站链接 青少年软件编程历年真题模拟题实时更新 2025年6月 青少年等级考试机器人实操真题四级 实际操作 主题&#xff1a;感应节能灯&#xff08;四级&am…

DLT645电表数据 保存到MySQL数据库项目案例

目录 1 案例说明 2 VFBOX网关工作原理 3 准备工作 4 配置VFBOX网关采集DLT645电表数据 5 网关写数据到MYSQL数据库 6 安装MYSQL数据库 7 其他说明 8 案例总结 1 案例说明 设置网关采集DLT645电表数据数据把采集的数据保存到MySQL数据库。 2 VFBOX网关工作原理 VFBOX网关…

Redux与React - 异步状态操作(React快速上手4)

异步操作样板代码1. 创建store的写法保持不变&#xff0c;配置好同步修改状态的方法 2. 单独封装一个函数&#xff0c;在函数内部return一个新函数&#xff0c;在新函数中 2.1 封装异步请求获取数据 2.2 调用同步actionCreater传入异步数据生成一个action对象&#xff0c;并使用…

win10桌面右键没有新建word

win10右键新建word不见解决方法1、点击开始&#xff0c;找到运行命令行&#xff0c;输入regedit&#xff0c;打开注册表。2、在左侧找到HKEY_CLASSES_ROOT目录&#xff0c;并展开。3.找到.docx 双击&#xff08;默认&#xff09;一项&#xff0c;将其改为 Word.Document.12。关…

Docker国内可用镜像(2025.08.06测试)

Docker渡渡鸟镜像可用&#xff08;测试时间2025.08.06&#xff09;https://docker.aityp.com/使用渡渡鸟镜像pull ollama latest 例子&#xff1a;docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/ollama/ollama:0.10.1毫秒镜像和轩辕镜像也可用&#xff0c;但…

决策树的实际案例

决策树作为一种直观、易解释的机器学习算法&#xff0c;在金融、医疗、电商、风控等多个领域都有广泛的实际应用。以下将讲解1个典型案例&#xff1a;贷款违约预测。对于贷款违约预测&#xff0c;即在贷款人员在贷款之前对其进行预测&#xff0c;通过他的众多特征情况判别是否可…

bool 类型转换运算符重载

以下是一个极简且聚焦核心知识点的示例代码&#xff0c;用最直观的方式演示 bool 类型转换运算符重载的触发逻辑、使用场景和避坑点&#xff0c;帮你快速掌握&#xff1a;cpp运行#include <iostream> using namespace std;// 核心类&#xff1a;演示 bool 转换运算符 cla…

LVGL代码框架简介

LVGL代码框架介绍LVGL&#xff08;Light and Versatile Graphics Library&#xff09;是一个轻量级、功能强大的嵌入式图形库。其代码架构设计清晰&#xff0c;模块化程度高。1. 整体架构层次LVGL采用分层架构设计&#xff0c;主要包含以下几个层次&#xff1a;┌───────…

【云计算】云主机的亲和性策略(三):云主机 宿主机

《云主机的亲和性策略》系列&#xff0c;共包含以下文章&#xff1a; 1️⃣ 云主机的亲和性策略&#xff08;一&#xff09;&#xff1a;快乐旅行团2️⃣ 云主机的亲和性策略&#xff08;二&#xff09;&#xff1a;集群节点组3️⃣ 云主机的亲和性策略&#xff08;三&#xf…

【世纪龙科技】虚拟技术助力职教汽车自动变速器拆装虚拟实训软件

在职业院校汽车专业实训课堂上&#xff0c;教师常面临这样的两难&#xff1a;学生围在昂贵的自动变速器实物旁&#xff0c;却因设备数量有限只能轮流操作&#xff1b;拆装步骤稍有偏差便可能损坏精密部件&#xff0c;维修成本让本就紧张的教学经费雪上加霜&#xff1b;传统教学…

[LVGL] 配置lv_conf.h | 条件编译 | 显示屏lv_display

链接&#xff1a;https://docs.lvgl.io/master/ docs&#xff1a;LVGL LVGL&#xff08;Light and Versatile Graphics Library&#xff09;是用于在资源受限的嵌入式系统上创建图形用户界面&#xff08;GUI&#xff09;的开源解决方案。 它提供丰富的控件和灵活的**事件系…

sqli-labs通关笔记-第32关 GET宽字符注入(单引号闭合 手工注入+脚本注入两种方法)

目录 一、宽字符注入 二、代码审计 1、代码审计 2、SQL注入安全性分析 三、渗透实战 1、进入靶场 2、id1探测 3、id%df-- 探测 4、手工注入 &#xff08;1&#xff09;获取列数 &#xff08;2&#xff09;获取回显位 &#xff08;3&#xff09;获取数据库名 &…

MySQL的创建管理表:

目录 基础知识&#xff1a; 一条数据存储的过程&#xff1a; 标识符命名规则&#xff1a; 创建和管理数据库&#xff1a; 创建数据库&#xff1a; 创建数据库&#xff1a; 创建数据库并指定字符集&#xff1a; 判断数据库是否存在&#xff0c;不存在则创建数据库&#…

Linux Vi常用指令总结

Vi&#xff08;及其改进版 Vim&#xff09;是 Linux 中常用的文本编辑器&#xff0c;虽然入门有一定门槛&#xff0c;但熟练掌握后效率极高。以下是常见指令分类整理&#xff1a;1. 模式切换 普通模式&#xff08;命令模式&#xff09;&#xff1a;启动后的默认模式&#xff0c…

解决远程连接云服务器mysql编号1130问题

连接云服务器mysql失败&#xff0c;可能是因为ip发生改变&#xff0c;被mysql拦截。我自己前后做了两件事。大家赶时间可直接从二开始&#xff0c;不放心的就从一开始到结束一、在云服务器实例中为当前ip配置安全组权限。1.找到当前服务器的实例&#xff0c;进入安全组。为其增…