我的C语言学习进阶之旅介绍一下NDK开发中关于JNI函数的两种注册方式:静态注册和动态注册

Posted 字节卷动

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的C语言学习进阶之旅介绍一下NDK开发中关于JNI函数的两种注册方式:静态注册和动态注册相关的知识,希望对你有一定的参考价值。

一、要介绍本篇博客的原因

前段时间学习OpenGL ES相关技术,下载了一个Github项目学习,项目地址在:https://github.com/githubhaohao/NDK_OpenGLES_3_0

项目的关键代码都是C++实现的,所以需要使用JNI技术。

我打开定义native方法的java类,如下所示:全部都是红色警告,


原因是,C++代码层没有对应的遵循特定JNI格式的JNI函数。

其实这个项目没有使用静态注册方法,而是使用了动态注册方法。下面我们分别来讲一下两种方式的区别。

二、静态注册

2.1 实现原理

根据函数名来建立java方法和JNI函数间的一一对应关系。

2.2 实现过程

  1. 编写.java代码;
  2. 编译.java代码,生成.class文件;
  3. 用过javah指令,利用生成的.class文件生成JNI.h文件;
  4. 生成后的JNI头文件中包含了Java函数在JNI层的声明;

可以参考下面几篇博客:

2.3 弊端

  1. 书写很不方便,因为JNI层函数的名字必须遵循特定的格式,且名字特别长;
  2. 会导致程序员的工作量很大,因为必须为所有声明了native函数的java类编写JNI头文件;
  3. 程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时。

关于第1和第2点,随着android Studio升级,现在可以直接写完java native方法 就可以使用快捷键【Alt + Enter】就会弹出提示提示【Create JNI function for xxx native method】,如下所示:

public class MyNativeRender 
	...  其他代码
	
    static 
        System.loadLibrary("native-render");
    

    public native void native_Init();

    public native void native_UnInit();
    
	...  其他代码

比如我们生成一下,自动生成如下代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_byteflow_app_MyNativeRender_native_1Init(JNIEnv *env, jobject thiz) 
    // TODO: implement native_Init()

再回到java层,就可以看到坐标有个C++的logo,点击即可跳转到对应的cpp代码的jni函数。

2.4 示例

目前我项目中还是使用的这种静态注册的方法。大家可以在下面的github查看

  1. java 层代码

java层代码编写native函数在com/oyp/openglesdemo/render/MyNativeRenderer.kt中,如下所示:

package com.oyp.openglesdemo.render

import android.app.Activity
import android.content.res.AssetManager
import android.opengl.GLSurfaceView
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10

