当前位置: 首页 > news >正文

Android JNI的理解与使用。

写在前面:Java相对于C/C++来说是更高级的语言,隐藏了指针,可读性更高,更容易学习,但是无法直接操作硬件、运行速度较慢也是不可回避的硬伤。JNI就是Java官方定义的一套标准“接口”,用于Java和C/C++之间互相调用,注意这里是互相调用,而不是只能用Java去调用C/C++。搞Android开发的,JNI是必须掌握的技术,以前我一直逃避,因为觉得比较难,但是真正学习下来并没有那么难。Java运行速度较慢,有一些对时间要求超高的场景,就必须用到JNI。
每一节文末会附上Demo APP的GitHub链接。

  在正式使用JNI之前,我们必须搞清楚两个相关的概念:
  Android NDK:这是Google官方提供的工具包,用于将C/C++代码链接它所需要的库,编译成.so或者.a文件。大白话说就是:没有它就不能在Android Studio 这个应用里面编译C/C++代码。
  JNI:JNI不是包含于Android NDK里面的,两者相互独立,JNI只要是Java代码都能使用,不局限于Android应用开发,很多人容易把两者混为一潭。
  下面开始介绍Android应用中如何使用JNI,以Java调用C/C++这种形式为例子,分两种情况:1、一开始就创建为C/C++项目;2、创建的是一般项目,开发中途想调用C/C++代码。

一、C/C++项目

1.1 创建一个Native C++项目

  在new一个项目的时候,选择Native C++,创建一个名为JNI的项目。
在这里插入图片描述
  创建完之后的项目结构如下:可以看到在java的同级目录,Android Studio已经自动为我们生成了cpp目录,cpp目录下自动生成了cpp文件和CMakeLists文件。
在这里插入图片描述

1.2 CMakeLists.txt 讲解

   CMakeLists的作用就是告诉Android NDK,这个CPP项目的源文件是哪些,需要链接哪些库,需要生成静态库还是动态库。自动生成的 CMakeLists.txt 如下:

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("jni")# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED# List C/C++ source files with relative paths to this CMakeLists.txt.native-lib.cpp)# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}# List libraries link to the target libraryandroidlog)

  cmake_minimum_required:用来说明现在使用的CMake的最低版本。
  project(“jni”):告诉NDK这个CMake的项目名称叫jni,与后续编译关系不大,有点吉祥物的意思。
  add_library:这里其实是分三段来看——》${CMAKE_PROJECT_NAME}是规定最后编译产物的名字,这里直接用了上一步的project name jni,也就是说最后编译出来的库名字为 libjni,当然你也可以取其它任意名字,比如Xusu这样。——》SHARED 告诉NDK最后编译的是动态库,生成的是libjni.so,如果需要生成静态库把SHARED更换为STATIC即可,最后生成 libjni.a文件。——》native-lib.cpp 是告诉NDK 需要编译的源文件,这里可以列出来很多个.cpp文件或者.h头文件,每个文件占一行。
  target_link_libraries:意思就是将NDK中现成的android库、log库链接到生成的libjni库中,链接到 android 库可以让你的 C/C++ 代码调用一些底层的 Android API,链接到 log 库就是为了打印日志嘛。写法也是固定的target_link_libraries(target_name library1 library2 …)。

1.3 native-lib.cpp 讲解

  自动生成的native-lib.cpp如下:

#include <jni.h>
#include <string>extern "C" JNIEXPORT jstring JNICALL
Java_com_htc_jni_MainActivity_stringFromJNI(JNIEnv* env,jobject thiz) {std::string hello = "Hello from C++"; /*声明一个字符串*/return env->NewStringUTF(hello.c_str()); /*将C语言中的字符通过.c_str()和NewStringUTF方法传化为Java中的String返回给Java代码使用*/
}

  这里我们贴出MainActivity中声明的方法一起对照更容易理解:

