NDK开发
NDK介绍
- app为什么会把代码放到so中
a) C语言历史悠久,有很多现成的代码可用
b) C代码执行效率比Java高
c) Java代码很容易被反编译,而且反编译以后的逻辑很清晰
- 为什么要学习NDK开发
在安卓的so开发中,其他基本与C/C++开发一致,而与Java交互需要用到jni
在本部分的NDK开发讲解中,主要就是介绍jni相关内容
so中会接触的:系统库函数、jni调用、加密算法、魔改算法、系统调用、自定义算法
- 什么是JNI
jni是Java Native Interface的缩写。从Java1.1开始,jni标准成为Java平台的一部分,允许Java代码和其他语言写的代码进行交互
- 什么是NDK
交叉编译工具链
NDK的配置
NDK、CMake、LLDB的作用 https://developer.android.com/ndk/guides
- ABI与指令集
https://developer.android.com/ndk/guides/abis
NDK与Java工程的区别
-
Java代码中加载so和声明所需使用的so中的函数
-
编写CMakeLists.txt和C文件
-
build.gradle中添加一些代码
defaultConfig {
......
externalNativeBuild {cmake {cppFlags "-std=c++11"}
}
ndk {abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
externalNativeBuild {
cmake {path "src/main/cpp/CMakeLists.txt"version "3.10.2"
}
}
第一个NDK工程
- CMakeLists介绍
指明文件源代码编译成哪个so、指明依赖库
2.so的加载
static {System.loadLibrary("native-lib");}
3.native函数的声明
public native String stringFromJNI();
- JNI函数的静态注册规则
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}
5.JNIEnv、jobject/jclass
so层对接java层需要的参数
- NewstringUTF
将c字符串转换为java字符串
关键代码定位点、系统函数
-
在NDK开发中,一定要注意哪些是Java的类型,哪些是C/C++的类型,在适当的时候需要转换
-
extern “C” JNIEXPORT jstring JNICALL
使用c语言方式编译、JNIEXPORT是否出现在导出表、jstring返回值
- 指定只编译arm64的so
ndk(){adiFilters 'arm64-v8a'
}
- 指定编译后的so名字
CMakeLists.txt中修改
so中常用的Log输出
#include <android/log.h>#define TAG "xxxxxx"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
#include <jni.h>
#include <string>
#include <android/log.h>#define TAG "xxxxxx"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from C++";LOGD("xxxxxx%d",100);return env->NewStringUTF(hello.c_str());
}
#define中的…和__VA_ARGS__
可变参数
NDK多线程
JavaVM每个进程中只有一份
JNIEnv每个线程中都有一份
为了更好的演示,所以先简单介绍一下多线程//线程id,其实就是long
pthread_t thread;
//线程id 线程属性 函数 传给函数的参数
pthread_create(&thread, nullptr, myThread, nullptr);
//等待线程执行完毕
//默认的线程属性是joinable 随着主线程结束而结束
//线程属性是dettach,可以分离执行
pthread_join(thread, nullptr);
//子线程中使用它来退出线程
pthread_exit(0);传递int参数
传递多个参数 结构体 数组
接收返回值
JNI_OnLoad
- so中各种函数的执行时机
init、init_array、JNI_OnLoad
- JNI_OnLoad的定义
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {JNIEnv *env = nullptr;if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {LOGD("GetEnv failed");return -1;}return JNI_VERSION_1_6;
}
- 注意事项
一个so中可以不定义JNI_OnLoad
一旦定义了JNI_OnLoad,在so被加载的时候会自动执行
必须返回JNI版本 JNI_VERSION_1_6
JavaVM
- JavaVM是什么
JavaVM结构体介绍
C和C++中的区别
JavaVM中的常用方法
GetEnv
AttachCurrentThread - JavaVM的获取方式
JNI_OnLoad的第一个参数
JNI_OnUnload的第一个参数
env->GetJavaVM
对比各种方式获取的JavaVM指针是否一致
%p打印地址
JNIEnv
- JNIEnv是什么
JNIEnv结构体介绍
C和C++中的区别
JNIEnv中的常用方法后续详细介绍 - JNIEnv的获取方式
函数静态/动态注册,传的第一个参数
vm->GetEnv
globalVM->AttachCurrentThread
对比各种方式获取的JNIEnv指针是否一致
%p打印地址
#include <jni.h>
#include <string>
#include <android/log.h>
#include <pthread.h>#define TAG "xxxx"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "xiaojianbang", __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);JavaVM* globalVM;void myThread(){// JNIEnv *env = nullptr;
// if (globalVM->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
// LOGD("GetEnv failed");
// }JNIEnv *env = nullptr;if (globalVM->AttachCurrentThread((JNIEnv **) &env, nullptr) != JNI_OK) {LOGD("GetEnv failed");}LOGD("myThread JNIEnv %p", env);LOGD("this is from myThread");
}extern "C" JNIEXPORT jstring JNICALL
Java_com_example_javaandso_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";LOGD("stringFromJNI JNIEnv %p", env);//__android_log_print(ANDROID_LOG_DEBUG, "xiaojianbang", "xxxxxx jni native %d %d", 100, 200);LOGD("xxxxxx jni native %d %d", 100, 200);pthread_t thread;// int pthread_create(pthread_t* __pthread_ptr, pthread_attr_t const* __attr, void* (*__start_routine)(void*), void*);pthread_create(&thread, nullptr, reinterpret_cast<void *(*)(void *)>(myThread), nullptr);pthread_join(thread, nullptr);return env->NewStringUTF(hello.c_str());
}JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {LOGD("this is form JNI_OnLoad");JNIEnv *env = nullptr;if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {LOGD("GetEnv failed");return -1;}LOGD("JavaVM %p", vm);env->GetJavaVM(&globalVM);LOGD("JavaVM %p", globalVM);LOGD("JNI_OnLoad JNIEnv %p", env);return JNI_VERSION_1_6;
}
so相关的几个概念
- 导出表、导入表是什么
- 出现在导出表、导入表里面的函数,一般可以通过frida相关API直接得到函数地址,也可以自己计算函数地址
- 没有出现在导出表、导入表、符号表里面的函数,都需要自己计算函数地址
- 要完成so层的hook,都需要得到一个地址
- Java层中的native函数,被调用后会找到so中对应的函数。简单的说,就是Java调用C需要先完成函数注册,函数注册分为静态注册、动态注册
so函数注册
- JNI函数的静态注册
必须遵循一定的命名规则,一般是Java_包名_类名_方法名
系统会通过dlopen加载对应的so,通过dlsym来获取指定名字的函数地址,然后调用
静态注册的jni函数,必然在导出表里 - JNI函数的动态注册
通过env->RegisterNatives注册函数,通常在JNI_OnLoad中注册
JNINativeMethod
函数签名
可以给同一个Java函数注册多个native函数,以最后一次为准
jclass MainActivityClazz = env->FindClass("com/xxxx/ndk/NDKMain");
JNINativeMethod methods[] = {//public native String encode(int i, String str, byte[] byt);{"encode", "(ILjava/lang/String;[B)Ljava/lang/String;", (void *)encodeFromC},
};
env->RegisterNatives(MainActivityClazz, methods, sizeof(methods)/sizeof(JNINativeMethod));
#include <jni.h>
#include <string>
#include <android/log.h>
#include <pthread.h>
#include <dlfcn.h>#define TAG "xiaojianbang"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "xiaojianbang", __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);JavaVM* globalVM;
void test();
extern "C" void fromSoB();void myThread(){// JNIEnv *env = nullptr;
// if (globalVM->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
// LOGD("GetEnv failed");
// }JNIEnv *env = nullptr;if (globalVM->AttachCurrentThread((JNIEnv **) &env, nullptr) != JNI_OK) {LOGD("GetEnv failed");}LOGD("myThread JNIEnv %p", env);LOGD("this is from myThread");
}extern "C" JNIEXPORT jstring JNICALL
Java_com_example_javaandso_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */, jstring path) {std::string hello = "Hello from C++";LOGD("stringFromJNI JNIEnv %p", env);//__android_log_print(ANDROID_LOG_DEBUG, "xiaojianbang", "xxxxxx jni native %d %d", 100, 200);LOGD("xxxxxx jni native %d %d", 100, 200);pthread_t thread;// int pthread_create(pthread_t* __pthread_ptr, pthread_attr_t const* __attr, void* (*__start_routine)(void*), void*);pthread_create(&thread, nullptr, reinterpret_cast<void *(*)(void *)>(myThread), nullptr);pthread_join(thread, nullptr);const char* cPath = env->GetStringUTFChars(path, nullptr);LOGD("cPath: %s", cPath);void *soinfo = dlopen(cPath, RTLD_NOW);void (*ref)();//ref = reinterpret_cast<void (*)()> (dlsym(soinfo, "_Z7fromSoBv"));ref = reinterpret_cast<void (*)()> (dlsym(soinfo, "fromSoB"));if (ref == nullptr) {LOGD("ref is null");}ref();fromSoB();dlclose(soinfo);return env->NewStringUTF(hello.c_str());
}jstring xxxx(JNIEnv* env, jobject obj, jstring a, jint b, jbyteArray c) {return env->NewStringUTF("xxxx from jni");
}jstring xxxx1(JNIEnv* env, jobject obj, jstring a, jint b, jbyteArray c) {test();return env->NewStringUTF("xxxx1 from jni");
}JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {LOGD("this is form JNI_OnLoad");JNIEnv *env = nullptr;if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {LOGD("GetEnv failed");return -1;}LOGD("JavaVM %p", vm);env->GetJavaVM(&globalVM);LOGD("JavaVM %p", globalVM);LOGD("JNI_OnLoad JNIEnv %p", env);jclass MainActivityClazz = env->FindClass("com/example/javaandso/MainActivity");JNINativeMethod methods[] = {{"stringFromJNI2", "(Ljava/lang/String;I[B)Ljava/lang/String;", (void *)xxxx},{"stringFromJNI2", "(Ljava/lang/String;I[B)Ljava/lang/String;", (void *)xxxx1},};
// jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
// jint nMethods)env->RegisterNatives(MainActivityClazz, methods, sizeof(methods)/sizeof(JNINativeMethod));return JNI_VERSION_1_6;
}
so之间的互相调用
- 多个cpp文件编译成一个so
CMake.txt文件中 添加cpp文件
add_library( # Sets the name of the library.xiaojianbang# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).xiaojianbang.cppxxx.cpp)
- 编译多个so
编写多个cpp文件
修改CMakeLists.txt
Java静态代码块加载多个so
dd_library( # Sets the name of the library.xxxxA# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).xxxx.cppmain.cpp)add_library( # Sets the name of the library.xxxxB# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).demo.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# 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.xxxxA# Links the target library to the log library# included in the NDK.${log-lib} )target_link_libraries( # Specifies the target library.xxxxB# Links the target library to the log library# included in the NDK.${log-lib} )
- so之间的相互调用
2.1 使用dlopen、dlsym、dlclose获取函数地址,然后调用。需要导入dlfcn.h
void soinfo = dlopen(nativePath, RTLD_NOW);
void (def)(char str) = nullptr;
def = reinterpret_cast<void ()(char *)>(dlsym(soinfo, “_Z7fromSoBPc”));
def(“”);
2.2 extern 函数声明
通过jni创建Java对象
1. NewObject创建对象
jclass clazz = env->FindClass("com/xxxx/ndk/NDKDemo");
jmethodID methodID = env->GetMethodID(clazz, "<init>", "()V");
jobject ReflectDemoObj = env->NewObject(clazz, methodID);
LOGD("ReflectDemoObj %p", ReflectDemoObj);2. AllocObject创建对象
jclass clazz = env->FindClass("com/xxxx/ndk/NDKDemo");
jmethodID methodID2 = env->GetMethodID(clazz, "<init>", "(Ljava/lang/String;I)V");
jobject ReflectDemoObj2 = env->AllocObject(clazz);
jstring jstr = env->NewStringUTF("from jni str");
env->CallNonvirtualVoidMethod(ReflectDemoObj2, clazz, methodID2, jstr, 100);
通过jni访问Java属性
- 获取静态字段
jfieldID privateStaticStringField = env->GetStaticFieldID(clazz, "privateStaticStringField", "Ljava/lang/String;");
jstring privateStaticString = static_cast<jstring>(env->GetStaticObjectField(clazz, privateStaticStringField));
const char* privatecstr = env->GetStringUTFChars(privateStaticString, nullptr);
LOGD("privateStaticString: %s", privatecstr);
env->ReleaseStringUTFChars(privateStaticString, privatecstr);
- 获取对象字段
jfieldID publicStringField = env->GetFieldID(clazz, "publicStringField", "Ljava/lang/String;");
jstring publicString = static_cast<jstring>(env->GetObjectField(ReflectDemoObj, publicStringField));
const char* publiccstr = env->GetStringUTFChars(publicString, nullptr);
LOGD("publicStringField: %s", publiccstr);
env->ReleaseStringUTFChars(publicString, publiccstr);
- 设置字段
env->SetObjectField(ndkobj, privateStringFieldID, env->NewStringUTF("xxxx"));
通过jni访问Java数组
- 获取数组字段ID
jfieldID byteArrayID = env->GetFieldID(clazz, "byteArray", "[B");
jbyteArray byteArray = static_cast<jbyteArray>(env->GetObjectField(ReflectDemoObj, byteArrayID));
int _byteArrayLength = env->GetArrayLength(byteArray);
jbyte* CBytes = env->GetByteArrayElements(byteArray,nullptr);
for(int i=0;i< _byteArrayLength;i++)
{LOGD(CBytes[i]);
}
- 修改数组字段
char javaByte[10];
for(int i = 0; i < 10; i++){javaByte[i] = static_cast<char>(100 - i);
}
const jbyte *java_array = reinterpret_cast<const jbyte *>(javaByte);
env->SetByteArrayRegion(byteArray, 0, _byteArrayLength, java_array);
- 获取数组字段
byteArray = static_cast<jbyteArray>(env->GetObjectField(ReflectDemoObj, byteArrayID));
_byteArrayLength = env->GetArrayLength(byteArray);
char* str = reinterpret_cast<char *>(env->GetByteArrayElements(byteArray, nullptr));
for(int i = 0; i< _byteArrayLength; i++){LOGD("str[%d]=%d", i, str[i]);
}
env->ReleaseByteArrayElements(jbyteArray, reinterpret_cast<jbyte *>(cbyteArray), 0);
通过jni访问Java方法
- 调用静态函数
jclass ReflectDemoClazz = env->FindClass("com/xiaojianbang/ndk/NDKDemo");
jmethodID publicStaticFuncID = env->GetStaticMethodID(ReflectDemoClazz, "publicStaticFunc", "()V");
env->CallStaticVoidMethod(ReflectDemoClazz, publicStaticFuncID);
- 调用对象函数
jmethodID privateFuncID = env->GetMethodID(ReflectDemoClazz, "privateFunc", "(Ljava/lang/String;I)Ljava/lang/String;");
jmethodID ReflectDemoInit = env->GetMethodID(ReflectDemoClazz, "<init>", "(Ljava/lang/String;)V");
jstring str1 = env->NewStringUTF("this is from NDK");
jobject ReflectDemoObj = env->NewObject(ReflectDemoClazz, ReflectDemoInit, str1);
jstring str2 = env->NewStringUTF("this is from JNI");
jstring retval = static_cast<jstring>(env->CallObjectMethod(ReflectDemoObj, privateFuncID, str2, 1000));
- CallObjectMethodA的使用
jvalue args[2];
args[0].l = str2;
args[1].i = 1000;
jstring retval = static_cast<jstring>(env->CallObjectMethodA(ReflectDemoObj, privateFuncID, args));
const char* cpp_retval = env->GetStringUTFChars(retval, nullptr);
LOGD("cpp_retval: %s", cpp_retval);
env->ReleaseStringUTFChars(retval, cpp_retval);
通过jni访问Java父类方法
//super.onCreate(savedInstanceState);
jclass AppCompatActivityClazz = env->FindClass("androidx/appcompat/app/AppCompatActivity");
jmethodID onCreateID = env->GetMethodID(AppCompatActivityClazz, "onCreate", "(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(thiz, AppCompatActivityClazz, onCreateID, saved_instance_state);
内存管理
- 局部引用
大多数的jni函数,调用以后返回的结果都是局部引用
因此,env->NewLocalRef 基本不用
一个函数内的局部引用数量是有限制的,在早期的安卓系统中,体现的更为明显
当函数体内需要大量使用局部引用时,比如大循环中,最好及时删除不用的局部引用
可以使用 env->DeleteLocalRef 来删除局部引用 - 局部引用相关的其他函数
env->EnsureLocalCapacity(num) 判断是否有足够的局部引用可以使用,足够则返回0
需大量使用局部引用时,手动删除太过麻烦,可使用以下两个函数来批量管理局部引用
env->PushLocalFrame(num)
env->PopLocalFrame(nullptr) - 全局引用
在jni开发中,需要跨函数使用变量时,直接定义全局变量是没用的
需要使用以下两个方法,来创建和删除全局引用
env->NewGlobalRef
env->DeleteGlobalRef - 弱全局引用
与全局引用基本相同,区别是弱全局引用有可能会被回收
env->NewWeakGlobalRef
env->DeleteWeakGlobalRef
子线程中获取Java类
- 在子线程中,findClass可以直接获取系统类
- 在主线程中获取类,使用全局引用来传递到子线程中
- 在主线程中获取正确的ClassLoader,在子线程中去加载类
3.1 在Java中,可以先获取类字节码,然后使用getClassLoader()来获取
Demo.class.getClassLoader()
new Demo().getClass().getClassLoader()
Class.forName(…).getClassLoader()
3.2 在jni的主线程中获取ClassLoader
3.3 在jni的子线程中loadClass
init与initarray
- so在执行JNI_Onload之前,还会执行两个构造函数init、initarray
- so加固、so中字符串加密等等,一般会把相关代码放到这里
- init的使用
extern “C” void _init(){ //函数名必须为_init
…
} - initarray的使用
attribute ((constructor)) void initArrayTest1(){ ... }
attribute ((constructor(200))) void initArrayTest2(){ ... }
attribute ((constructor(101))) void initArrayTest3(){ ... }
attribute ((constructor, visibility("hidden"))) void initArrayTest4(){ ... }
constructor后面的值,较小的先执行,最好从100以后开始用
如果constructor后面没有跟值,那么按定义的顺序,从上往下执行
visibility(“hidden”)隐藏函数名
相关文章:
NDK开发
NDK介绍 app为什么会把代码放到so中 a) C语言历史悠久,有很多现成的代码可用 b) C代码执行效率比Java高 c) Java代码很容易被反编译,而且反编译以后的逻辑很清晰 为什么要学习NDK开发 在安卓的so开发中,其他基本与C/C开发一致ÿ…...
docker overlay 占用空间太大,迁移到 /data/
将 Docker 的 overlay 存储驱动迁移到 /data/ 目录下,可以通过以下步骤完成: 1. 停止 Docker 服务 首先,停止 Docker 服务以确保没有容器在运行,并且数据不会被写入到当前的存储位置。 sudo systemctl stop docker2. 备份现有数…...
Windows性能监控与调优:让电脑运行如飞
一、性能监控 1. 使用任务管理器深入监控 打开任务管理器 我们可以通过按下Ctrl Shift Esc快捷键来打开任务管理器。 或者右键点击任务栏空白处,选择“任务管理器”。 查看性能 在任务管理器中,点击“性能”标签页。 我们可以看到“概览”标签&#x…...
前端响应式布局
1.什么是响应式布局? 响应式布局是一种使网页在不同设备(如手机、平板和桌面)上均能良好显示的设计理念。 2.响应式布局的原理? 通过灵活的网格布局、CSS 媒体查询和弹性单位等技术,实现内容自适应屏幕尺寸变化。 3.响…...
力扣MySQL 1581
先把两张表连接,amount为null 的正是我们需要的,再按customer_id聚合 select Visits.visit_id,customer_id ,Transactions.visit_id ,transaction_id ,amount from Visits left join Transactions on Visits.visit_idTransactions.visit_id 正确代码&…...
就是这个样的粗爆,手搓一个计算器:科学计算器
作为程序员,没有合适的工具,就得手搓一个,PC端,移动端均可适用。废话不多说,直接上代码。 HTML: <div class"calculator"><div class"display-wrapper"><div class"display…...
wordpress使用popup弹窗插件的对比
您在寻找最好的 WordPress 弹出插件吗?大多数网站利用某种形状或形式的弹出窗口来将访问者指向他们希望他们去的地方。例如,这可能用于结帐、电子邮件订阅或用于生成潜在客户。 表现 弹出插件会减慢您的网站速度。当插件使用 WordPress 跟踪弹出窗口的…...
开源OpenStack
1.查询HCS基于OpenStack哪个版本开发 2.九大核心组件 OpenStack可以对接FC也可以对接KVM主机;(OpenStack 对接华为FusionCompute,一个集群对应 openstack 一台计算主机)-引申出nova compute 2.1nova nova两个核心组件nova contro…...
基于Spring Boot+vue技术的导游系统设计与实现
论文下载【免费】基于SpringBootvue技术的导游系统设计与实现资源-CSDN文库 摘 要 本研究背景主要聚焦于当前旅游业信息化、智能化的发展趋势。随着移动互联网的普及和人们出行方式的多样化,导游系统作为旅游服务的重要组成部分,亟需进行技术革新以提…...
软件测试 —— 灰度测试及测试流程!
软件测试中的灰度测试是一种结合了黑盒测试和白盒测试特点的测试方法,旨在通过逐步扩大测试范围来评估新系统或新功能在真实环境中的性能和稳定性。灰度测试是软件开发过程中的一个重要环节,它有助于在全面发布前发现并修复潜在问题,同时收集…...
中科星图GVE(案例)——AI实现光伏面板提取
目录 简介 函数 gve.Services.AI.solarExtraction(image) 代码 结果 知识星球 机器学习 简介 光伏面板提取是一种将光伏面板从图像或视频中准确地分割出来的任务,可以通过使用深度学习算法来实现。 以下是一种基于深度学习的光伏面板提取的实现步骤&#x…...
一种压缩QRCode矩阵以用于存储的方法
通常QRCode由服务器生成,以图片格式发送到客户端,由客户端直接展示,也可以由客户端使用javascript或其他内置的SDK直接生成。 0、需求 QRCode生成过程中往往是先生成矩阵,然后使用矩阵生成图片,矩阵就是由01组成的一…...
鸿蒙HarmonyOS开发:系统服务
拨打电话 call.makeCall 跳转到拨号界面,并显示待拨出的号码。使用callback异步回调。 makeCall(phoneNumber: string, callback: AsyncCallback<void>): voidimport { call } from kit.TelephonyKit;import { BusinessError } from kit.BasicServicesKit;c…...
【Go】GO语言知识总结浅析
Go语言是一种现代化的编程语言,由Google于2007年设计并于2009年发布。它旨在使编程变得简单、高效,并且可以在多核处理器上轻松构建高性能应用。Go语言的编程思想、发展历史、版本特点、运行原理、数据类型、应用场景,以及在web开发、网络编程…...
GWO-Transformer-LSTM灰狼算法优化深度学习多变量回归预测(Maltab)
GWO-Transformer-LSTM灰狼算法优化深度学习多变量回归预测(Maltab) 目录 GWO-Transformer-LSTM灰狼算法优化深度学习多变量回归预测(Maltab)效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现灰狼算法OOA-Transf…...
上市公司企业供应链抵抗力数据集(2012-2023年)
一、测算方式:参考《财经研究》张树山(2024)老师的做法,供应链抵抗力(Resis)体现了供应链运行状态的稳定性,即在应对外部扰动时,供应链仍能维持循环畅通。本文从稳固供应链关系来筛选…...
javaWeb项目-ssm+jsp-XX牙科诊所管理系统功能介绍
本项目源码(点击下方链接下载):java-ssmjsp私人牙科诊所管理系统实现源码(项目源码-说明文档)资源-CSDN文库 项目关键技术 开发工具:IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架:ssm、Springboot 前端&…...
tcp_rmem中有三个值4896 131072 6291456是什么意思,有什么作用?
在 TCP 中,tcp_rmem参数的三个值分别具有以下含义和作用: 一、含义 “4896”: 通常代表 TCP 接收缓冲区的最小大小。这是接收端为接收数据预先分配的最小内存空间。当网络中数据量较小时,这个最小缓冲区可以确保有足够的空间来存储…...
转行AI产品经理:高薪诱惑,年薪90万不是梦!
近期有很多社招的小伙伴都在看转行的机会,同时马上要到了秋招的季节,校招生们都在积极选择第一份工作。所有人想要进入一个有前景、高薪高潜力的黄金赛道。 2024年如果大家看新机会,重点给大家推荐AI领域的岗位。先看一组数据: …...
javaWeb项目-ssm+jsp股票交易管理系统功能介绍
本项目源码(点击下方链接下载):java-ssmjsp股票交易管理系统实现源码(项目源码-说明文档)资源-CSDN文库 项目关键技术 开发工具:IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架:ssm、Springboot 前端ÿ…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