class MyNativeRenderer(activity: Activity) : GLSurfaceView.Renderer, RenderAction 
    private var mActivity: Activity = activity
    var mSampleType = 0

    init 
        System.loadLibrary("ouyangpeng-opengles-lib")
    

    // 通用的
    private external fun nativeSurfaceCreate(assetManager: AssetManager)
    private external fun nativeSurfaceChange(width: Int, height: Int)
    private external fun nativeDrawFrame()
    private external fun nativeSetRenderType(sampleCategoryType: Int, renderSampleType: Int)
    private external fun nativeOnDestroy()

    // 特定的方法
    private external fun nativeSwitchBlendingMode()

    // 特定的方法
    private external fun nativeSetDelta(x: Float, y: Float)
    private external fun nativeSetMinFilter(filter: Int)
    private external fun nativeSetMagFilter(filter: Int)

    private external fun nativeSetImageData(
        format: Int,
        width: Int,
        height: Int,
        imageData: ByteArray?
    )

    private external fun nativeSetImageDataWithIndex(
        index: Int,
        format: Int,
        width: Int,
        height: Int,
        imageData: ByteArray?
    )

    private external fun nativeUpdateTransformMatrix(
        rotateX: Float,
        rotateY: Float,
        scaleX: Float,
        scaleY: Float
    )


    override fun onSurfaceCreated(gl: GL10, config: EGLConfig) 
        val assetManager: AssetManager = mActivity.assets
        nativeSurfaceCreate(assetManager)
    

    override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) 
        nativeSurfaceChange(width, height)
    

    override fun onDrawFrame(gl: GL10) 
        nativeDrawFrame()
    

    fun setRenderType(sampleCategoryType: Int, renderSampleType: Int) 
        if (sampleCategoryType == IMyNativeRendererType.SAMPLE_TYPE) 
            mSampleType = renderSampleType
        
        nativeSetRenderType(sampleCategoryType, renderSampleType)
    

    fun onDestroy() 
        nativeOnDestroy()
    

    override fun switchBlendingMode() 
        nativeSwitchBlendingMode()
    

    override fun setMinFilter(filter: Int) 
        nativeSetMinFilter(filter)
    

    override fun setMagFilter(filter: Int) 
        nativeSetMagFilter(filter)
    

    override fun setDelta(deltaX: Float, deltaY: Float) 
        nativeSetDelta(deltaX, deltaY)
    

    override fun setImageData(
        format: Int,
        width: Int,
        height: Int,
        imageData: ByteArray
    ) 
        nativeSetImageData(format, width, height, imageData)
    

    override fun setImageDataWithIndex(
        index: Int,
        format: Int,
        width: Int,
        height: Int,
        imageData: ByteArray
    ) 
        nativeSetImageDataWithIndex(index, format, width, height, imageData)
    

    override fun updateTransformMatrix(
        rotateX: Float,
        rotateY: Float,
        scaleX: Float,
        scaleY: Float
    ) 
        nativeUpdateTransformMatrix(rotateX, rotateY, scaleX, scaleY)
    


2. 对应的JNI方法

对应的JNI方法实现在app/src/main/cpp/jni/JniImpl.cpp文件中,如下所示:

//
// Created by OuyangPeng on 2021/11/26.
//
#include <jni.h>
#include <MyGLRenderContext.h>
#include <EGLRender.h>

extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeSurfaceCreate(
        JNIEnv *env, jobject thiz, jobject asset_manager) 
    MyGLRenderContext::GetInstance()->OnSurfaceCreated(env, asset_manager);


extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeSurfaceChange(
        JNIEnv *env, jobject thiz, jint width, jint height) 
    MyGLRenderContext::GetInstance()->OnSurfaceChanged(width, height);


extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeDrawFrame(JNIEnv *env, jobject thiz) 
    MyGLRenderContext::GetInstance()->OnDrawFrame();


extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeSetRenderType(
        JNIEnv *env, jobject thiz, jint sampleCategoryType, jint renderSampleType) 
    MyGLRenderContext::GetInstance()->SetRenderType(sampleCategoryType, renderSampleType);


extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeOnDestroy(JNIEnv *env, jobject thiz) 
    MyGLRenderContext::DestroyInstance();


extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeSwitchBlendingMode(JNIEnv *env, jobject thiz) 
    MyGLRenderContext::GetInstance()->SwitchBlendingMode();

extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeSetDelta(
        JNIEnv *env, jobject thiz, jfloat x, jfloat y) 
    MyGLRenderContext::GetInstance()->SetDelta(x, y);

extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeSetMinFilter(
        JNIEnv *env, jobject thiz, jint filter) 
    MyGLRenderContext::GetInstance()->SetMinFilter(filter);

extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeSetMagFilter(
        JNIEnv *env, jobject thiz, jint filter) 
    MyGLRenderContext::GetInstance()->SetMagFilter(filter);

extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeSetImageData(
        JNIEnv *env, jobject thiz,
        jint format, jint width, jint height, jbyteArray imageData) 

    int len = env->GetArrayLength(imageData);
    u_int8_t *buf = new u_int8_t[len];
    env->GetByteArrayRegion(imageData, 0, len, reinterpret_cast<jbyte *>(buf));

    MyGLRenderContext::GetInstance()->SetImageData(format, width, height, buf);
    delete[]buf;
    env->DeleteLocalRef(imageData);


extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeSetImageDataWithIndex(
        JNIEnv *env, jobject thiz,
        jint index, jint format, jint width, jint height, jbyteArray imageData) 

    int len = env->GetArrayLength(imageData);
    u_int8_t *buf = new u_int8_t[len];
    env->GetByteArrayRegion(imageData, 0, len, reinterpret_cast<jbyte *>(buf));

    MyGLRenderContext::GetInstance()->SetImageDataWithIndex(index, format, width, height, buf);
    delete[]buf;
    env->DeleteLocalRef(imageData);


extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_MyNativeRenderer_nativeUpdateTransformMatrix(
        JNIEnv *env, jobject thiz, jfloat rotateX,jfloat rotateY,jfloat scaleX,jfloat scaleY) 
    MyGLRenderContext::GetInstance()->UpdateTransformMatrix(rotateX, rotateY, scaleX, scaleY);


   EGL 渲染相关 

extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_egl_NativeEglRender_nativeEglRenderInit(JNIEnv *env, jobject thiz, jobject asset_manager) 
    EGLRender::GetInstance()->Init(env,asset_manager);


extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_egl_NativeEglRender_nativeEglRenderSetImageData(
        JNIEnv *env, jobject thiz, jbyteArray data, jint width, jint height) 

    int len = env->GetArrayLength (data);
    uint8_t* buf = new uint8_t[len];
    env->GetByteArrayRegion(data, 0, len, reinterpret_cast<jbyte*>(buf));

    EGLRender::GetInstance()->SetImageData(buf, width, height);

    delete[] buf;
    env->DeleteLocalRef(data);

extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_egl_NativeEglRender_nativeEglRenderSetFragmentShaderType(
        JNIEnv *env, jobject thiz, jint param_type, jint fShaderType) 
    EGLRender::GetInstance()->SetFragmentShaderType(param_type, fShaderType);

extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_egl_NativeEglRender_nativeEglRenderDraw(JNIEnv *env, jobject thiz) 
    EGLRender::GetInstance()->Draw();

extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_openglesdemo_render_egl_NativeEglRender_nativeEglRenderUnInit(JNIEnv *env, jobject thiz) 
    EGLRender::GetInstance()->UnInit();


   EGL 渲染相关 

实际上在Android Studio中,显示的效果还行,如下所示:

三、动态注册

由上面的介绍可以发现,通过Android Studio等IDE的功能越来越完善,关于JNI函数名很长的问题其实并不是瓶颈。

上面有提到一个弊端是程序运行效率低 :因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时。

应用层的Java类别通过VM而调用到native函数。一般是通过VM去寻找*.so里的native函数。
如果需要连续呼叫很多次,每次都需要寻找一遍,会多花许多时间。

此时,C组件开发者可以将本地函数向VM进行注册,以便能加快后续调用native函数的效率。

可以这么想象一下,假设VM内部一个native函数链表,初始时是空的,在未动态注册之前此native函数链表是空的,每次java调用native函数之前会首先在此链表中查找需要查找需要调用的native函数,如果找到就直接使用,如果未找到,得再通过载入的.so文件中的函数列表中去查找,且每次java调用native函数都是进行这样的流程,因此,效率就自然会下降。

为了克服这样现象,我们可以通过在.so文件载入初始化时,即JNI_OnLoad函数中,先行将native函数注册到VMnative函数链表中去,这样一来,后续每次java调用native函数时都会在VM中的native函数链表中找到对应的函数,从而加快速度.

注:在Android 源码开发环境下,大多采用动态注册native方法.

下面我们来介绍一下动态注册的方式。

3.1 实现原理

直接告诉native函数其在JNI中对应函数的指针;