package com.htc.jni;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.widget.TextView;import com.htc.jni.databinding.ActivityMainBinding;public class MainActivity extends AppCompatActivity {// Used to load the 'jni' library on application startup.static { //固定写法,通过System.loadLibrary加载需要的C/C++动态库System.loadLibrary("jni");}private ActivityMainBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());// Example of a call to a native methodTextView tv = binding.sampleText;tv.setText(stringFromJNI());}/*** A native method that is implemented by the 'jni' native library,* which is packaged with this application.*/public native String stringFromJNI();
}

  可以看到我们在java代码中通过System.loadLibrary(“jni”) 去加载了libjni.so库,这是JNI的固定写法,然后我们用public native String stringFromJNI()声明了一个JNI方法,并通过tv.setText(stringFromJNI())去调用了它,Java声明JNI方法一定要用native修饰,这也是固定写法。
  我们回到native-lib.cpp,开始就是常规的include头文件不用说,从extern "C"开始说:
  extern “C”:是为了确保NDK按照 C 语言的方式来处理函数名称。C++ 和 C 语言在编译时对函数名称的处理方式有所不同,C++ 会进行名称修饰(name mangling),而 C 不会。下面举个例子——》如果没有 extern “C”,C++ 编译器会对函数名进行名称修饰,编译器会把函数名 Java_com_example_myapp_MainActivity_stringFromJNI 转换为一个复杂的符号,比如:_ZN12MainActivity13stringFromJNIEv 。 这就是 C++ 的名称修饰。Java 层的 JNI 机制无法识别这个符号,会导致无法正确调用该方法。
  JNIEXPORT jstring JNICALL:JNIEXPORT 、JNICALL都是固定写法,它们中间夹着的jstring 是函数的返回值,还记得我们之前声明的native函数吗?public native String stringFromJNI() 这里的jstring对应的就是java中的String。JNI为了实现java和C/C++的通信,规定了一套基本数据类型的对照表:
在这里插入图片描述
  Java_com_htc_jni_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz):这个是stringFromJNI对应的JNI函数的名称,Java_是固定前缀,com_htc_jni_MainActivity_stringFromJNI是Java中声明的native方法所在的包名+类名+native方法名的组合,组合的顺序也是固定的。方法参数有两个JNIEnv* env, jobject,这里有同学就会问了,我们Java里声明的native方法明明没有参数,怎么这里有参数??public native String stringFromJNI() 是没参数,所以这里的JNIEnv* env, jobject thiz其实固定写法,JNIEnv* env代表的是Java环境,后续可以通过env创建Java中的数据对象,访问Java中的函数,jobect thiz表示调用这个方法的 Java 对象实例,在这个例子中指的就是MainActivity,如果声明成静态方法 public static native String stringFromJNI() ,jobject就需要更换成 jclass

1.4 总结

  如果从一开始就创建一个Native C++ 项目,Android Studio会帮我们把JNI需要的环境和文件都准备好,我们只需要学习它的语法即可,其中有几个需要关注的点:1、cpp目录和java目录是同级。2、Java代码中通过System.loadLibrary加载so动态库、通过native关键字声明JNI方法。3、cpp文件JNI方法的命名规则和参数规范。

点击下载Native C++项目Demo:https://github.com/xuhao120833/JN

二、一般的项目,中途想使用C/C++代码

  上面我们讲了用Android Studio创建一般的Native C++项目,Android Studio 已经帮我们做好了准备工作,那么我们APP开发到一半,突然加了个需求对时效性要求很高,必须用C/C++实现,这个时候项目肯定不能推倒重来了,那怎么办???按下面的操作即可:

2.1 自己创建cpp目录和相关文件

  选中main目录按右键,在java的同级目录创建cpp目录:
在这里插入图片描述
  自己创建CMakeLists.txt、编写用到的cpp文件和头文件:
在这里插入图片描述
  CMakeLists.txt的文件内容如下:

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.10.2)   #说明CMake的最低要求版本# Declares and names the project.project("Xctouch")  #CMake项目名称取为Xctouch,随意取的# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.add_library( # Sets the name of the library.PxScale     #最后生成的库的名字取为PxScale# Sets the library as a shared library.SHARED      #设置最后生成库为动态库,即.so文件# Provides a relative path to your source file(s).scdefine.h   #列出所有的cpp文件和头文件,每个文件占一行jz_scale.cpptp_savedata_check.cppscJNIfun.cpp)# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.find_library( # Sets the name of the path variable. log-lib    #找到NDK里面现成的log库,并给它取一个别名log-lib# Specifies the name of the NDK library that# you want CMake to locate.log )# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.target_link_libraries( # Specifies the target library.  #将log库链接到最后生成的libPxScale.so库中PxScale# Links the target library to the log library# included in the NDK.${log-lib} )

  scJNIfun.cpp的文件内容如下:定义了三个方法,前两个相对复杂一些,extern 导入了两个其它cpp文件的方法。

