Android音视频开发实战02-Jni

news2024/11/17 13:47:30

一 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: I
  public com.luoye.bzmedia.bean.VideoInfo();
    descriptor: ()V
		
  public int getHeight();
    descriptor: ()I

  public void setHeight(int);
    descriptor: (I)V

  public long getDuration();
    descriptor: ()J

  public 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() {
            @Override
            public 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.coloradmin.cn/o/654480.html

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

相关文章

Maven编译常见问题收集

1、父pom里面有引入lombok依赖&#xff0c;为什么子pom有用到lombok&#xff0c;依然识别不到呢 这是因为父pom引入依赖的时候&#xff0c;把 <dependency></dependency>依赖标签&#xff0c;最外层包 在了<dependencyManagement></dependencyManagemen…

Python学了基本语法 下一步该干什么 ?

刚入门Python,学习了基本语法后&#xff0c;你可以开始编写简单的程序了。接下来&#xff0c;你可以学习Python的标准库和第三方库&#xff0c;掌握更多的编程技巧和知识&#xff0c;提高自己的编程能力。同时&#xff0c;也可以通过实践项目来巩固所学知识&#xff0c;提高自己…

web3描述以太坊与区块链之间的操作关系

好通过前面两篇文章 Web3.0概念 web3带大家简单建立区块链概念 大家开始明白 web3.0是基于区块链为基础开发的 我们讲区块链多次提到以太坊 EVM 那这个东西到底是什么呢&#xff1f; 就现在的情况来讲 从零到一去搭建一个区块链环境 那可太难了 所以 以太坊一开始 确实是做一…

关于GPT-3和GPT-4,你需要知道都在这里

友情提示 提示&#xff1a;本文约3500个文字&#xff0c;字数较长&#xff0c;可直接点击序号进入相关目录阅读字数 文章目录 友情提示前言一、什么是 GPT-3、GPT-4 和 ChatGPT&#xff1f;二、对未来的担忧三、什么时候可以用GPT4&#xff1f;四、GPT-4用在什么地方五、GPT-4能…

json_decode返回NULL

json_decode返回NULL 最近在调用某公司的API时&#xff0c;将对方返回的数据&#xff0c;使用PHP的json_decode函数解析&#xff0c;但是返回NULL,最终排查为对方传送来的json格式有误 打印$_REQUEST&#xff0c;数据结构大致如下&#xff1a; 1 2 3 4 5 6 7 8 9 10 array (…

Android 面试延伸技术点有哪些,你能答上几个?

1、如何进行单元测试&#xff0c;如何保证 App 稳定 &#xff1f; 参考回答&#xff1a; 要测试 Android 应用程序&#xff0c;通常会创建以下类型自动单元测试 本地测试&#xff1a;只在本地机器 JVM 上运行&#xff0c;以最小化执行时间&#xff0c;这种单元测试不依赖于 An…

JavaScript之ES6高级语法(三)

本文是我在学习过程中记录学习的点点滴滴&#xff0c;目的是为了学完之后巩固一下顺便也和大家分享一下&#xff0c;日后忘记了也可以方便快速的复习。 ES6高级语法(三&#xff09; 前言一、原型对象1.1、constructor 属性1.2、对象原型 二、原型继承三、原型链 前言 今天学习的…

【C++】红黑树的概念与模拟实现

红黑树的概念与模拟实现 红黑树的概念红黑树的性质红黑树节点的定义红黑树的迭代器红黑树的插入红黑树和AVL树的比较红黑树的模拟实现 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。…

计算机组成原理(0)概述

前言 没有想好到底是要怎么学习&#xff0c;看哪个视频课&#xff0c;看到1.2 计算机的基本组成1.2-a1_哔哩哔哩_bilibili 是15年录的视频读PPT课本不一样就更不想看了&#xff0c;但是CSAPP难度大&#xff08;主要是广度&#xff0c;所以可能不很适用于考试或计组的学习&…

java代码审计和安全漏洞修复

java代码审计和安全漏洞修复 本文目录 java代码审计和安全漏洞修复开源&#xff1a;奇安信代码卫士官网使用gitee服务使用 非开源&#xff1a;思客云找八哥错误类型以及修改方法1.硬编码2. 路径操作3. 路径操作 - Zip文件条目覆盖4. SQL注入5. SQL注入 - Hibernate6. XML外部实…

视频与图片检索中的多模态语义匹配模型 ——原理、启示、应用与展望

前言 三多前笔者在《万字长文漫谈视频理解》[1]一文中&#xff0c;曾经将自己对视频理解的认识进行过简单总结&#xff0c;幸而获得了朋友们的认可&#xff0c;能让读者认可是笔者最为骄傲的成就。现在看来文中观点有不少纰漏狭隘之处&#xff0c;特别是近年来多模态模型的流行…

6.5this关键字

1. 关键字&#xff1a;this 1.1 this 是什么&#xff1f; 首先。this在Java中是一个关键字&#xff0c;this 指代的是本类的引用对象 1.2 什么时候使用 this 1.2.1 实例方法或构造器中使用当前对象的成员 1、在实例方法或构造器中&#xff0c;我们在使用get和set方法中使用…

行为型设计模式06-迭代器模式

&#x1f9d1;‍&#x1f4bb;作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 迭代器模式 1、迭代器模式介绍 迭代器模式是一种行为型设计模式&#xff0c;它提供了一种方法来访问聚…

HydroD 实用教程(九)时域水动力分析

目 录 一、前言二、前处理三、定义/提交作业3.1 创建分析作业3.2 定义输入数据3.3 设置执行指令3.4 指定输出格式3.5 提交求解计算 四、输出文件五、结果后处理5.1 绘制力/位移时程5.2 傅里叶变换与导荷5.3 播放时域结果动画 六、参考文献 一、前言 SESAM &#xff08;Super El…

扫描仪连续扫描提示有一个问题阻值扫描该文档。请重试,错误的解决办法

故障现象: 用户新安装的联想M7650DNA一体多功能激光打印机,安装完所有驱动后;打印、复印都正常,只有扫描不正常,扫描多张后就会提示:有一个问题阻值扫描该文档。请重试,或者参阅“帮助和支持”或扫描仪附带的信息,了解有关疑难解答的信息。如下图:故障。 开始怀…

基于Pytest+Allure+Excel的接口自动化测试框架

1. Allure 简介 简介 Allure 框架是一个灵活的、轻量级的、支持多语言的测试报告工具&#xff0c;它不仅以 Web 的方式展示了简介的测试结果&#xff0c;而且允许参与开发过程的每个人可以从日常执行的测试中&#xff0c;最大限度地提取有用信息。 Allure 是由 Java 语言开发…

Maven项目管理-随笔(入门)

目录 前言 什么是Maven Maven的优点 Maven的核心概念有哪些 POM是什么 什么是依赖管理 什么是插件 什么是仓库 概述 1、构建 2、依赖 安装与配置 1、下载 2、Windows Maven安装 1&#xff09;解压到指定目录 2&#xff09;配置环境变量 3&#xff09;目录结构 …

离散数学题目收集整理练习(期末过关进度60%)

✨博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;离散数学考前复习&#xff08;知识点题&#xff09; &#x1f353;专栏&#xff1a;概率论期末速成&#xff08;一套卷&#xff09; &#x1f433;专栏&#xff1a;数字电路考前复习 ✨博主的其他文章&#xff1a;点击…

基于ssm框架的数字化题库与在线考试系统设计与实现+第二稿+文档

博主介绍&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 项目名称 基于ssm框架的数字化题库与在线考试系统设计与实现第二稿文档 视频演示 视频去哪了呢&#xff1f;_哔哩哔哩_bilibili 系统介绍 摘 要 随着科学技术…

iOS游戏反外挂方案解析

自2007年iPhone OS发布以来&#xff0c;iOS系统已经发展了近17年&#xff0c;凭借着独家的系统环境、安全性更高的闭源生态等优势。iOS从一众手机系统中脱颖而出&#xff0c;与安卓稳坐手机系统市场两把头部交椅。 不同于安卓的开源生态&#xff0c;iOS的闭源生态中的硬件、软…