android studio创建NDK项目流程

本文详细介绍了如何在Android Studio中创建一个NDK项目,包括从新建Native C++项目到编译SO包,再到Java与JNI的交互。重点讲解了CMake配置、JNI方法调用、数据类型转换以及访问Java类的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.android studio新建NDK项目

①.创建新的项目,然后选择最下面的Native C++

 ②.然后一路next,最后点finish

​编辑​编辑③.下面项目就是显示一个字符串,字符串是native底层返回的

 ④.运行结果如下:

⑤.build.gradle中cmake相关编译内容,其实真正native层编译还是在cmake中进行的

​编辑 ⑥.刚运行的编译so包产物位置

2.CMake编译

①.编译so包

②.cmake相关编译so包讲解

③.新建一个Demo类的cpp文件并且与native-lib关联示例

 ④.运行结果

 3.java调用jni方法

①.java方法与jni方法对应关系

②.静态注册与动态注册

4.JAVA与JNI数据类型转换

①.基础数据类型

②.String类型java与jni互传

③.数组类型

5.JNI方法访问JAVA类,修改字段,调用方法

java层对应jni层签名:

1).基础数据类型:

2): 引用类型的描述符

jni调用java方法简单流程概要:

①.获取java的类,两种方式:

②.GetMethodID获取java类的所有方法 / GetFieldID获取java类的所有属性id

③.NewObject 创建java对象

④.调用方法或属性


1.android studio新建NDK项目

①.创建新的项目,然后选择最下面的Native C++

 ②.然后一路next,最后点finish

③.下面项目就是显示一个字符串,字符串是native底层返回的

 ④.运行结果如下:

⑤.build.gradle中cmake相关编译内容,其实真正native层编译还是在cmake中进行的

 ⑥.刚运行的编译so包产物位置

2.CMake编译

①.编译so包

②.cmake相关编译so包讲解

​​​ 注意真正的so包名称是“lib+上面的so包名称+.so”,shared是动态库,如果是static则是静态库

③.新建一个Demo类的cpp文件并且与native-lib关联示例

 ④.运行结果

 3.java调用jni方法

①.java方法与jni方法对应关系

JNIEnv:Java的本地化环境,C/C++可以通过它访问Java的类、对象、方法、属性等。每个线程对应一个JNIEnv,所以在jni层调用要注意线程。

②.静态注册与动态注册

静态注册:jni层方法名是java层包名+类名+方法名然后进行对应

弊端:jni层方法名过长,第一次调用需要java层去查找对应的jni层方法。此处两个弊端动态注册都能解决

动态注册:在动态库加载过程中会回调JNI_OnLoad方法,在此处调用RegisterNatives方法进行动态注册关联,不需要调用的时候在查找jni层方法。 

将上面的静态注册改成动态注册示例:

#include <jni.h>
#include <string>

#include <Demo.h>  //从搜索路径进行搜索
//#include "demo/Demo.h"  //从相对路径去搜索

//方法按照c的方式进行编译,防止出现连接失败的问题
//extern "C" JNIEXPORT jstring JNICALL
//包名+类名+方法名  env参数和jobject参数不能去掉!!!
//Java_com_example_myapplication_MainActivity_stringFromJNI(
//        JNIEnv *env,
//        jobject /* this */) {
//    Demo demo;
//    return env->NewStringUTF(demo.getString().c_str());
//}

//宏定义类名(包名+类名)
#define JAVA_CLASS "com/example/myapplication/MainActivity"

//jni方法
jstring getMessage(
        JNIEnv *env,
        jobject /* this */) {
    Demo demo;
    return env->NewStringUTF("动态注册测试");
}

//定义java方法和jni方法关联关系,中间的"()Ljava/lang/String;"是方法签名
static JNINativeMethod  method [] =  {
        {"stringFromJNI","()Ljava/lang/String;",(void *)getMessage}
};

//动态注册方法
int registerNativesMethods(JNIEnv *env, const char *name,
        JNINativeMethod *method, jint nMethods) {
    jclass jcls;
    jcls = env->FindClass(name);
    if (jcls == nullptr) {
        return JNI_FALSE;
    }
    if (env->RegisterNatives(jcls, method, nMethods) < JNI_OK) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

//动态库加载到虚拟机的时候会回调此方法
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env; //通过vm拿到env
    //失败返回false
    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_FALSE;
    }
    //类名全路径,java方法对应jni方法关联的数组,方法数
    //方法数可以动态获取:sizeof(method)/sizeof(method[0])  这样写可以以后不需要改这行代码
    registerNativesMethods(env, JAVA_CLASS, method, 1);
    return JNI_VERSION_1_6;//jni版本
}

JavaVM:java虚拟机(Android一个进程对应一个javaVm,即一个app对应一个javaVm) 可以获取jnienv{ getEnv() } 一个线程对应一个jnienv

4.JAVA与JNI数据类型转换

①.基础数据类型

java层到jni层可以直接使用,无需转换

根据源码可以看出来jni中的数据类型原始类型还是c/c++中的类型,所以jni中的类型可以很轻易的转换成c/c++类型

