Android音视频开发实战02-Jni

一 JNI

1.1 什么是JNI

JNI是Java Native Interface的缩写,是Java提供的一种机制,用于在Java代码中调用本地(C/C++)代码。它允许Java代码与本地代码进行交互,通过JNI,Java应用程序可以调用一些原生库或者操作系统API,以获取更好的性能和更强的功能支持。

使用JNI需要编写一些Native方法,并将其实现在本地代码(如C/C++)中。这些本地方法可以直接从Java代码中调用,从而获得更高的性能和更灵活的控制权。通常情况下,为了方便和可维护性,我们会将所有本地方法的实现都封装到一个动态链接库文件中,然后在Java代码中加载并调用其中的函数。

JNI为Java程序员提供了一种无缝集成本地代码的方式,使得Java应用程序可以更加高效地利用系统资源,从而提升性能和扩展性。

1.2 JNI的优劣势

JNI的优势:

  1. 快速的执行效率:通过JNI,可以将Java应用程序与本地代码结合起来,从而获得更高的执行效率。因为本地代码是通过C/C++等编程语言编写的,这些语言通常比Java更接近硬件和操作系统,因此本地代码在执行时可以更加快速和高效。

  2. 可以访问底层系统资源:通过JNI,Java应用程序可以直接调用操作系统的API,从而获得对系统底层资源(如文件、网络、内存等)的直接访问能力。这使得Java程序员可以实现更加灵活和复杂的功能,同时也可以提高程序的运行效率。

  3. 跨平台性:虽然本地代码必须针对每个平台进行编译,但是一旦编译完成,它们就可以在任何支持JNI的平台上运行。这意味着开发人员可以使用最适合自己的工具和环境来编写本地代码,而不需要考虑跨平台问题。

JNI的劣势:

  1. 复杂度较高:JNI需要开发人员熟悉Java和本地代码两种编程语言,并且需要掌握JNI规范及其相关的工具和技术。这使得JNI的学习曲线较陡峭,初学者可能需要花费较长时间才能掌握。

  2. 可移植性差:尽管JNI可以跨平台运行,但是在不同的平台上,本地代码必须重新编译和链接,这可能会带来一些兼容性问题。此外,由于Java虚拟机(JVM)的不同实现可能存在差异,因此在某些情况下,JNI代码也可能无法在不同的JVM上正确运行。

  3. 方法调用成本高,相对于Java调用Java代码,Java调用Jni的性能开销会更大一些,因此只有在执行特别耗时的代码的时候才会使用Jni,用C++代码的执行效率来覆盖掉方法调用的性能开销
    4.数据类型不通用,C/C++的数据类型与Java并不通用,在相互调用的时候需要做转化
    5.C/C++调用Java代码很复杂,很不方便

1.3 JNI常见的数据类型

JNI支持的数据类型可以分为两类:基本类型和引用类型。

基本类型包括:

  • jboolean:布尔类型,取值范围是true或false。
  • jbyte:字节类型,取值范围是-128到127。
  • jchar:字符类型,取值范围是0到65535。(Java中的char类型被映射为C语言的unsigned short类型)
  • jshort:短整型,取值范围是-32768到32767。
  • jint:整型,取值范围与平台相关,通常是32位有符号整数。
  • jlong:长整型,取值范围与平台相关,通常是64位有符号整数。
  • jfloat:单精度浮点型,取值范围约为1.4E-45到3.4E+38。
  • jdouble:双精度浮点型,取值范围约为4.9E-324到1.8E+308。

引用类型包括:

  • jobject:表示一个Java对象的引用。
  • jclass:表示一个Java类的引用。
  • jstring:表示一个Java字符串的引用。
  • jarray:表示一个Java数组的引用。
  • jthrowable:表示一个Java异常的引用。

基本数据类型是不需要手动释放,但是和Java不一样的是引用类型是需要手动调用释放的

二 JNI常见的数据处理方式

2.1 基本数据处理方式

基本数据在Java Jni C/C++之间差别不大,详见下面的对应关系:

JavaJNIC/C++占用内存大小备注
booleanjbooleanuint8_tunsigned 8 bits
bytejbyteint8_tsigned 8 bits
charjcharuint16_tunsigned 16 bits
shortjshortint16_tsigned 16 bits
intjintint32_tsigned 32 bits
longjlongint64_tsigned 64 bits在Native是用int64_t去承接long,不要用Native自身的long类型去承接
floatjfloatfloat32-bit IEEE 754IEEE 754是一种标准,由IEEE制定
doublejdoubledouble64-bit IEEE 754

