searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

如何在Android下进行JNI开发

2023-07-31 06:30:57
5
0

在进行Android开发的过程中,我们必定会遇到视频图像处理、高强度密集运算、特殊算法等场景,这时我们就不得不需要去接触一些C/C++代码,进行JNI开发。下面我们将一起看看JNI如何进行开发。

1. 编写JNI接口和C/C++代码

1) JNI接口

package cn.ctcdn.jni;

public class JNIApi {

    public native String stringFromJNI();
}

2) C/C++代码

extern "C" JNIEXPORT jstring
JNICALL
Java_cn_ctcdn_jni_JNIApi_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

2. 编写CMakeLists.txt文件

# TODO 设置构建本机库文件所需的 CMake的最小版本
cmake_minimum_required(VERSION 3.4.1)

# TODO 添加自己写的 C/C++源文件
add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

# TODO 依赖 NDK中的库
find_library( log-lib
              log )

# TODO 将目标库与 NDK中的库进行连接
target_link_libraries( native-lib
                       ${log-lib} )

3. build.gradle的配置

android {
    ...
    defaultConfig {
        ...

        ndk {
            // Android 支持的 CPU 架构
            abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
        }
    }
    buildTypes {
        ...
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

4. .SO库的使用

static {
    System.loadLibrary("native-lib");
}

5. JNI基础语法

  5.1 基础类型

Java类型

native类型

描述

boolean

jboolean

unsigned 8 bits

byte

jbyte

signed 8 bits

char

jchar

unsigned 16 bits

short

jshort

signed 16 bits

int

jint

signed 32 bits

long

jlong

signed 64 bits

float

jfloat

32 bits

double

jdouble

64 bits

void

void

N/A

  5.2 引用类型

在JNI里面,所有JNI引用类型其实都是jobject。

  5.3 Native方法参数

  • JNI接口指针是native方法的第一个参数,JNI接口指针的类型是JNIEnv。
  • 第二个参数取决于native method是否静态方法,如果是非静态方法,那么第二个参数是对对象的引用,如果是静态方法,则第二个参数是对它的class类的引用
  • 剩下的参数跟Java方法参数一一对应
extern "C" /* specify the C calling convention */
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (

     JNIEnv *env,        /* interface pointer */

     jobject obj,        /* "this" pointer */

     jint i,             /* argument #1 */

     jstring s)          /* argument #2 */
{

     const char *str = env->GetStringUTFChars(s, 0);

     ...

     env->ReleaseStringUTFChars(s, str);

     return ...

}

  5.4 签名描述

5.4.1 基础数据类型

Java类型

签名描述

boolean

Z

byte

B

char

C

short

S

int

I

long

J

float

F

double

D

void

 

5.4.2 引用数据类型

(以 L开头,以 ;结束,中间对应的是该类型的完整路径)

String : Ljava/lang/String;
Object : Ljava/lang/Object;
自定义类型 Area : Lcn/ctcdn/jni/Area;

5.4.3 数组

(在类型前面添加 [,几维数组就在前面添加几个 [)

int [] :[I
Long[][]  : [[J
Object[][][] : [[[Ljava/lang/Object

6 JNI常见用法

  6.1 jni访问java非静态成员变量

1.使用 GetObjectClass、 FindClass获取调用对象的类

2.使用 GetFieldID获取字段的ID。这里需要传入字段类型的签名描述。

3.使用 GetIntField、 GetObjectField等方法,获取字段的值。使用 SetIntField、 SetObjectField等方法,设置字段的值。

注意:即使字段是 private也照样可以正常访问。

extern "C"
JNIEXPORT void JNICALL
Java_cn_ctcdn_jni_JNIApi_testCallNoStaticField(JNIEnv *env, jobject instance) {
    //获取jclass
    jclass j_class = env->GetObjectClass(instance);
    //获取jfieldID
    jfieldID j_fid = env->GetFieldID(j_class, "noStaticField", "I");
    //获取java成员变量int值
    jint j_int = env->GetIntField(instance, j_fid);
    LOGI("noStaticField==%d", j_int);

    //Set<Type>Field    修改noStaticKeyValue的值改为888
    env->SetIntField(instance, j_fid, 888);
}

  6.2 jni访问java静态成员变量

1.使用 GetObjectClass、 FindClass获取调用对象的类

2.使用 GetStaticFieldID获取字段的ID。这里需要传入字段类型的签名描述。

3.使用 GetStaticIntField、 GetStaticObjectField等方法,获取字段的值。使用 SetStaticIntField、 SetStaticObjectField等方法,设置字段的值。

  6.3 jni调用java非静态成员方法

1.使用 GetObjectClass、 FindClass获取调用对象的类

2.使用 GetMethodID获取方法的ID。这里需要传入方法的签名描述。

3.使用 CallVoidMethod执行无返回值的方法,使用 CallIntMethod、 CallBooleanMethod等执行有返回值的方法。

extern "C"
JNIEXPORT void JNICALL
Java_cn_ctcdn_jni_JNIApi_testCallParamMethod(JNIEnv *env, jobject instance) {
    // 回调JNIApi中的noParamMethod
    jclass clazz = env->FindClass("com/xuexiang/jnidemo/JNIApi");
    if (clazz == NULL) {
        printf("find class Error");
        return;
    }
    jmethodID id = env->GetMethodID(clazz, "paramMethod", "(I)V");
    if (id == NULL) {
        printf("find method Error");
        return;
    }
    env->CallVoidMethod(instance, id, ++number);
}

  6.4 jni调用java静态成员方法

1.使用 GetObjectClass、 FindClass获取调用对象的类

2.使用 GetStaticMethodID获取方法的ID。这里需要传入方法的签名描述。

3.使用 CallStaticVoidMethod执行无返回值的方法,使用 CallStaticIntMethod、 CallStaticBooleanMethod等执行有返回值的方法。

  6.5 jni调用java构造方法

1.使用 FindClass获取需要构造的类

2.使用 GetMethodID获取构造方法的ID。方法名为 <init>, 这里需要传入方法的签名描述。

3.使用 NewObject执行创建对象。

extern "C"
JNIEXPORT jint JNICALL
Java_cn_ctcdn_jni_JNIApi_testCallConstructorMethod(JNIEnv *env, jobject instance) {
    // 获取jclass
    jclass j_class = env->FindClass("com/xuexiang/jnidemo/Area");
    // 找到构造方法jmethodID   public Area(int width, int height)
    jmethodID j_constructor_methoid = env->GetMethodID(j_class, "<init>", "(II)V");
    // 初始化java类构造方法  public Area(int width, int height)
    jobject j_Area_obj = env->NewObject(j_class, j_constructor_methoid, 2, 10);

    // 找到getArea()  jmethodID
    jmethodID j_getArea_methoid = env->GetMethodID(j_class, "getArea", "()I");
    // 调用java中的   public int getArea() 获取面积
    jint j_area = env->CallIntMethod(j_Area_obj, j_getArea_methoid);
    LOGI("面积==%d", j_area);//面积==20
    return j_area;
}

  6.6 jni引用全局变量

1.使用 NewGlobalRef创建全局引用,使用 NewLocalRef创建局部引用。

2.局部引用,通过DeleteLocalRef手动释放对象;全局引用,通过DeleteGlobalRef手动释放对象。

3.引用不主动释放会导致内存泄漏。

  6.7 jni异常处理

1.使用 ExceptionOccurred进行异常的检测。注意,这里只能检测java异常。

2.使用 ExceptionClear进行异常的清除。

3.使用 ThrowNew来上抛异常。

注意, ExceptionOccurred和 ExceptionClear一般是成对出现的,类似于java的try-catch。

//上抛java异常
void throwException(JNIEnv *env, const char *message) {
    jclass newExcCls = env->FindClass("java/lang/Exception");
    env->ThrowNew(newExcCls, message);
}

extern "C"
JNIEXPORT void JNICALL
Java_cn_ctcdn_jni_JNIApi_jniTryCatchException(JNIEnv *env, jobject instance) {

    // 获取jclass
    jclass j_class = env->GetObjectClass(instance);
    // 获取jfieldID
    jfieldID j_fid = env->GetFieldID(j_class, "method", "Ljava/lang/String666;");

    // 检测是否发生Java异常
    jthrowable exception = env->ExceptionOccurred();
    if (exception != NULL) {
        LOGE("jni发生异常");
        // jni清空异常信息
        env->ExceptionClear(); // 需要和ExceptionOccurred方法成对出现
        throwException(env, "native出错!");
    }
}

  6.8 日志打印

#include <android/log.h> // 引用android log

// 定义日志打印的方法
#define TAG "CMake-JNI" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型

LOGE("jni发生异常"); // 日志打印
0条评论
作者已关闭评论
8****m
4文章数
0粉丝数
8****m
4 文章 | 0 粉丝
8****m
4文章数
0粉丝数
8****m
4 文章 | 0 粉丝
原创

如何在Android下进行JNI开发

2023-07-31 06:30:57
5
0

在进行Android开发的过程中,我们必定会遇到视频图像处理、高强度密集运算、特殊算法等场景,这时我们就不得不需要去接触一些C/C++代码,进行JNI开发。下面我们将一起看看JNI如何进行开发。

1. 编写JNI接口和C/C++代码

1) JNI接口

package cn.ctcdn.jni;

public class JNIApi {

    public native String stringFromJNI();
}

2) C/C++代码

extern "C" JNIEXPORT jstring
JNICALL
Java_cn_ctcdn_jni_JNIApi_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

2. 编写CMakeLists.txt文件

# TODO 设置构建本机库文件所需的 CMake的最小版本
cmake_minimum_required(VERSION 3.4.1)

# TODO 添加自己写的 C/C++源文件
add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

# TODO 依赖 NDK中的库
find_library( log-lib
              log )

# TODO 将目标库与 NDK中的库进行连接
target_link_libraries( native-lib
                       ${log-lib} )

3. build.gradle的配置

android {
    ...
    defaultConfig {
        ...

        ndk {
            // Android 支持的 CPU 架构
            abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
        }
    }
    buildTypes {
        ...
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

4. .SO库的使用

static {
    System.loadLibrary("native-lib");
}

5. JNI基础语法

  5.1 基础类型

Java类型

native类型

描述

boolean

jboolean

unsigned 8 bits

byte

jbyte

signed 8 bits

char

jchar

unsigned 16 bits

short

jshort

signed 16 bits

int

jint

signed 32 bits

long

jlong

signed 64 bits

float

jfloat

32 bits

double

jdouble

64 bits

void

void

N/A

  5.2 引用类型

在JNI里面,所有JNI引用类型其实都是jobject。

  5.3 Native方法参数

  • JNI接口指针是native方法的第一个参数,JNI接口指针的类型是JNIEnv。
  • 第二个参数取决于native method是否静态方法,如果是非静态方法,那么第二个参数是对对象的引用,如果是静态方法,则第二个参数是对它的class类的引用
  • 剩下的参数跟Java方法参数一一对应
extern "C" /* specify the C calling convention */
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (

     JNIEnv *env,        /* interface pointer */

     jobject obj,        /* "this" pointer */

     jint i,             /* argument #1 */

     jstring s)          /* argument #2 */
{

     const char *str = env->GetStringUTFChars(s, 0);

     ...

     env->ReleaseStringUTFChars(s, str);

     return ...

}

  5.4 签名描述

5.4.1 基础数据类型

Java类型

签名描述

boolean

Z

byte

B

char

C

short

S

int

I

long

J

float

F

double

D

void

 

5.4.2 引用数据类型

(以 L开头,以 ;结束,中间对应的是该类型的完整路径)

String : Ljava/lang/String;
Object : Ljava/lang/Object;
自定义类型 Area : Lcn/ctcdn/jni/Area;

5.4.3 数组

(在类型前面添加 [,几维数组就在前面添加几个 [)

int [] :[I
Long[][]  : [[J
Object[][][] : [[[Ljava/lang/Object

6 JNI常见用法

  6.1 jni访问java非静态成员变量

1.使用 GetObjectClass、 FindClass获取调用对象的类

2.使用 GetFieldID获取字段的ID。这里需要传入字段类型的签名描述。

3.使用 GetIntField、 GetObjectField等方法,获取字段的值。使用 SetIntField、 SetObjectField等方法,设置字段的值。

注意:即使字段是 private也照样可以正常访问。

extern "C"
JNIEXPORT void JNICALL
Java_cn_ctcdn_jni_JNIApi_testCallNoStaticField(JNIEnv *env, jobject instance) {
    //获取jclass
    jclass j_class = env->GetObjectClass(instance);
    //获取jfieldID
    jfieldID j_fid = env->GetFieldID(j_class, "noStaticField", "I");
    //获取java成员变量int值
    jint j_int = env->GetIntField(instance, j_fid);
    LOGI("noStaticField==%d", j_int);

    //Set<Type>Field    修改noStaticKeyValue的值改为888
    env->SetIntField(instance, j_fid, 888);
}

  6.2 jni访问java静态成员变量

1.使用 GetObjectClass、 FindClass获取调用对象的类

2.使用 GetStaticFieldID获取字段的ID。这里需要传入字段类型的签名描述。

3.使用 GetStaticIntField、 GetStaticObjectField等方法,获取字段的值。使用 SetStaticIntField、 SetStaticObjectField等方法,设置字段的值。

  6.3 jni调用java非静态成员方法

1.使用 GetObjectClass、 FindClass获取调用对象的类

2.使用 GetMethodID获取方法的ID。这里需要传入方法的签名描述。

3.使用 CallVoidMethod执行无返回值的方法,使用 CallIntMethod、 CallBooleanMethod等执行有返回值的方法。

extern "C"
JNIEXPORT void JNICALL
Java_cn_ctcdn_jni_JNIApi_testCallParamMethod(JNIEnv *env, jobject instance) {
    // 回调JNIApi中的noParamMethod
    jclass clazz = env->FindClass("com/xuexiang/jnidemo/JNIApi");
    if (clazz == NULL) {
        printf("find class Error");
        return;
    }
    jmethodID id = env->GetMethodID(clazz, "paramMethod", "(I)V");
    if (id == NULL) {
        printf("find method Error");
        return;
    }
    env->CallVoidMethod(instance, id, ++number);
}

  6.4 jni调用java静态成员方法

1.使用 GetObjectClass、 FindClass获取调用对象的类

2.使用 GetStaticMethodID获取方法的ID。这里需要传入方法的签名描述。

3.使用 CallStaticVoidMethod执行无返回值的方法,使用 CallStaticIntMethod、 CallStaticBooleanMethod等执行有返回值的方法。

  6.5 jni调用java构造方法

1.使用 FindClass获取需要构造的类

2.使用 GetMethodID获取构造方法的ID。方法名为 <init>, 这里需要传入方法的签名描述。

3.使用 NewObject执行创建对象。

extern "C"
JNIEXPORT jint JNICALL
Java_cn_ctcdn_jni_JNIApi_testCallConstructorMethod(JNIEnv *env, jobject instance) {
    // 获取jclass
    jclass j_class = env->FindClass("com/xuexiang/jnidemo/Area");
    // 找到构造方法jmethodID   public Area(int width, int height)
    jmethodID j_constructor_methoid = env->GetMethodID(j_class, "<init>", "(II)V");
    // 初始化java类构造方法  public Area(int width, int height)
    jobject j_Area_obj = env->NewObject(j_class, j_constructor_methoid, 2, 10);

    // 找到getArea()  jmethodID
    jmethodID j_getArea_methoid = env->GetMethodID(j_class, "getArea", "()I");
    // 调用java中的   public int getArea() 获取面积
    jint j_area = env->CallIntMethod(j_Area_obj, j_getArea_methoid);
    LOGI("面积==%d", j_area);//面积==20
    return j_area;
}

  6.6 jni引用全局变量

1.使用 NewGlobalRef创建全局引用,使用 NewLocalRef创建局部引用。

2.局部引用,通过DeleteLocalRef手动释放对象;全局引用,通过DeleteGlobalRef手动释放对象。

3.引用不主动释放会导致内存泄漏。

  6.7 jni异常处理

1.使用 ExceptionOccurred进行异常的检测。注意,这里只能检测java异常。

2.使用 ExceptionClear进行异常的清除。

3.使用 ThrowNew来上抛异常。

注意, ExceptionOccurred和 ExceptionClear一般是成对出现的,类似于java的try-catch。

//上抛java异常
void throwException(JNIEnv *env, const char *message) {
    jclass newExcCls = env->FindClass("java/lang/Exception");
    env->ThrowNew(newExcCls, message);
}

extern "C"
JNIEXPORT void JNICALL
Java_cn_ctcdn_jni_JNIApi_jniTryCatchException(JNIEnv *env, jobject instance) {

    // 获取jclass
    jclass j_class = env->GetObjectClass(instance);
    // 获取jfieldID
    jfieldID j_fid = env->GetFieldID(j_class, "method", "Ljava/lang/String666;");

    // 检测是否发生Java异常
    jthrowable exception = env->ExceptionOccurred();
    if (exception != NULL) {
        LOGE("jni发生异常");
        // jni清空异常信息
        env->ExceptionClear(); // 需要和ExceptionOccurred方法成对出现
        throwException(env, "native出错!");
    }
}

  6.8 日志打印

#include <android/log.h> // 引用android log

// 定义日志打印的方法
#define TAG "CMake-JNI" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型

LOGE("jni发生异常"); // 日志打印
文章来自个人专栏
文章 | 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0