【新手入门】Android基础知识(三):JNI(Java Native Interface)

目          录

一、核心要素

1. Native 方法声明

2. JNI函数命名规则

3. JNIEnv

4.jobject 类型

二、数据类型映射

1. 基本数据类型映射 

2.引用类型映射

三、函数签名

签名规则:

 四、开发流程


        JNI(Java Native Interface)是一种编程框架,允许Java代码和其他语言(如C和C++)编写的代码进行交互。它在Java和原生代码之间提供了一个桥梁,使得开发者可以在Java应用程序中调用和访问原生代码(通常是C或C++代码),同时也可以从原生代码中调用Java代码。

        JNI的主要目的是在Java虚拟机(JVM)中嵌入本地代码。通过JNI,可以在Java代码中声明本地方法,在本地方法实现中编写底层的本地代码。这样,Java代码可以直接调用本地方法,而本地方法中的本地代码可以与操作系统和硬件交互,执行一些Java无法直接完成的任务,例如访问系统资源、调用特定的库函数等。

一、核心要素

1. Native 方法声明

JAVA代码中声明native方法,在 C/C++ 代码中进行方法实现。 

public class MainActivity extends AppCompatActivity {
    private String name = "Chesnut";
    
    // 声明native方法
    public native void changeName();
    public static native void changeAge();
}

注意事项 :

  • 访问权限修饰符:native方法可以被public、protected、private修饰,其访问规则与普通 Java 方法一致。
  • 静态与实例方法:支持声明静态native方法,格式为public static native String getVersion(),调用时无需实例化对象,通过 类名.方法名() 即可调用。 
  • 参数与返回值:可以包含任意 Java 数据类型的参数和返回值(基本类型、引用类型均可),但需遵循 JNI 数据类型映射规则。 
  • 方法名避免特殊字符:native方法名若包含下划线 “_”,生成的 JNI 函数名需通过转义处理(如native void a_b()对应 JNI 函数Java_xxx_a_1b),建议命名时尽量使用驼峰式。

2. JNI函数命名规则

JNI 函数的命名有特定的规则,以便 Java 虚拟机能够正确识别和关联对应的native方法。其基本格式为:Java_<包名>_<类名>_<方法名>,其中包名中的 “.” 需要替换为 “_”。

Java_com_example_jnidemo_MainActivity_changeName(
        JNIEnv * env,   // jni桥梁
        jobject thiz) { }
  • 包名转义:若包名包含下划线(如com.example.my_jni),需在 JNI 函数名中用 “_1” 代替下划线。 
  • 内部类处理:若native方法属于内部类,类名需用 “ ”连接外部类与内部类。
  • 函数修饰符:JNI 函数必须添加JNIEXPORT和JNICALL宏(定义在jni.h中),确保函数能被 Java 虚拟机识别和调用。

3. JNIEnv

JNIEnv是 JNI 接口指针,它是一个指向函数表的指针,包含了大量的 JNI 函数,用于在 C/C++ 代码中操作 Java 对象、调用 Java 方法等。在 JNI 函数中,第一个参数通常是JNIEnv*。

  • 字符串处理:NewStringUTF、GetStringUTFChars、ReleaseStringUTFChars
  • 类操作:FindClass、GetObjectClass
  • 字段操作:GetFieldID、GetIntField、SetIntField
  • 方法操作:GetMethodID、CallIntMethod
extern "C"  // 下面代码采用C的编译方法
JNIEXPORT   //JNIEXPORT JNI重要关键字,不能少
void JNICALL // JNICALL关键字,用于约束函数入栈顺序和堆栈内存清理的规则
// Java 包名 com_xxx_1xxx 类名 方法名
// jobject: MainActivity this 实例调用
// jclss:MainActivity class 类调用
Java_com_example_jnidemo_MainActivity_changeName(
        JNIEnv * env,   // jni桥梁
        jobject thiz) { 
//  C语言中的JNIEnv * env是二级指针
// (*env)->DeleteLocalRef(NULL);
// C++中的JNIEnv * env是一级指针
    env->DeleteLocalRef(NULL);
}