2.2 ​String相互传递

2.2.1获取Java的Sting

extern "C"
JNIEXPORT void JNICALL
Java_com_luoye_bzmedia_BZMedia_testString(JNIEnv *env, jclass clazz, jstring video_path_) {const char *video_path = env->GetStringUTFChars(video_path_, JNI_FALSE);...//必须要释放env->ReleaseStringUTFChars(video_path_, video_path);
}

2.2.2 C/C++的数据转换为String

extern "C"
JNIEXPORT jstring JNICALL
Java_com_luoye_bzmedia_BZMedia_getString(JNIEnv *env, jclass clazz) {const char *imagePath="hello world!";return env->NewStringUTF(imagePath);
}

2.3 ​数组相互传递

2.3.1获取数组

extern "C" JNIEXPORT jint JNICALL
Java_com_luoye_bzmedia_BZMedia_mergeVideoOrAudio(JNIEnv *env, jclass type,jobjectArray inputPaths,jstring outPutPath) {size_t arrayLength = static_cast<size_t>(env->GetArrayLength(inputPaths));char **pstr = (char **) malloc(arrayLength * sizeof(char *));memset(pstr, 0, arrayLength * sizeof(char *));for (int i = 0; i < arrayLength; i++) {jstring javaPath = (jstring) env->GetObjectArrayElement(inputPaths, i);const char *path = env->GetStringUTFChars(javaPath, JNI_FALSE);size_t length = strlen(path) + 1;char *buffer = static_cast<char *>(malloc(length));memset(buffer, 0, length);sprintf(buffer, "%s", path);env->ReleaseStringUTFChars(javaPath, path);pstr[i] = buffer;}for (int i = 0; i < arrayLength; i++) {free(pstr[i]);}free(pstr);return 0;
}

2.3.2返回数组

extern "C"
JNIEXPORT jintArray JNICALL
Java_com_luoye_bzmedia_BZMedia_getVideoSize(JNIEnv *env, jclass clazz, jstring video_path_) {if (nullptr == video_path_) {BZLogUtil::logE("getVideoSize nullptr == video_path_");return nullptr;}const char *inputPath = env->GetStringUTFChars(video_path_, 0);int *ret = VideoUtil::getVideoSize(inputPath);env->ReleaseStringUTFChars(video_path_, inputPath);if (nullptr == ret) {return nullptr;}jintArray size = env->NewIntArray(2);env->SetIntArrayRegion(size, 0, 2, ret);delete[]ret;return size;
}

​2.4Java对象相互传递

Java对象相互传递需要特别注意的是,字段和方法签名,Java中的字段和方法签名包含了相应成员的名称、类型和参数列表等信息,可以唯一地标识一个成员,获取方式如下:
javap -s class_name
由于是正对class的所以我们需要在build目录下执行这个命令而不是Java源文件,样例如下:

public class com.luoye.bzmedia.bean.VideoInfo {public int width;descriptor: Ipublic com.luoye.bzmedia.bean.VideoInfo();descriptor: ()Vpublic int getHeight();descriptor: ()Ipublic void setHeight(int);descriptor: (I)Vpublic long getDuration();descriptor: ()Jpublic void setDuration(long);descriptor: (J)V}

只有public类型的字段和方法才能获取到签名

2.4.1获取Java对象

extern "C"
JNIEXPORT jlong JNICALL
Java_com_luoye_bzmedia_BZMedia_startRecord(JNIEnv *env, jclass clazz,jobject videoRecordParamsObj) {VideoRecordParams videoRecordParams;jclass videoRecordParamsClass = env->GetObjectClass(videoRecordParamsObj);jint srcWidth = env->GetIntField(videoRecordParamsObj,env->GetFieldID(videoRecordParamsClass, "inputWidth", "I"));videoRecordParams.inputWidth = srcWidth;
}

2.4.2返回Java对象

