学习 Android(十四)NDK基础

学习 Android(十四)NDK基础

Android NDK 是一个工具集,可让我们使用 C 和 C++ 等语言以原生代码实现应用的各个部分。对于特定类型的应用,这可以帮助我们重复使用以这些语言编写的代码库。

接下来,我们将按照以下步骤进行讲解

  • NDK 是什么,作用和原理
  • Android Studio 中配置 NDK 与 CMake
  • 创建简单 Native 库(C/C++),Java 调用 Native 方法
  • 了解 JNI 基本概念,基本数据类型映射,Java 和 C++ 函数签名
  • 学习如何传递 Java 字符串、数组到 Native ,反之亦然

1. NDK 是什么?作用和原理

1.1 NDK 是什么?

原生开发套件 (NDK) 是一套工具,能够让我们在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库,我们可使用这些平台库管理原生 activity 和访问实体设备组件,例如传感器和触控输入。NDK 可能不适合大多数 Android 编程初学者(例如作者我),初学者只需使用 Java 代码和框架 API 开发应用。然而,我们需要实现以下一个或多个目标,那么 NDK 就能派上用场:

  • 进一步提升设备性能,以降低延迟或运行游戏或物理模拟等计算密集型应用。

  • 重复使用您自己或其他开发者的 C 或 C++ 库。

我们可以在 Android Studio 2.2 或更高版本中使用 NDK 将 C 和 C++ 代码编译到原生库中,然后使用 Android Studio 的集成构建系统 Gradle 将原生库打包到 APK 中。Java 代码随后可以通过 Java 原生接口 (JNI) 框架调用原生库中的函数。

Android Studio 编译原生库的默认构建工具是 CMake。由于很多现有项目都使用 ndk-build 构建工具包,因此 Android Studio 也支持 ndk-build。不过,如果要创建新的原生库,则应使用 CMake。

1.2 NDK 的工作原理

NDK 的本质是通过 JNI(Java Native Interface)桥接 Java/Kotlin 和 C/C++ 本地代码,从而实现跨语言通信与调用,并在 Android 系统中生成 .so 动态链接库供运行时加载。

  • 整体架构流程图如下所示