注意事项:

  • C 与 C++ 语法差异:C 语言中需显式解引用函数表,如(*env)->NewStringUTF(env, "test");C++ 中env被封装为类指针,可直接调用env->NewStringUTF("test")。 
  • 避免野指针:JNIEnv的生命周期与当前线程绑定,线程结束后env指针失效,继续使用会导致内存访问错误。 
  • 检查函数返回值:部分 JNI 函数在失败时返回NULL(如FindClass找不到类时),需添加判空逻辑避免崩溃。

4.jobject 类型

        jobject代表 Java 对象在 JNI 中的引用。在实例方法的 JNI 函数中,第二个参数是jobject类型,它指向调用该方法的 Java 对象本身。而在静态方法的 JNI 函数中,第二个参数是jclass类型,指向该静态方法所属的类。jobject可强制转换为更具体的引用类型(如jstring、jarray),但需确保类型匹配,否则会导致运行时错误。

extern "C"
JNIEXPORT void JNICALL
Java_com_example_jnidemo_MainActivity_changeAge(JNIEnv *env, jclass clazz) {
    // TODO: implement changeAge()

    // 静态函数不存在this
    // jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
    jfieldID ageFid = env->GetStaticFieldID(clazz, "age", "I");

    //    jint value = 29;
    //  void SetIntField(jobject obj, jfieldID fieldID, jint value)
    env->SetStaticIntField(clazz,ageFid, env->GetStaticIntField(clazz, ageFid) + 1);

//    int result = env->GetStaticIntField(clazz, ageFid);
//
//    LOGD("result:%i\n", result);
}

二、数据类型映射

        Java 和 C/C++ 的数据类型存在差异,在 JNI 中需要进行相应的映射,以确保数据能够正确传递。

1. 基本数据类型映射 

Java 类型JNI 类型C/C++ 类型
boolean jbooleanunsigned char
bytejbytesigned char
charjcharunsigned short
shortjshortshort
intintint
longjlonglong long
floatjfloatfloat
doublejdoubledouble

2.引用类型映射

Java 的引用类型(如类、对象、数组等)在 JNI 中也有对应的类型: 

  • jobject:对应 Java 中的 Object 类型。 
  • jclass:对应 Java 中的 Class 类型。 
  • jstring:对应 Java 中的 String 类型。 
  • jarray:对应 Java 中的数组类型,此外还有jintArray、jbyteArray等具体数组类型。

三、函数签名

        在 JNI 编程中,函数签名是 Java 与 C/C++ 代码交互的 “暗号”,用于唯一标识方法的参数类型和返回值类型。当从 C/C++ 调用 Java 方法时(如通过GetMethodID获取方法 ID),必须提供正确的函数签名。

签名规则:

  • 基础类型:如int对应I,long对应J
  • 引用类型:以L开头,以;结尾,中间为完整类路径,如String对应Ljava/lang/String;
  • 数组类型:在类型前加[,如int[]对应[I,二维数组long[][]对应[[J
  • 函数签名的格式为:(参数类型列表)返回值类型 

基本类型和引用类型的表示规则如下:

Java 类型 签名表示示例
boolean Z
byte        B
char    C
short    S
int    I
long    J
float    F
double    D
void    V
引用类型(类)L 全类名;String → Ljava/lang/String;
数组类型[元素类型;int[] → [I;String[] → [Ljava/lang/String;

 四、开发流程

  1. 设置开发环境:在 Android Studio 中安装 Android NDK,并配置好相关开发环境。
  2. 创建或导入项目:在 Android Studio 中创建一个新的 Android Native 项目,或者将现有的 C/C++ 代码导入到一个新的Android项目中。
  3. 编写 C/C++ 代码:在项目中编写 C/C++ 代码,实现你的功能和逻辑。
  4. 配置构建脚本:使用 CMake 配置构建脚本,指定 C/C++ 代码的编译选项和依赖库
// app/build.gradle
externalNativeBuild {
    cmake {        //  CMake 构建系统
        path file('src/main/cpp/CMakeLists.txt')  // 指定 CMakeLists.txt 文件, 该文件定义了如何构建项目中的 C++ 代码
        version '3.22.1'
    }
}

      5. 编译和运行:通过 Android Studio 的构建工具编译你的项目,并在模拟器或实际设备上运行。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值