extern "C"
JNIEXPORT jobject JNICALL
Java_com_luoye_bzmedia_BZMedia_getVideoInfo(JNIEnv *env, jclass clazz, jstring video_path_) {if (nullptr == video_path_) {BZLogUtil::logE("getVideoSize nullptr == video_path_");return nullptr;}const char *inputPath = env->GetStringUTFChars(video_path_, 0);VideoInfo *pInfo = VideoUtil::getVideoInfo(inputPath);env->ReleaseStringUTFChars(video_path_, inputPath);if (nullptr == pInfo) {return nullptr;}jclass videoInfoClass = env->FindClass("com/luoye/bzmedia/bean/VideoInfo");jmethodID constructorMid = env->GetMethodID(videoInfoClass, "<init>", "()V");jobject videoInfoObj = env->NewObject(videoInfoClass, constructorMid);env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setWidth", "(I)V"), pInfo->width);env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setHeight", "(I)V"), pInfo->height);env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setDuration", "(J)V"), pInfo->duration);env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setRotate", "(I)V"), pInfo->rotate);env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setFrameRate", "(D)V"), pInfo->frameRate);env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setBitRate", "(J)V"), pInfo->bitRate);delete pInfo;return videoInfoObj;
}

2.5 ​Bitmap的数据相传递

2.5.1获取Bitmap的数据

extern "C" JNIEXPORT jlong JNICALL
Java_com_luoye_bzmedia_BZMedia_addVideoData4Bitmap(JNIEnv *env, jclass type,jlong nativeHandle, jobject bitmap,jint width, jint height) {int ret = 0;void *pixelscolor = NULL;if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixelscolor)) < 0) {BZLogUtil::logE("AndroidBitmap_lockPixels() failed ! error=%d", ret);return ret;}frame_RGBA->data[0] = (uint8_t *) pixelscolor;AVFrame *videoFrame = VideoUtil::allocVideoFrame(AV_PIX_FMT_YUV420P, width, height);sws_scale(sws_video_to_YUV,(const uint8_t *const *) frame_RGBA->data,frame_RGBA->linesize, 0, videoFrame->height,videoFrame->data,videoFrame->linesize);AndroidBitmap_unlockPixels(env, bitmap);return addVideoData(nativeHandle, videoFrame);
}

2.5.2返回Bitmap的数据

extern "C" JNIEXPORT jobject JNICALL
Java_com_luoye_bzmedia_BZMedia_bzReadPixelsNative(JNIEnv *env, jclass type,jint startX,jint startY, jint width,jint height) {if (width < 1 || height < 1) {BZLogUtil::logE("params is error width<1||height<1");return nullptr;}JNIEnv *jniEnv = nullptr;bool needDetach = JvmManager::getJNIEnv(&jniEnv);int ret;void *targetPixels;jclass bitmapCls = jniEnv->FindClass("android/graphics/Bitmap");jobject newBitmap;jmethodID createBitmapFunctionMethodID = jniEnv->GetStaticMethodID(bitmapCls, "createBitmap","(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");jstring configName = jniEnv->NewStringUTF("ARGB_8888");jclass bitmapConfigClass = jniEnv->FindClass("android/graphics/Bitmap$Config");jmethodID valueOfBitmapConfigFunctionMethodID = jniEnv->GetStaticMethodID(bitmapConfigClass,"valueOf","(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");jobject bitmapConfigObj = jniEnv->CallStaticObjectMethod(bitmapConfigClass,valueOfBitmapConfigFunctionMethodID,configName);newBitmap = jniEnv->CallStaticObjectMethod(bitmapCls, createBitmapFunctionMethodID, width,height, bitmapConfigObj);if ((ret = AndroidBitmap_lockPixels(jniEnv, newBitmap, &targetPixels)) < 0) {BZLogUtil::logE("gifDataCallBack AndroidBitmap_lockPixels() targetPixels failed ! error=%d",ret);}if (ret >= 0) {char *data = new char[width * height * 4];glReadPixels(startX, startY, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);memcpy(targetPixels, data, (size_t) (width * height * 4));AndroidBitmap_unlockPixels(jniEnv, newBitmap);delete[](data);}jniEnv->DeleteLocalRef(bitmapCls);jniEnv->DeleteLocalRef(configName);jniEnv->DeleteLocalRef(bitmapConfigObj);jniEnv->DeleteLocalRef(bitmapConfigClass);if (needDetach)JvmManager::getJavaVM()->DetachCurrentThread();return newBitmap;
}

2.6 Java与C/C++类的对象相互持有