#include <jni.h>
#include <string>
#include <android/log.h>
#include <linux/agpgart.h>
#include "scdefine.h"JNIEXPORT///
extern void ratio_tra_point(int *pRet, int *px4, int *py4, int oldRatio, int newRatio, float scale, int w, int h);
extern int check_bd_data(char *pBuf);
xtouch/////该方法的功能是将 Java 层的两个 int[] 数组(px4 和 py4)传递到 C++ 层,经过 ratio_tra_point 函数处理后,返回一个计算结果数组。
extern "C"
JNIEXPORT jintArray JNICALL
Java_com_htc_server_PxScale_getpxRatioxy(JNIEnv *env, jobject thiz, jintArray px4, jintArray py4, jint oldRatio, jint newRatio, jfloat scale, jint w, jint h) {jintArray intArray = env->NewIntArray(20);jint *intdata = env->GetIntArrayElements(intArray, NULL);jint *tpx4 = NULL;jint *tpy4 = NULL;if(px4!=NULL){tpx4 = (jint *) env->GetIntArrayElements(px4, 0);}else{tpx4 = NULL;}if(py4!=NULL){tpy4 = (jint *) env->GetIntArrayElements(py4, 0);}else{tpy4 = NULL;}LOGD("CPP: Java_com_htc_server_PxScale_getpxRatioxy");memset(intdata,0,sizeof(int)*20);ratio_tra_point(intdata, tpx4, tpy4, oldRatio,newRatio, scale, w, h);env->ReleaseIntArrayElements(intArray, intdata, 0);return intArray;
}//Java 层接收一个字符串,将其进行字符过滤(只保留字母和数字),然后调用 check_bd_data 函数对过滤后的数据进行进一步处理,最终返回一个整型结果。
extern "C"
JNIEXPORT jint JNICALL
Java_com_htc_server_PxScale_checkbddata(JNIEnv *env, jobject thiz, jstring data) {jint ret = 0;int i;char c,checkbuf[2048];const char *str;//LOGD("CPP: Java_com_htc_server_PxScale_checkbddata");str = env->GetStringUTFChars(data, NULL);if(str == NULL){return 0;}else{memset(checkbuf, 0, 2048);for(i=0;i<2048;i++){c = str[i];if((c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z')){checkbuf[i] = c;}else{break;}}ret = check_bd_data(checkbuf);}return ret;
}extern "C"
JNIEXPORT jstring JNICALL
Java_com_htc_server_PxScale_sayHello(JNIEnv* env,jobject thiz) {// 创建 C++ 字符串std::string message = "I'm JNI, Hello Java.";// 返回一个 Java 字符串(jstring)return env->NewStringUTF(message.c_str());
}

2.2 java目录下创建PxScale.java文件

在这里插入图片描述

package com.htc.server;public class PxScale {static {System.loadLibrary("PxScale");}/*** A native method that is implemented by the 'duRYXtp' native library,* which is packaged with this application.**/public native int[] getpxRatioxy(int[] px4, int[] py4, int oldRatio, int newRatio, float scale, int w, int h);public native int checkbddata(String data);public native String sayHello();
}