②.String类型java与jni互传

 示例代码:

java层

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(testString("java"));
    }

    public native String testString(String s);
}

native层代码:

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_testString(
        JNIEnv *env,
        jobject /* this */, jstring jst) {
    //获取java方法的string参数到c++中,因为java一般是utf格式,所以这里获取也是utf
    const char *str = env->GetStringUTFChars(jst, JNI_FALSE);
    //此处必须是[]而不能是* 因为*是不能修改的 []是可以修改的 下面的strcat方法会修改cString
    char cString [128] = "this is c style string ";
    //此处是将后一个字符串拼接到前一个并且修改前一个字符串
    strcat(cString, str);
    //所有的env->Get方法都对应这Release方法,用于释放内存
    //Get Characters函数将字符固定在内存中,直到调用Release方法. Java无法进行垃圾收集或以其他方式移动此数据,直到确定没有人使用它为止.
    env->ReleaseStringUTFChars(jst, str);
    return env->NewStringUTF(cString);
}

调用后的示例:

注意点:

native层获取String的方法有对应的释放内存方法,此方法必须要调用,不然会有内存泄漏。

③.数组类型

引用类型java与native层对应关系如下

可以看出java String[]对应jni中没有特殊的类型,需要用到的是jobjectArray类型

代码示例:

java层

public class MainActivity extends AppCompatActivity {
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(testString(new String[]{"java1","java2","java3"}));
    }

    public native String testString(String [] s);
}

jni层:

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_testString(
        JNIEnv *env,
        jobject /* this */, jobjectArray jstArray) {
    //获取数组长度
    jsize size = env->GetArrayLength(jstArray);
    //此处必须是[]而不能是* 因为*是不能修改的 []是可以修改的 下面的strcat方法会修改cString 还有128是必须要存在的 不然调用strcat会失败
    char cString [128] = "this is c style string";
    for (int i = 0; i < size; ++i) {
        //获取java的string数组中各个元素的jstring
        jstring jstring1 = static_cast<jstring>(env->GetObjectArrayElement(jstArray, i));
        //将jstring转换成c/c++的char类型指针
        const char *str = env->GetStringUTFChars(jstring1, JNI_FALSE);//得到字符串
        //此处是将后一个字符串拼接到前一个并且修改前一个字符串
        strcat(cString, " ");
        strcat(cString, str);
        //内存释放还是需要的
        env->ReleaseStringUTFChars(jstring1, str);
    }

    return env->NewStringUTF(cString);
}

5.JNI方法访问JAVA类,修改字段,调用方法

java层对应jni层签名:

1).基础数据类型:

2): 引用类型的描述符