如果是纯Java对象或者纯C/C++要想持有另外一个对象是一件很容易的事情,但是要想在Java与C/C++类的对象中相互持有彼此的实例,就显得很无助
这里就涉及到内存存储对象的机制了,内存一般分为栈内存与堆内存,栈内存存储基本数据类型与变量名,堆内存存储实际的对象,这个对象有一个唯一的地址,通过内存地址可以访问它,在Java中叫引用,在C/C++中叫指针,那么问题就转换到在Java中怎么存储C++对象的指针,在C++中怎么存储Java对象的引用
我们知道机器一般分为32位机器和64位机器,这个本质上说的是内存寻址能力的范围,32位机器的寻址能力为2的32次方,64位机器的寻址能力为2的64次方,也就是说64位机器最大有2的64次方个内存地址,我们只需要用一个变量来记录这个内存地址就好了,无疑用long/int64_t 类型是最合适的,他们有8字节,64bit的大小,刚好可以存下这些内存地址

2.6.1 Java 存储C/C++对象

//返回C/C++对象,强转为jlong
extern "C"
JNIEXPORT jlong JNICALL
Java_com_luoye_bzmedia_BZMedia_startRecord(JNIEnv *env, jclass clazz,jobject videoRecordParamsObj) {VideoRecorder *videoRecorder = new VideoRecorder();return reinterpret_cast<int64_t>(videoRecorder);}
//Java调用方式long nativeHandle = BZMedia.startRecord(videoRecordParams);
//传入C/C++对象的地址long ret = BZMedia.addYUV420Data(nativeHandle, buffer, pts);
//在C/C++代码中获取内存地址,并转换为C/C++对象,内存地址=0的时候是NULL,内存地址也可能为负值,这个需要注意一下extern "C"
JNIEXPORT jlong JNICALL
Java_com_luoye_bzmedia_BZMedia_addYUV420Data(JNIEnv *env, jclass clazz, jlong native_handle,jbyteArray data_, jlong pts) {if (native_handle == 0) {return -1;}VideoRecorder *videoRecorder = reinterpret_cast<VideoRecorder *>(native_handle);unsigned char *buffer = (unsigned char *) env->GetByteArrayElements(data_, nullptr);long ret = videoRecorder->addVideoData(buffer, pts);env->ReleaseByteArrayElements(data_, reinterpret_cast<jbyte *>(buffer), 0);return ret;
}

2.6.3 C/C++ 存储Java对象

同上,Java对象的引用也可以转换为一个int64_t的内存地址,因此也可以很方便的在C/C++对象中存储,在需要调用Java方法的时候就可以很方便

三 回调方法的写法

结合2.6的对象相互存储的技术就可以实现C++回调Java方法,至于Java回调C++同理,由于这种场景比较少我们以C++回调Java方法为例
大致步骤如下:

  1. 在Java层定义回调接口
  2. Java调用Native方法并传入接口对象
  3. Native获取Java接口对象并存储到对象中
  4. 在C++对象中通过函数指针调用C函数,并传入Java接口对象的地址
  5. C函数使用JNI调用Java接口函数

给一个完整的Demo:
Java 方法:

    {testCallBack(new OnActionListener() {@Overridepublic void progress(float progress) {Log.d(TAG, "native call back progress=" + progress);}});}public native void testCallBack(OnActionListener onActionListener);public interface OnActionListener {void progress(float progress);}

Native方法:

void callBackGateway(int64_t methodHandle, float progress) {JNIEnv *jniEnv = nullptr;bool needDetach = JvmManager::getJNIEnv(&jniEnv);if (methodHandle == 0 || nullptr == jniEnv) {jniEnv = nullptr;if (needDetach)JvmManager::getJavaVM()->DetachCurrentThread();return;}auto *videoPlayerMethodInfo = (ActionMethodInfo *) methodHandle;jniEnv->CallVoidMethod(videoPlayerMethodInfo->listenerObj,videoPlayerMethodInfo->onProgressMethodId, progress);jniEnv = nullptr;if (needDetach)JvmManager::getJavaVM()->DetachCurrentThread();
}extern "C"
JNIEXPORT void JNICALL
Java_com_example_ffmpegtest_MainActivity_testCallBack(JNIEnv *env, jobject thiz, jobject on_action_listener) {CallBackTest callBackTest;auto *videoPlayerMethodInfo = new ActionMethodInfo();//CallBack一般涉及到多线程交互,一般通过NewGlobalRef作为全局变量jobject listenerObj = env->NewGlobalRef(on_action_listener);videoPlayerMethodInfo->listenerObj = listenerObj;jclass listenerClass = env->GetObjectClass(on_action_listener);videoPlayerMethodInfo->onProgressMethodId = env->GetMethodID(listenerClass,"progress","(F)V");env->DeleteLocalRef(listenerClass);callBackTest.init(reinterpret_cast<int64_t>(videoPlayerMethodInfo), callBackGateway);env->DeleteGlobalRef(listenerObj);delete videoPlayerMethodInfo;
}//CallBackTest.h
class CallBackTest {
public:int init(int64_t handle, void (*progressCallBack)(int64_t javaHandle, float progress));
};//CallBackTest.cpp
int CallBackTest::init(int64_t handle, void (*progressCallBack)(int64_t, float)) {progressCallBack(handle, 0);progressCallBack(handle, 0.2);progressCallBack(handle, 0.4);progressCallBack(handle, 0.6);progressCallBack(handle, 0.8);progressCallBack(handle, 1);return 0;
}//JvmManager.h
class JvmManager {
public:static JavaVM *getJavaVM();static bool getJNIEnv(JNIEnv **pJNIEnv);static int32_t JNI_VERSION;
};//JvmManager.cpp
#include <android/log.h>
#include "JvmManager.h"extern "C" {
#include <libavcodec/ffmpeg_jni.h>
}
JavaVM *bzJavaVM = nullptr;int32_t JvmManager::JNI_VERSION = JNI_VERSION_1_6;jint JNI_OnLoad(JavaVM *vm, void *reserved) {bzJavaVM = vm;__android_log_print(ANDROID_LOG_DEBUG, "bz_", "JNI_OnLoad success");av_jni_set_java_vm(vm, NULL);if (vm->GetEnv(reinterpret_cast<void **>(&vm), JNI_VERSION_1_6) != JNI_OK) {JvmManager::JNI_VERSION = JNI_VERSION_1_4;return JNI_VERSION_1_4;}return JNI_VERSION_1_6;
}bool JvmManager::getJNIEnv(JNIEnv **pJNIEnv) {if (NULL == bzJavaVM) {return false;}bzJavaVM->GetEnv((void **) pJNIEnv, JNI_VERSION);if (NULL != *pJNIEnv) {return false;} else {bzJavaVM->AttachCurrentThread(pJNIEnv, NULL);return true;}
}JavaVM *JvmManager::getJavaVM() {return bzJavaVM;
}

四 Jni资源回收

C++,Java的对象都有自身的新建与回收的机制与方法,Jni也不例外主要是涉及到引用的释放,只要父类是jobject的都涉及到手动释放,主要的释放方法为:

  1. DeleteLocalRef
  2. DeleteGlobalRef
  3. DeleteWeakGlobalRef
  4. ReleaseStringUTFChars
  5. ReleaseStringChars
  6. ReleasePrimitiveArrayCritical
  7. ReleaseIntArrayElements
  8. GetDirectBufferAddress
  9. FreeDirectByteBuffer

常见新建与回收的情况如下:

jstring:

env->GetStringUTFChars
env->ReleaseStringUTFChars
如:
const char *mediaPath = env->GetStringUTFChars(media_path, 0);
env->ReleaseStringUTFChars(media_path, mediaPath);

jclass

env->GetObjectClass
env->DeleteLocalRef
如:
jclass listenerClass = env->GetObjectClass(listener);
env->DeleteLocalRef(listenerClass);

jobject

env->NewGlobalRef
env->DeleteGlobalRef
如:
jobject actionObj = env->NewGlobalRef(obj);
env->DeleteLocalRef(actionClass);

数组

env->GetIntArrayElements
env->ReleaseIntArrayElements
如:
jint *elems = env->GetIntArrayElements(arr, 0);
env->ReleaseIntArrayElements(arr, elems, 0);

五 如何防止SO被脱裤

在Android侧SO的调用是通过JNI调入的,也就是说我们只要知道Java的native方法就可以直接使用SO的能力,再通过二进制修改掉native方法路径,以及SO的名字就可以完完全全把SO的能力直接复用,而且基本发现不了,因此我们需要对SO进行处理,也就是需要做鉴权处理。
SO需要做鉴权就需要拿到比较唯一的身份识别,在Android侧能做为身份识别的有Android包名以及APK签名,一般做鉴权都是基于这两者来做的,但是系统方法是可以被hook掉的,一般来说我们无法通过获取包名,以及签名来做鉴权。
我这里提供的一种方案是通过加密包名,提前放到SO里面,然后遍历包名下的私有目录,通过判断是否有文件权限的方式来鉴权,这样可以不调Java获取包名以及签名的方法就可以做好鉴权
相关Demo详见:https://github.com/bookzhan/bzmedia/tree/master/bzmedialib/src/main/cpp/permission 相关的代码

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

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