2.3 build.gradle下添加NDK、JNI配置

  我们需要手动指定CMakeLists.txt的位置和版本、ndk的版本、ndk最后编译产物的信息。
  defaultConfig标签范围内添加如下标签信息:

		externalNativeBuild {cmake {cppFlags ""}}ndk {ldLibs "log"moduleName "PxScale"                   //生成的so名字。abiFilters "arm64-v8a", "armeabi-v7a"  //输出指定abi体系结构下的so库。}

  android标签范围内添加如下标签信息:

	externalNativeBuild {cmake {path "src/main/cpp/CMakeLists.txt"  //指定CMakeLists的绝对路径。version "3.10.2"                    //指定CMake的版本。}}ndkVersion '21.0.6113669'                   //指定ndk的版本。

注意:build.gradle中不要打开代码混淆开关,打开会导致Java函数可以找到C/C++函数,但是C/C++函数执行完毕返回值给Java函数的时候找不到Java函数,导致执行报错。解决办法暂时未知。

buildTypes {release {minifyEnabled false  //true混淆打开//zipAlignEnabled true  //优化代码//shrinkResources true  //优化资源proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}

2.4 MainActivity调用JNI测试

  MainActivity的代码如下:

package com.htc.server;import android.os.Bundle;
import android.widget.Toast;import androidx.activity.EdgeToEdge;
import androidx.annotation.Px;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;public class MainActivity extends AppCompatActivity {private PxScale pxScale;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);pxScale = new PxScale();pxScale.checkbddata("zojdoifjaoidj");String hello = pxScale.sayHello();Toast.makeText(this, hello, Toast.LENGTH_SHORT).show();}
}

2.5 总结

  一般项目和NativeC++项目相比,想要使用JNI需要自己去创建cpp目录、cpp文件、CMakeLists.txt等,这些文件的组成语法都是大同小异的,唯一值得注意的就是需要自己在build.gradle中去声明项目JNI的相关信息,不然无法运行。

点击下载 一般项目使用JNI 的 Demo APP:https://github.com/xuhao120833/Server

三、C/C++调用Java方法

  前文举例子一直用的都是Java通过JNI去调用C/C++,那么反过来怎么弄呢???

从C/C++使用JNI调用Java方法分两种情况,一种是调用static静态方法,一种是调用普通方法,下面就分这两种情况分别说。以创建Native C++项目为例子。

3.1 调用静态方法

  首先我们按照上文的方法创建一个Native C++项目,然后创建一个ArrayProcessor类供C/C++代码调用,最后的代码结构如下:
在这里插入图片描述
  在ArrayProcessor中首先定义一个简单的静态方法:

package com.htc.jni2;public class ArrayProcessor {// 静态方法:打印日志public static void printLog(String message) {System.out.println("Log from Java: " + message);}
}

  MainActivity中定义一个native方法callSumArray,并调用它:

package com.htc.jni2;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.widget.TextView;import com.htc.jni2.databinding.ActivityMainBinding;public class MainActivity extends AppCompatActivity {// Used to load the 'jni2' library on application startup.static {System.loadLibrary("jni2");}/*** A native method that is implemented by the 'jni2' native library,* which is packaged with this application.*/public native String stringFromJNI();public native int callSumArray(int[] array);private ActivityMainBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());// Example of a call to a native methodTextView tv = binding.sampleText;tv.setText(stringFromJNI());// 创建一个整数数组int[] array = {1, 2, 3, 4, 5};// 调用 JNI 方法int sum = callSumArray(array);// 打印结果System.out.println("Sum of array: " + sum);}
}

  JNI中对应定义一个callSumArray方法:

#include <jni.h>
#include <string>
#include <android/log.h>#define LOG_TAG "JNI_LOG"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)extern "C" JNIEXPORT jstring JNICALL
Java_com_htc_jni2_MainActivity_stringFromJNI(JNIEnv* env,jobject thiz) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}extern "C" JNIEXPORT jint JNICALL
Java_com_htc_jni2_MainActivity_callSumArray(JNIEnv* env,jobject thiz,jintArray array) {// 获取数组长度jsize length = env->GetArrayLength(array);// 获取数组元素jint *elements = env->GetIntArrayElements(array, 0);// 调用静态方法 printLogjclass arrayProcessorClass = env->FindClass("com/htc/jni2/ArrayProcessor");if (arrayProcessorClass == nullptr) {LOGI("Class not found!");return -1; // 错误返回}jmethodID printLogMethod = env->GetStaticMethodID(arrayProcessorClass, "printLog", "(Ljava/lang/String;)V");if (printLogMethod == nullptr) {LOGI("Method not found!");return -1; // 错误返回}// 调用静态方法 printLogjstring logMessage = env->NewStringUTF("Hello from JNI!");env->CallStaticVoidMethod(arrayProcessorClass, printLogMethod, logMessage). . . . . .
}

  要想在C/C++代码中调用Java的静态方法,总共分如下三步:
  1、通过env->FindClass找到指定路径的Java类。
  2、通过env->GetStaticMethodID指定Java类中的静态方法的jmethodID 。
  解释一下函数中的参数意义:GetStaticMethodID(arrayProcessorClass, “printLog”, “(Ljava/lang/String;)V”),arrayProcessorClass是第一步找到的jclass——》 “printLog"是Java中对应函数的名字。——》”(Ljava/lang/String;)V") 对应方法的签名,说人话就是告诉JNI,要使用的Java函数的参数是什么?返回类型是什么?(Ljava/lang/String;)指printLog的参数是String,V指printLog的返回类型是void。对照表如下:
在这里插入图片描述
在这里插入图片描述
   3、通过env->CallStaticVoidMethod调用Java中的printLog函数。有一个参数就是env->CallStaticVoidMethod(arrayProcessorClass, printLogMethod, logMessage),多个参数就是env->CallStaticVoidMethod(arrayProcessorClass, printLogMethod, 参数1,参数2,参数3. . . . . .)

  到这里,C/C++如何调用静态Java方法就说完了。

3.2 调用普通实例方法

  我们在ArrayProcessor中再定义两个普通方法:

	// 非静态方法:计算数组的和public int sumArray(int[] array) {int sum = 0;for (int num : array) {sum += num;}return sum;}// 非静态方法:打印数组public void printArray(int[] array) {System.out.print("Array: ");for (int num : array) {System.out.print(num + " ");}System.out.println();}

  JNI cpp文件中,如下调用:

    // 获取数组长度jsize length = env->GetArrayLength(array);// 获取数组元素jint *elements = env->GetIntArrayElements(array, 0);// 获取构造函数 IDjmethodID constructor = env->GetMethodID(arrayProcessorClass, "<init>", "()V");if (constructor == nullptr) {LOGI("Constructor not found!");return -1; // 错误返回}// 创建 ArrayProcessor 类的实例对象jobject arrayProcessorObject = env->NewObject(arrayProcessorClass, constructor);// 获取实例方法 sumArray 的 IDjmethodID sumArrayMethod = env->GetMethodID(arrayProcessorClass, "sumArray", "([I)I");if (sumArrayMethod == nullptr) {LOGI("sumArray method not found!");return -1; // 错误返回}// 调用实例方法 sumArrayjint result = env->CallIntMethod(arrayProcessorObject, sumArrayMethod, array);// 调用实例方法 printArrayjmethodID printArrayMethod = env->GetMethodID(arrayProcessorClass, "printArray", "([I)V");if (printArrayMethod == nullptr) {LOGI("printArray method not found!");return -1; // 错误返回}env->CallVoidMethod(arrayProcessorObject, printArrayMethod, array);// 释放数组元素env->ReleaseIntArrayElements(array, elements, 0);return result;

  1、我们都知道实例方法需要实例对象才能调用,所以第一步就是要构建ArrayProcessor的实例对象:通过env->GetMethodID(arrayProcessorClass, init , “()V”);找到ArrayProcessor的构造方法, init JNI中固定代表构造方法,"()V"就是说构造函数没有参数,返回类型为void,也就是Java中默认的构造函数。
  2、通过 env->NewObject(arrayProcessorClass, constructor);获取到ArrayProcessor的实例对象。 JNI中用jobject 表示。
  3、通过env->GetMethodID分别获取到sumArray和printArray的jmethodID。
  4、通过CallIntMethod、CallVoidMethod调用Java中的静态方法。
  最后把我们创建的指针释放掉env->ReleaseIntArrayElements。第四步中用的CallIntMethod、CallVoidMethod是根据Java函数的返回类型来的,有一个对照表如下:
在这里插入图片描述

3.3 总结

  C/C++使用JNI调用Java方法,过程是相似的:1、静态方法——》先找到类然后调用。2、普通方法——》先创建Java实例对象,再通过Java对象调用。调用方法前都需要找到 jmethodID,流程都是固定的,重点在于正确使用JNI规定的语法。

点击下载C/C++通过JNI调用Java代码的Demo APP https://github.com/xuhao120833/JNI2

写在最后:

  通篇看下来其实JNI没有想象中那么难,更多的是学习现有的规则和语法,然后再正确使用即可。生活中很多事也是这样,我们大多数时候在自己吓自己。不管你怕不怕,生活最重要的是勇敢出发。

相关文章:

Android JNI的理解与使用。

写在前面&#xff1a;Java相对于C/C来说是更高级的语言&#xff0c;隐藏了指针&#xff0c;可读性更高&#xff0c;更容易学习&#xff0c;但是无法直接操作硬件、运行速度较慢也是不可回避的硬伤。JNI就是Java官方定义的一套标准“接口”&#xff0c;用于Java和C/C之间互相调用…...

fpga助教面试题

第一题 module sfp_pwm( input wire clk, //clk is 200M input wire rst_n, input wire clk_10M_i, input wire PPS_i, output reg pwm ) reg [6:0] cunt ;always (posedge clk ) beginif(!rst_n)cunt<0;else if(cunt19) //200M是10M的20倍cunt<0;elsecunt<cunt1;…...

Git命令详解与工作流介绍:全面掌握版本控制系统的操作指南

Git Git是一个版本控制系统&#xff08;也称为源代码控制系统&#xff09;&#xff0c;允许程序员和其他处理文本文件的人在独立工作时协调更改。Git还支持二进制资产&#xff0c;如图片&#xff0c;但这些格式不支持逐行版本管理&#xff0c;这使得版本控制真正强大。 Git概…...

提升信息检索准确性和效率的搜索技巧

一、基础技巧 精准关键词 避免长句子&#xff0c;提取核心关键词&#xff08;如用“光合作用 步骤”代替“请告诉我光合作用的具体过程”&#xff09;。 同义词替换&#xff1a;尝试不同表达&#xff08;如“AI 发展史” vs “人工智能 历史”&#xff09;。 排除干扰词 使用…...

Qt 中使用 ffmpeg 获取采集卡数据录制视频

作者&#xff1a;billy 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 前言 之前做了一个功能&#xff0c;从采集卡获取数据然后录制成视频&#xff0c;结果发现录制的视频内存占用非常大&#xff0c;1分钟的…...

Python爬虫TLS

TLS指纹校验原理和绕过 浏览器可以正常访问&#xff0c;但是用requests发送请求失败。 后端是如何监测得呢&#xff1f;为什么浏览器可以返回结果&#xff0c;而requests模块不行呢&#xff1f; https://cn.investing.com/equities/amazon-com-inc-historical-data 1.指纹校…...

【Linux AnolisOS】配置Linux固定ip地址。然后在Windows上连接使用linux中docker容器里的redis和nacos。

1.关于将虚拟机ip地址更改为静态地址 &#xff0c;跟着下面这个视频搞的&#xff0c;不想看文章的可以看视频。 第四章-07-配置Linux固定IP地址哔哩哔哩bilibili 当用的centos9 视频里让我们打开网络配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33 但是我打开时…...

IDEA中查询Maven项目的依赖树

在Maven项目中&#xff0c;查看项目的依赖树是一个常见的需求&#xff0c;特别是当你需要了解项目中直接或间接依赖了哪些库及其版本时。你可以通过命令行使用Maven的dependency:tree插件来做到这一点。这个命令会列出项目中所有依赖的树状结构。 打开idea项目的终端&#xff…...

【Ubuntu】GPU显存被占用,但显示没有使用GPU的进程

文章目录 一、问题描述二、解决方案2.1 寻找问题进程2.2 尝试杀死相关进程2.3 投放核弹&#xff0c;一键全杀2.4 再次查看GPU使用情况 参考资料 一、问题描述 今天使用服务器的时候发现gpu被占了很多内存&#xff0c;但是使用 nvidia-smi 命令并没有发现占这么多显存的进程&am…...

【并发编程】Java并发编程核心包

1、简介 java.util.concurrent 是 Java 并发编程的核心包&#xff0c;提供了丰富的工具和框架来支持多线程编程、并发任务执行、线程安全集合、同步机制等。 2、线程池Thread Pool 线程池是并发编程中最重要的工具之一&#xff0c;用于管理和复用线程&#xff0c;避免频繁创…...

Unity 淡入淡出

淡入&#xff08;Fade in&#xff09;&#xff1a;类似打开幕布 淡出&#xff08;Fade out&#xff09;&#xff1a;类似关上幕布 方案一 使用Dotween&#xff08;推荐&#xff09; using DG.Tweening; using UnityEngine; using UnityEngine.UI;public class Test : MonoB…...

完整的 LoRA 模型训练步骤:如何使用 Kohya_ss 进行 LoRA 训练

完整的 LoRA 模型训练步骤&#xff1a;如何使用 Kohya_ss 进行 LoRA 训练 一、环境配置1. 安装 Python 和虚拟环境2. 克隆 Kohya_ss 仓库3. 安装依赖4. 启动 GUI lora训练1. 准备数据 图片处理打标签2. 配置 LoRA 训练2.2 配置图片文件夹和输出目录 训练解决方法&#xff1a; 使…...

视觉分析之边缘检测算法

9.1 Roberts算子 Roberts算子又称为交叉微分算法&#xff0c;是基于交叉差分的梯度算法&#xff0c;通过局部差分计算检测边缘线条。 常用来处理具有陡峭的低噪声图像&#xff0c;当图像边缘接近于正45度或负45度时&#xff0c;该算法处理效果更理想。 其缺点是对边缘的定位…...

git输错用户名或者密码

git push时候跳出window弹窗&#xff0c;输入用户名和密码&#xff0c;如果错误&#xff0c;会有如下情况&#xff1a; $ git push -u origin “master” remote: [session-6c466aa6] rain: Incorrect username or password (access token) fatal: Authentication failed for ‘…...

【Unity Shader编程】之图元装配与光栅化

执行方式&#xff1a;自动完成 图元装配自动化流程 顶点坐标存入装配区 → 按绘制模式连接顶点 → 生成完整几何图元 示例&#xff1a;gl.drawArrays(gl.TRIANGLES, 0, 3)自动生成三角形 会自动自动裁剪超出屏幕范围&#xff08;NDC空间外&#xff09;的三角形&#xff0c;仅保…...

以ChatGPT为例解析大模型背后的技术

目录 1、大模型分类 2、为什么自然语言处理可计算&#xff1f; 2.1、One-hot分类编码&#xff08;传统词表示方法&#xff09; 2.2、词向量 3、Transformer架构 3.1、何为注意力机制&#xff1f; 3.2、注意力机制在 Transformer 模型中有何意义&#xff1f; 3.3、位置编…...

网页版的俄罗斯方块

1、新建一个txt文件 2、打开后将代码复制进去保存 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>俄…...

Linux运维_Dockerfile_打包Moby-26.1.4编译dockerd环境

Linux运维_Dockerfile_打包Moby-26.1.4编译dockerd环境 Dockerfile 是一个文本文件, 包含了构建 Docker 镜像的所有指令。 Dockerfile 是一个用来构建镜像的文本文件, 文本内容包含了一条条构建镜像所需的指令和说明。 通过定义一系列命令和参数, Dockerfile 指导 Docker 构…...

数据中心储能蓄电池状态监测管理系统 组成架构介绍

安科瑞刘鸿鹏 摘要 随着数据中心对供电可靠性要求的提高&#xff0c;蓄电池储能系统成为关键的后备电源。本文探讨了蓄电池监测系统在数据中心储能系统中的重要性&#xff0c;分析了ABAT系列蓄电池在线监测系统的功能、技术特点及其应用优势。通过蓄电池监测系统的实施&#…...

layui.table.exportFile 导出数据并清除单元格中的空格

Layui在执行数据导出的时候&#xff0c;会出现部分数据单元格中有空格的情况,下面的方法可以去除掉单元格中的空格,供大家参考&#xff01;&#xff01; function table_export(id,title) {//根据传入tableID获取表头var headers $("div[lay-id" id "] .layu…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...