Java/Kotlin 层|| 调用 native 方法v
JNI (Java Native Interface)|| 负责参数类型转换、方法注册v
C/C++ 层代码(通过 NDK 编译)|| 编译为 .so 动态库v
libnative-lib.so 被 Android 加载并运行
  • Java 层声明 native 方法

    我们首先要在 Java 或 Kotlin 中用 native 关键字声明一个方法:

    public class NativeLib {static {System.loadLibrary("native-lib"); // 加载 C/C++ 编译生成的 .so 文件}public native int add(int a, int b); // native 方法,C/C++ 实现
    }
    
  • C/C++ 层实现(通用JNI)

    我们需要在 C/C++ 中用 JNI 方式实现这个方法,签名必须完全匹配

    extern "C" JNIEXPORT jint JNICALL
    Java_com_example_NativeLib_add(JNIEnv *env, jobject thisz, jnit a, jint b) {return a + b;
    }
    
    • JNIEnv* 是 JNI 环境指针(用于访问 Java)

    • jobject 是 Java 传进来的对象引用(即 this)

  • 构建和变异位动态库(.so 文件)

    使用 CMakeLists.txtAndroid.mk 构建规则,把你的 C++ 文件编译成 .so

    • 输出目录:app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so

    • 会被打包进 APK,在运行时由 System.loadLibrary 加载

  • 运行时调用流程

    • 用户点击或代码调用 NativeLib.add()

    • JVM 会通过 JNI 找到 .so 文件中注册的 Java_com_example_NativeLib_add() 方法

    • 调用 C++ 实现,返回结果给 Java

2. Android Studio 中配置 NDK 与 CMake

2.1 在 Android Studio 中操作:

  1. 打开 Preferences(设置)

    • macOS: Android Studio > Preferences

    • Windows/Linux: File > Settings

  2. 导航到:
    Appearance & Behavior > System Settings > Android SDK > SDK Tools

  3. 勾选并安装:

    • NDK (Side by side)

    • CMake

    • LLDB(可选,调试 C++ 用)

2.2 配置 build.gradle 文件

以下以 App 模块的 build.gradle(Groovy 版) 为例说明配置方式:

  1. defaultConfig 中添加:

    defaultConfig {...externalNativeBuild {cmake {cppFlags ""}}ndk {abiFilters 'armeabi-v7a', 'arm64-v8a' // 你可以根据需求精简架构}
    }
    
  2. 配置 externalNativeBuild

    android {...externalNativeBuild {cmake {path "src/main/cpp/CMakeLists.txt" // 指向你的 CMake 配置文件version "3.22.1" // 根据你安装的版本写}}
    }
    

2.3 创建 C/C++ 文件和 CMake 配置

app/└── src/└── main/├── cpp/│    ├── native-lib.cpp│    └── CMakeLists.txt└── java/

native-lib.cpp

#include <jni.h>extern "C"
JNIEXPORT jint JNICALL
Java_com_example_ndkdemo_NativeLib_add(JNIEnv *env, jobject obj, jint a, jint b) {return a + b;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.10.2)project("ndkdemo")add_library( # 构建库名native-libSHAREDnative-lib.cpp
)find_library( # 找到 log 库log-liblog
)target_link_libraries( # 链接 log 库native-lib${log-lib}
)

2.4 Java 层调用 native 方法

public class NativeLib {static {System.loadLibrary("native-lib"); // 加载 .so}public native int add(int a, int b); // 声明 native 方法
}

2.5 构建与运行

  1. 点击 Build → Rebuild Project

  2. .so 文件将生成在:
    app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so

  3. 如果你运行到 ARM64 模拟器或真机,程序会自动加载对应 .so 并调用你的 native 方法。

3. 创建简单 Native 库(C/C++),Java 调用 Native 方法

3.1 步骤一:项目结构准备

在 Android Studio 中新建一个空项目(Empty Activity),选择 Java语言,API 21,然后按如下结构添加文件:

app/└── src/└── main/├── cpp/│    ├── native-lib.cpp      C++ 实现文件│    └── CMakeLists.txt      CMake 构建文件└── java/com/example/ndkdemo/└── NativeLib.java      Java 调用封装类

3.2 步骤二:配置 build.gradle (app 模块)

android {defaultConfig {...// 指定使用的 ABI 架构ndk {abiFilters 'armeabi-v7a', 'arm64-v8a'}// 配置 CMake 构建externalNativeBuild {cmake {cppFlags ""}}}// 指定 CMake 构建文件路径externalNativeBuild {cmake {path "src/main/cpp/CMakeLists.txt"}}
}

3.3 步骤三:创建 CMake 构建文件(CMakeLists.txt)

app/src/main/cpp/CMakeLists.txt 中实现

cmake_minimum_required(VERSION 3.10.2)
project("ndkdemo") // 记得这是填对应的名称add_library( # native 库名native-libSHAREDnative-lib.cpp
)find_library( # 引用 Android 日志库(可选)log-liblog
)target_link_libraries(native-lib${log-lib}
)

3.4 步骤四:实现 C++ 代码(native-lib.cpp)

app/src/main/cpp/native-lib.cpp 中实现

#include <jni.h>// 使用 extern "C" 避免 C++ 方法名被改写(mangling)
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_ndkdemo_NativeLib_add(JNIEnv *env, jobject thiz, jint a, jint b) {return a + b;
}

3.5 步骤五:Java 封装 Native 调用

com/example/ndkdemo/NativeLib.java 中实现

package com.example.ndkdemo;public class NativeLib {static {System.loadLibrary("native-lib"); // 加载 native-lib.so 动态库}// native 方法声明,由 C++ 实现public native int add(int a, int b);
}

3.6 步骤六:在 Activity 中调用验证

com/example/ndkdemo/MainActivity.java 中实现

public class MainActivity extends AppCompatActivity {private final NativeLib nativeLib = new NativeLib();private TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView = findViewById(R.id.tv_result);int result = nativeLib.add(3, 4); // 调用 native 方法textView.setText("3 + 4 = " + result);}
}

3.7 步骤七:构建运行

编译运行结果如下所示
在这里插入图片描述

4. 了解 JNI 基本概念,基本数据类型映射,Java 和 C++ 函数签名

接下来我们将针对 JNI 进行相关的学习和了解

4.1 基本数据类型

Java 类型JNI 类型描述
booleanjboolean无符号 8 位(通常为 unsigned char),
值为 JNI_TRUE (1)JNI_FALSE (0)
bytejbyte有符号 8 位
charjchar无符号 16 位
shortjshort有符号 16 位
intjint有符号 32 位
longjlong有符号 64 位
floatjfloat32 位 IEEE 浮点数
doublejdouble64 位 IEEE 浮点数
voidvoid对应 void 类型

4.2 引用类型

Java 类型JNI 类型说明
java.lang.Objectjobject所有对象的基类
任意 Java 类jclassJava 类的引用
java.lang.StringjstringJava 字符串
T[](Java 数组)jarray所有数组的基类
原始类型数组jintArrayjbyteArray特定类型的数组
Java 对象数组jobjectArray包含对象引用的数组
异常jthrowable可被 throw 的对象

4.3 特殊辅助类型

JNI 类型定义用途
jsizetypedef jint jsize;表示数组、字符串长度或大小等
jfieldID不透明指针类型标识一个类的字段
jmethodID不透明指针类型标识一个类的方法

4.4 本地方法接口类型

JNI 提供的所有函数都通过这两个结构体访问:

类型名说明
JNIEnv *每个线程独有,包含 JNI 所有函数指针
JavaVM *JVM 实例指针,用于跨线程附加线程等操作

4.5 布尔常量

为兼容 C 语言布尔类型,定义了:

#define JNI_TRUE  1
#define JNI_FALSE 0

4.6 原始类型数组

Java 类型JNI 类型
boolean[]jbooleanArray
byte[]jbyteArray
char[]jcharArray
short[]jshortArray
int[]jintArray
long[]jlongArray
float[]jfloatArray
double[]jdoubleArray

4.7 对象数组

Java 类型JNI 类型说明
String[]jobjectArray指向一组 jstring 对象的数组
Object[]jobjectArray可存放任意引用类型对象
SomeClass[]jobjectArray存放 SomeClass 对象的数组

5 Java 和 C++ 函数签名

Java 和 C++ 函数签名是函数唯一身份的定义方式,但两者的表现形式和规则存在差异。

5.1 Java 的函数签名

Java 中函数签名包括:函数名 + 参数类型列表(不包括返回值)

public int add(int a, int b) { ... }

Java 中,下面两个方法签名相同,会报错

public void test(int x) { }
public int tes(int x) { } // 编译报错:签名冲突(返回值不算签名)

Java 方法签名示例(包括参数类型):

方法声明签名(方法名 + 参数类型)
void foo(int x)foo(I)
void foo(String s)foo(Ljava/lang/String;)
void foo(int[] arr)foo([I)
void foo(int x, String s)foo(ILjava/lang/String;)

5.2 C++ 的函数签名

C++ 中函数签名包括:函数名 + 参数类型列表(返回值不计入签名)

int add(int a, int b);
double add(int a, int b); // 编译错误:重定义函数(签名冲突)

但和 Java 不同的是,C++ 支持函数重载:C++ 的重载机制在编译和链接层处理得很好,不需要额外区分。但 Java 的重载虽然语法上支持,但在调用 native 方法时,需要开发者显式编码函数签名,这让处理重载略显麻烦。并不是说 Java 不支持重载,而是说 Java 的重载不天然适用于 native binding,需要额外工作。

void print(int x);
void print(double x);

函数签名还包括是否为指针、引用、常量等修饰:

void func(int &x); // 引用
void func(const int x); // const 修饰不同参数,签名不同

5.3 Java 和 C++ 在 JNI 中的函数签名映射

JNI 中为了让 Java 调用 C/C++ 函数,会将 Java 方法 签名映射为 JNI 名字。

public class MyClass {public native void hello(String msg);
}

对应的 C 函数签名为:

JNIEXPORT void JNICALL Java_MyClass_hello(JNIENV *env, jobject obj, jstring msg);

规则如下:

  • 包名和类名中的 . 替换为 _

  • 方法名拼接在类名后

  • 参数类型在 JNI 中通过 jintjstringjbooleanArray 等类型传递

5.4 常见 JNI 签名编码表

Java 类型JNI 类型编码
intI
booleanZ
byteB
charC
shortS
longJ
floatF
doubleD
voidV
ObjectL类名;
int[][I
StringLjava/lang/String;

6. 学习如何传递 Java 字符串、数组到 Native ,反之亦然

6.1 Java 与 Native(C/C++)之间的数据传递总览

类型Java -> NativeNative -> Java
Stringjstringconst char*(使用 GetStringUTFChars创建 jstring(用 NewStringUTF
基本类型数组jintArray, jbyteArray 等 → jint*(使用 GetXxxArrayElementsGetXxxArrayRegion创建数组并填充(用 NewXxxArray + SetXxxArrayRegion
对象数组jobjectArray → 单个元素用 GetObjectArrayElement 访问创建 jobjectArray 并填充每一项
自定义对象传入 jobject,通过 JNI API 访问其字段或方法构造 Java 对象并返回

6.2 Java 字符串与 Native 的相互转换:

  • Java -> Native :获取 C 字符串:

    extern "C" JNIEXPORT void JNICALL
    Java_com_example_hello_NativeLib_print(JNIEnv* env, jobject thiz, jstring jStr) {const char* cStr = (*env).GetStringUTFChars(jStr, 0);printf("收到字符串: %s\n", cStr);(*env).ReleaseStringUTFChars(jStr, cStr); // 一定要释放
    }
    
  • Native -> Java :创建 Java 字符串:

    extern "C" JNIEXPORT jstring JNICALL
    Java_com_example_hello_NativeLib_stringFromJNI(JNIEnv* env,jobject thiz /* this */) {jstring result = (*env).NewStringUTF("你好 MainActivity");return result;
    }
    

6.3 Java 数组与 Native 的相互转换:

  • Java int[] -> Native

    extern "C" JNIEXPORT void JNICALL
    Java_com_example_demo_NativeLib_sum(JNIEnv* env, jobject thiz, jintArray arr) {jsize len = (*env).GetArrayLength(arr);jint* elems = (*env).GetIntArrayElements(arr, NULL);int sum = 0;for (int i = 0; i < len; i++) {sum += elems[i];}printf("总和: %d\n", sum);(*env).ReleaseIntArrayElements(arr, elems, 0); // 0 表示更新 Java 数组
    }
  • Native int[] -> Java int[]

    extern "C" JNIEXPORT jintArray JNICALL
    Java_com_example_demo_NativeLib_getNumbers(JNIEnv *env, jobject) {jint nums[] = {1, 2, 3, 4, 5};jintArray arr = (*env).NewIntArray(5);(*env).SetIntArrayRegion(arr, 0, 5, nums);return arr;
    }
    

在 Native (C/C++) 中使用 printf() 打印日志时,它的输出位置取决于哪个平台运行,在 Android 中 printf() 输出不会自动出现在 Logcat,我们通常看不到它的输出。

为此我们需要使用 __android_log_print

native-lib.cpp 中添加

#include <android/log.h>#define LOG_TAG "NativeLog"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

将在 Native 中使用 print() 的方法替换成 LOGI() 或者 LOGE() 方法,我们就可以在 Logcat 查看日志了
jintArray arr = (*env).NewIntArray(5);
(*env).SetIntArrayRegion(arr, 0, 5, nums);
return arr;
}

在 Native (C/C++) 中使用 `printf()` 打印日志时,它的输出位置取决于哪个平台运行,**在 Android 中 `printf()` 输出不会自动出现在 Logcat**,我们通常看不到它的输出。为此我们需要使用 `__android_log_print`在 `native-lib.cpp` 中添加```cpp
#include <android/log.h>#define LOG_TAG "NativeLog"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

将在 Native 中使用 print() 的方法替换成 LOGI() 或者 LOGE() 方法,我们就可以在 Logcat 查看日志了

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

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

相关文章

宝塔(免费版9.2.0)的docker拉取仓库失败的加速方法

宝塔docker拉取仓库失败 完美加速方法_宝塔docker加速-CSDN博客 版本&#xff1a;免费版 9.2.0 https://docker.1ms.run 其他的试了很多 都不行 最后不要用宝塔的控制面板(很卡)&#xff0c;直接在linux中用命令行&#xff0c;效果就很好了。

文献解读-生境分析亚区域选择+2D_DL+3D_DL-局部晚期食管鳞状细胞癌新辅助化疗免疫治疗反应预测

研究标题&#xff1a;结合亚区域放射组学与多通道二维或三维深度学习模型预测局部晚期食管鳞状细胞癌&#xff08;LA-ESCC&#xff09;患者对新辅助化疗免疫治疗&#xff08;NACI&#xff09;的反应借鉴点&#xff1a;建模思路&#xff08;看流程图理解就够了&#xff09;引言食…

机器学习第四课之决策树

目录 简介 一.决策树算法简介 二. 决策树分类原理 1.ID3算法 1.1 熵值 1.2 信息增益 1.3 案例分析 ​编辑 2.C4.5 2.1 信息增益率 2.2.案例分析 3.CART决策树 3.1基尼值和基尼指数 3.2案例分析 三、决策树剪枝 四、决策树API 五、电信客户流失 六、回归树 七. 回归…

Java面试题和答案大全

一、Java基础知识 1. Java语言特点 题目: 请说明Java语言的主要特点? 答案: 面向对象:Java是纯面向对象的语言,支持封装、继承、多态 平台无关性:一次编译,到处运行(Write Once, Run Anywhere) 简单性:语法简洁,去掉了C++中的指针、多重继承等复杂特性 安全性:提…

用NAS如何远程访问:详细教程与实用技巧

在信息时代&#xff0c;家用NAS&#xff08;网络附加存储&#xff09;成为家庭数据存储和管理的热门设备。它不仅可以作为家庭照片、视频、工作文件的集中存储中心&#xff0c;还支持远程访问&#xff0c;方便用户随时随地获取数据。那么&#xff0c;如何配置和实现家用NAS的远…

Qt-桌面宠物

目录 一&#xff0c;演示&#xff08;部分功能&#xff09; 二&#xff0c;开发环境准备 三&#xff0c;部分代码实现 1.创建基础窗口 2.实现宠物动画 3.添加交互功能 4.系统托盘集成 5.行为模式实现 6.状态管理系统 7.资源打包部署 四&#xff0c;接受定制 一&…

C++编程学习(第19天)

局部变量和全局变量每一个变量都有其有效作用范围&#xff0c;这就是变量的作用域&#xff0c;在作用域以外是不能访问这些变量的。局部变量在一个函数内部定义的变量是局部变量&#xff0c;它只在本函数范围内有效&#xff0c;也就是说只有在本函数内才能使用他们&#xff0c;…

客流特征识别准确率提升 29%:陌讯多模态融合算法在零售场景的实战解析

原创声明本文为原创技术解析文章&#xff0c;涉及的技术参数与架构设计引用自《陌讯技术白皮书》&#xff0c;禁止任何形式的抄袭与转载。一、行业痛点&#xff1a;零售客流识别的技术瓶颈在零售数字化转型过程中&#xff0c;客流特征识别&#xff08;包括性别、年龄分层、停留…

YOLOv8/YOLOv11 C++ OpenCV DNN推理

首先需要将yolov8/yolov11的pt文件转为onnx文件 from ultralytics import YOLO model YOLO("best.pt") model.export(format"onnx",opset11,dynamicFalse) 本次C工具使用vs2017&#xff0c;需要下载OpenCV包&#xff1a;https://opencv.org/releases/&a…

【Mysql】日志--错误日志、二进制日志、查询日志、慢查询日志

错误日志:数据库出现错误时&#xff0c;进行故障排除默认位置&#xff1a;/var/log/mysqld.log查看日志位置show variables like %log_error%查看日志tail -50 /var/log/mysqld.log二进制日志&#xff1a;记录了所有的DDL语句和DML语句&#xff0c;不包含查询&#xff08;selec…

后端常用框架环境与软件详解

一、基础运行环境 1. JDK&#xff08;Java Development Kit&#xff09; 定义&#xff1a;Java 开发工具包&#xff0c;包含编译器、运行时环境&#xff08;JRE&#xff09;及核心类库 作用&#xff1a;提供 Java 程序开发和运行的基础环境&#xff0c;是所有 Java 应用的必备依…

本地服务器端部署基于大模型的通用OCR项目——dots.ocr

本地服务器端部署基于大模型的通用OCR项目——dots.ocrdots.ocr相关介绍本地服务器端部署第一步&#xff1a;安装cuda12.8与CUDNN8.9.7第二步&#xff1a;创建项目所需的依赖环境第三步&#xff1a;启动项目第四步&#xff1a;测试第五步&#xff1a;文本解析相关性测试第六步&…

Text2SQL 智能问答系统开发-spider验证集(三)

概述 已完成 基础 Text2SQL 功能实现 实现用户输入自然语言问题后&#xff0c;系统能够自动生成 SQL 并执行返回结果。用户交互优化 支持用户通过补充信息对查询进行调整&#xff0c;提升易用性。模糊时间处理机制 对“最近”“近期”等模糊时间关键词进行补全或引导&#xf…

ElementUI常用的组件展示

文章目录1、要使用ElementUI先导入组件库2、自定义表头&#xff0c;可以改为添加和批量删除的按钮3、Dialog模态框&#xff0c;主要用于添加和修改时展示信息4、抽屉5、消息提示&#xff1a;用于提示是否操作成功6、询问&#xff1a;常用于询问是否确定删除7、批量选择复选框8、…

在电脑上可以存储文件并合理备份文件的工具用哪个?

每天被群消息、报表、PPT 轮番轰炸的上班族&#xff0c;最怕的不是加班&#xff0c;而是——文件突然失踪&#xff01;别再把“CtrlS”当护身符&#xff0c;今天一口气测完 4 款热门“文件保险箱”&#xff0c;看看谁才真正配得上你的 Deadline。 敬业签 首先登场的是敬业签&am…

JavaWeb(04)

MyBatis 时一款优秀的持久层框架&#xff0c;用于简化JDBC的开发 The MyBatis Blog 目录 MyBatis入门Mybatis基础CRUDMybatis动态SQL Mybatis入门 快速入门 JDBC介绍 数据库连接池 lombok 准备工作(创建springboot工程&#xff0c;数据库表user&#xff0c;实体类User) …

统计学1:伯努利模型的参数估计与等价性分析

伯努利模型的参数估计方法 1. 统计学习方法三要素对比方法模型策略算法极大似然估计概率模型经验风险最小化数值解贝叶斯估计概率模型结构风险最小化解析解2. 极大似然估计 2.1 模型设定 设P(x1)θP(x1)\thetaP(x1)θ&#xff0c;则P(x0)1−θP(x0)1-\thetaP(x0)1−θ 2.2 似然…

游戏行业DDoS攻防实战指南

一、游戏DDoS攻击特征分析游戏行业DDoS攻击呈现高度复合化特征&#xff0c;攻击手段日益专业化。2023年Akamai监测数据显示&#xff0c;63%的游戏服务器攻击采用UDP反射放大&#xff08;如NTP、Memcached协议&#xff09;与HTTP慢速攻击&#xff08;如Slowloris&#xff09;相结…

[自动化Adapt] 录制引擎 | iframe 穿透 | NTP | AIOSQLite | 数据分片

链接&#xff1a;https://github.com/OpenAdaptAI/OpenAdapt/wiki/OpenAdapt-Architecture-(draft) docs&#xff1a;OpenAdapt OpenAdapt 是一个开源项目&#xff0c;旨在 记录 和 回放 用户在计算机上的交互行为。 它如同智能助手般 观察 我们的操作&#xff08;鼠标点击、…

ipv6学习

ipv6的历史背景和及展望ipv6普及不够&#xff0c;ipv4快要用完。ipv6技术部分ivp6包头结构ipv6不允许分片&#xff0c;减轻中间设备压力。IPv6 包头结构可按字段分层解析&#xff0c;核心特点是 固定头部长度&#xff08;40 字节&#xff09; &#xff0c;将可选功能移至扩展头…