android studio创建NDK项目流程

15 篇文章 0 订阅
6 篇文章 1 订阅

目录

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
  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
使用Android Studio创建NDK项目的步骤如下: 1. 创建项目:在Android Studio中选择“Start a new Android Studio project”。 2. 添加NDK支持:在“Add an Activity to Mobile”页面中,选择“Native C++”作为Activity类型,然后单击“Next”。 3. 配置C++支持:在“Customize C++ Support”页面中,选择“C++11”作为C++标准库,并选择“None”作为异常支持。单击“Finish”。 4. 配置gradle文件:在项目的build.gradle文件中,添加以下代码: ``` externalNativeBuild { cmake { path "CMakeLists.txt" } } ``` 5. 配置CMakeLists.txt文件:在项目的CMakeLists.txt文件中,添加以下代码: ``` cmake_minimum_required(VERSION 3.4.1) add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp ) # Searches for a specified prebuilt library and stores the path as a # variable. Because system libraries are included in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log ) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in the # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. native-lib # Links the target library to the log library # included in the NDK. ${log-lib} ) ``` 6. 编写C++代码:在src/main/cpp目录下创建native-lib.cpp文件,并添加以下代码: ``` #include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapplication_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); } ``` 7. 在MainActivity.java文件中添加以下代码: ``` static { System.loadLibrary("native-lib"); } public native String stringFromJNI(); ``` 8. 运行应用程序:构建并运行应用程序,您将看到“Hello from C++”消息。 --相关问题--: 1. 如何在Android Studio中使用OpenCV? 2. 如何在NDK中使用CMake? 3.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

龚礼鹏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值