3.2 实现过程

  1. 利用结构体JNINativeMethod保存Java Native函数和JNI函数的对应关系;
  2. 在一个JNINativeMethod数组中保存所有native函数和JNI函数的对应关系;
  3. Java中通过System.loadLibrary加载完JNI动态库之后,调用JNI_OnLoad函数,开始动态注册;
  4. JNI_OnLoad中会调用AndroidRuntime::registerNativeMethods函数进行函数注册;
  5. AndroidRuntime::registerNativeMethods中最终调用jni RegisterNativeMethods完成注册。

3.3 优点

克服了静态注册的弊端。

  1. RegisterNatives方法能帮助你把C/C++中的方法隐射到Java中的native方法,而无需遵循特定的方法命名格式。
  2. 更有效率去找到函数。
  3. 在执行期间进行抽换。

3.4 示例

我们就以 https://github.com/githubhaohao/NDK_OpenGLES_3_0的代码作为示例:

  • java层代码

代码定义在 app/src/main/java/com/byteflow/app/MyNativeRender.java 文件中

/**
 *
 * Created by 公众号:字节流动 on 2021/3/12.
 * https://github.com/githubhaohao/NDK_OpenGLES_3_0
 * 最新文章首发于公众号:字节流动,有疑问或者技术交流可以添加微信 Byte-Flow ,领取视频教程, 拉你进技术交流群
 *
 * */

package com.byteflow.app;

public class MyNativeRender 
    其他代码


    static 
        System.loadLibrary("native-render");
    

    public native void native_Init();

    public native void native_UnInit();

    public native void native_SetParamsInt(int paramType, int value0, int value1);

    public native void native_SetParamsFloat(int paramType, float value0, float value1);

    public native void native_UpdateTransformMatrix(float rotateX, float rotateY, float scaleX, float scaleY);

    public native void native_SetImageData(int format, int width, int height, byte[] bytes);

    public native void native_SetImageDataWithIndex(int index, int format, int width, int height, byte[] bytes);

    public native void native_SetAudioData(short[] audioData);

    public native void native_OnSurfaceCreated();

    public native void native_OnSurfaceChanged(int width, int height);

    public native void native_OnDrawFrame();



2. Native层代码
代码定义在 app/src/main/cpp/JniImpl.cpp 文件中

  • 定义JNI NativeMethod的映射关系

g_RenderMethods[]和g_BgRenderMethods[]是2个<名称,函数指针>对照表,在程序执行时,可多次呼叫RegisterNativeMethods()函数来更换本地函数之指针,而达到弹性抽换本地函数之目的。

static JNINativeMethod g_RenderMethods[] = 
		"native_Init",                      "()V",       (void *)(native_Init),
		"native_UnInit",                    "()V",       (void *)(native_UnInit),
		"native_SetImageData",              "(III[B)V",  (void *)(native_SetImageData),
		"native_SetImageDataWithIndex",     "(IIII[B)V", (void *)(native_SetImageDataWithIndex),
		"native_SetParamsInt",              "(III)V",    (void *)(native_SetParamsInt),
		"native_SetParamsFloat",            "(IFF)V",    (void *)(native_SetParamsFloat),
		"native_SetAudioData",              "([S)V",     (void *)(native_SetAudioData),
		"native_UpdateTransformMatrix",     "(FFFF)V",   (void 以上是关于我的C语言学习进阶之旅介绍一下NDK开发中关于JNI函数的两种注册方式:静态注册和动态注册的主要内容,如果未能解决你的问题,请参考以下文章

我的C/C++语言学习进阶之旅介绍一下NDK开发之C的简单易用图像库stb

我的C/C++语言学习进阶之旅介绍一下NDK开发之C的简单易用图像库stb

我的C/C++语言学习进阶之旅NDK开发之Native层使用fopen打开Android设备上的文件

我的C/C++语言学习进阶之旅NDK开发之Native层使用fopen打开Android设备上的文件

我的C/C++语言学习进阶之旅NDK开发运行的时候出现错误signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x940a2e48

我的C/C++语言学习进阶之旅NDK开发运行的时候出现错误signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x940a2e48