对于一维数组,其为 :  [ + 其类型的域描述符 + ;

多维数组则是 n个[ +该类型的域描述符 , N代表的是几维数组

引用类型则为 L + 该类型类描述符 + ;   (注意,这儿的分号“;”是JNI的一部分)

例如:

String类型的域描述符为 Ljava/lang/String; 

int[ ]       其描述符为[I

float[ ]    其描述符为[F

String[ ]  其描述符为[Ljava/lang/String;

Object[ ] 其描述符为[Ljava/lang/Object;

多维数组:

int  [ ][ ] 其描述符为[[I

float[ ][ ] 其描述符为[[F

示例代码:

java层:

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        TestObject object = new TestObject();
        testObject(object);
        tv.setText(object.toString());
    }

    public native void testObject(TestObject object);
}


public class TestObject {

    public static int testInt = 1;
    public int testInt2 = 2;
    public String testString = "java";

    @Override
    public String toString() {
        return "TestObject{" +
                "testInt2=" + testInt2 +
                ", testString='" + testString + '\'' +
                '}' + "static testInt" + testInt;
    }
}

jni层:

extern "C"
JNIEXPORT void JNICALL
//其实此处的第一个参数this是MainActivity类型,如果要修改MainActivity示例中的参数可以根据第二个参数修改,无需重新传值
//如果是静态方法第二个参数是clazz,可以直接用于调用MainActivity的静态属性和方法(不能调用非静态,因为没有实例)
Java_com_example_myapplication_MainActivity_testObject(JNIEnv *env, jobject thiz, jobject object) {
    //根据实例获取类
    jclass clazz = env->GetObjectClass(object);

    //根据类获取属性id(类,属性名称,属性类型)
    jfieldID testIntId = env->GetFieldID(clazz, "testInt2", "I");
    //根据实例和属性id获取属性的参数(实例,属性id)
    int num = env->GetIntField(object, testIntId);
    //修改属性的参数(实例,属性id,参数)
    env->SetIntField(object, testIntId, ++num);

    //根据类获取属性id(类,属性名称,属性类型)
    jfieldID testStringId = env->GetFieldID(clazz, "testString", "Ljava/lang/String;");
    //根据实例和属性id获取属性的参数
    jstring testString = static_cast<jstring>(env->GetObjectField(object, testStringId));
    //将jstring转换成c/c++的char类型指针
    const char *str = env->GetStringUTFChars(testString, JNI_FALSE);//得到字符串
    char cString[128] = "c++ ";
    strcat(cString, str);
    //修改属性的参数(实例,属性id,参数)
    env->SetObjectField(object, testStringId, env->NewStringUTF(cString));

    //根据类获取静态属性id(类,属性名称,属性类型) 注意此处方法是多了个Static
    jfieldID testStaticIntId = env->GetStaticFieldID(clazz, "testInt", "I");
    //根据类和属性id获取属性的参数(类,属性id) 注意此处是类
    int num2 = env->GetStaticIntField(clazz, testStaticIntId);
    //修改属性的参数(类,属性id,参数) 注意此处是类
    env->SetStaticIntField(clazz, testStaticIntId, --num2);
}

最后测试显示结果:

jni调用java方法简单流程概要:

①.获取java的类,两种方式:

Ⅰ.直接参数传递object然后通过 env->GetObjectClass(object);方式

Ⅱ.通过包名+类名然后通过env->FindClass(JNI_CLASS_PATH);方式

②.GetMethodID获取java类的所有方法 / GetFieldID获取java类的所有属性id

③.NewObject 创建java对象

④.调用方法或属性

Call<Type>Method
Get<Type>Field
Set<Type>Field
Call<TYPE> Method
[G/S]et<Type> Field
<think>嗯,用户想了解Android Native开发或者JNI的入门教程。首先,我需要整理一下手头的引用资料,看看哪些信息能帮到他。用户提到的引用有五个,都是关于JNI和NDK的。 首先,我应该明确几个概念:NDK是开发工具包,允许在Android中使用C/C++代码,而JNI是Java和本地代码交互的接口。根据引用[1],NDK是工具集,JNI是它的一部分。所以教程需要从NDK安装开始,然后讲JNI的使用步骤。 接下来,用户可能需要一个简单的入门步骤,像创建项目、配置NDK、编写native方法、生成头文件、实现C代码、编译成so库,最后在Java中调用。引用[2]和[3]提到创建项目并实现JNI示例,这可能作为步骤参考。需要详细说明每个步骤,比如在Android Studio中如何配置NDK,可能需要修改build.gradle文件,添加CMakeLists.txt,这些在引用[3]中应该提到过。 然后,用户可能关心JNI的应用场景,比如性能优化、加密库、网络库等,引用[4]和[5]里有提到这些情况,应该列举出来,让用户知道为什么需要学习JNI。 另外,用户可能遇到的问题,比如环境配置错误、JNI函数命名规则、内存管理,这些需要作为注意事项提醒。引用[2]提到需要一定的学习和实践,所以可能常见错误需要强调。 还要生成相关问题,根据引用里的内容,可能的问题包括NDK和JNI的区别、JNI函数命名规则、多线程处理、性能优化等。需要确保问题相关且有答案来源。 最后,检查是否所有数学公式都用了正确的LaTeX格式,但根据用户问题,可能这部分内容不多,不过如果有的话要确认格式正确。回答结构要清晰,分步骤,注意事项,应用场景,相关问题,这样用户容易理解。</think>### Android JNI开发入门指南 #### 一、开发环境搭建 1. 安装Android Studio 4.0+(自带NDK支持) 2. 在SDK Manager中勾选: - NDK(Native Development Kit) - CMake(跨平台编译工具) - LLDB(本地代码调试器) 3. 验证安装:在终端执行`ndk-build --version`查看NDK版本[^2] #### 二、创建JNI项目步骤 1. 新建项目时选择**Native C++**模板 2. 配置`CMakeLists.txt`: ```cmake cmake_minimum_required(VERSION 3.10.2) add_library(native-lib SHARED native-lib.cpp) find_library(log-lib log) target_link_libraries(native-lib ${log-lib}) ``` 3. Java层声明native方法: ```java public native String stringFromJNI(); ``` #### 三、JNI函数实现 1. 自动生成头文件: ```bash javac -h . MainActivity.java ``` 2. 实现C++函数: ```cpp #include <jni.h> extern "C" JNIEXPORT jstring JNICALL Java_com_example_app_MainActivity_stringFromJNI(JNIEnv* env, jobject /* this */) { return env->NewStringUTF("Hello from C++"); } ``` 3. 函数命名规范:`Java_包名_类名_方法名`[^3] #### 四、常见应用场景 1. 高性能计算(图像处理/物理引擎) 2. 使用现有C/C++库(如OpenCV) 3. 硬件级操作(传感器/加密芯片) 4. 跨平台代码复用(如游戏引擎)[^4][^5] #### 五、注意事项 1. JNI方法必须通过`System.loadLibrary("native-lib")`加载 2. 数据类型转换需谨慎(如jstring ↔ char*) 3. 使用`JNIEnv`指针时注意线程安全 4. 推荐使用`ndk-gdb`进行本地代码调试 ```mermaid graph TD A[Java代码] -->|JNI调用| B(JNI接口层) B --> C[C/C++实现] C --> D[本地机器码] D --> E[硬件执行] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚礼鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值