相关文章

责任链模式(Chain of Responsibility)

别名 命令链&#xff08;Chain of Command&#xff09;。 定义 责任链是一种行为设计模式&#xff0c;允许你将请求沿着处理者链进行发送。收到请求后&#xff0c;每个处理者均可对请求进行处理&#xff0c;或将其传递给链上的下个处理者。 前言 1. 问题 假如你正在开发一…

Linux基于thundersvm使用GPU对svm进行加速

Linux基于thundersvm使用GPU对svm进行加速 文章目录 Linux基于thundersvm使用GPU对svm进行加速下载方法pip快速下载命令 普通下载命令问题解决方法以下操作需要使用sudo权限 使用thundersvm调用方式 在代码中的使用训练代码更改更改内容 加载模型同样需要导入thundersvm更改内容…

Flutter 组件(二)文本 与 输入框组件

Flutter开发笔记 Flutter 组件&#xff08;二&#xff09;文本 与 输入框组件 - 文章信息 - Author: Jack Lee (jcLee95) Visit me at: https://jclee95.blog.csdn.netEmail: 291148484163.com. Shenzhen ChineAddress of this article:https://blog.csdn.net/qq_28550263/art…

QT学习笔记4--自定义信号的槽

逻辑&#xff1a;下课后&#xff0c;老师饿了&#xff0c;学生请吃饭。 使用connect函数连接自定义的信号和槽函数。 创建类 信号 #ifndef TEACHER_H #define TEACHER_H#include <QObject>class teacher : public QObject {Q_OBJECT public:explicit teacher(QObjec…

【网络管理发展】网络杂谈(12)之网络管理未来发展趋势

涉及知识点 网络管理未来的发展方向&#xff0c;网络管理未来的发展趋势&#xff0c;个人闲谈网络管理未来发展&#xff0c;网络管理技术现状&#xff0c;应用服务供应商&#xff08;ASP&#xff09;&#xff0c;网络的远程管理&#xff0c;人工智能与未来。 原创于&#xff1…

爬虫工具-替换js文件ReRes插件/Gores插件

目录 一、ReRes插件二、Gores插件 一、ReRes插件 用途&#xff1a;爬虫逆向过程中一些文件需要替换时 ① 原始网站js文件有无限debugger&#xff0c;复制原始网站js文件&#xff0c;删掉无限debugger相关代码保存为新的js文件&#xff1b;用ReRes插件进行替换② 原始网站js文件…

【分布式存储】聊聊共识和一致性

在分布式存储系统中&#xff0c;对于提高性能、可用性、可拓展性来说都有相关机制可以保证&#xff0c;比如复制、切片等&#xff0c;但是一旦涉及到分布式系统中选主的问题&#xff0c;就比较难&#xff0c;因为网络是不可靠的&#xff0c;并且可能还有拜占庭将军问题。所以如…

线程安全的集合类

目录 一、线程安全的集合类 1.1、多线程环境下使用ArrayList 1.2、多线程使用队列 1.3、多线程环境使用哈希表 1.3.1、Hashtable 1.3.2、ConcurrentHashMap 一、线程安全的集合类 在多线程的环境下&#xff0c;多个线程对同一个共享变量进行写操作的时候&#xff0c;有可…

chatGPT流式回复是怎么实现的

chatGPT流式回复是怎么实现的 先说结论&#xff1a; chatGPT的流式回复用的就是HTTP请求方案中的server-send-event流式接口&#xff0c;也就是服务端向客户端推流数据。 那eventStream流式接口怎么实现呢&#xff0c;下面就进入正题&#xff01; 文章目录 chatGPT流式回复…

【NVIDIA】Jetson AGX Orin内核、设备树更新指南

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

redis-单节点安装

daemonize yes port 6379 bind 0.0.0.0 requirepass 123456 save 3600 1 300 100 60 10000dir /usr/local/redis dbfilename dump.rdb logfile redis.log pidfile redis.pid##save 3600 1 300 100 60 10000 ##3600秒(一小时),至少有一个值的话,会进行存盘 ##300秒(五分钟),至少…

Java版企业工程项目管理系统源码+java版本+项目模块功能清单+spring cloud +spring boot

工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff1a;实现对数据字典标签的增删改查操作 2、编码管理&#xff1a;实现对系统编码的增删改查操作 3、用户管理&#xff1a;管理和查看用户角色 4、菜单管理&#xff1a;实现对系统菜单的增删改查操…