写点什么

JNI 与 NDK 入门(一),设计思想与代码质量优化 + 程序性能优化 + 开发效率优化

用户头像
Android架构
关注
发布于: 刚刚

cmake {


cppFlags ""


}


}


}


buildTypes {


release {


minifyEnabled false


proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'


}


}


externalNativeBuild {


cmake {


path "CMakeLists.txt"


}


}


}


dependencies {


implementation fileTree(dir: 'libs', include: ['*.jar'])


implementation 'com.android.support:appcompat-v7:27.1.1'


implementation 'com.android.support.constraint:constraint-layout:1.1.3'


testImplementation 'junit:junit:4.12'


androidTestImplementation 'com.android.support.test<span class="emoji emoji-sizer" style="background-image:url(/emoji-data/img-apple-64/1f3c3.png)" data-codepoints="1f3c3"></span>1.0.2'


androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'


}


开发 Native 代码



在 Java 文件中声明 native 方法

我们首先需要在 Java 代码的类中通过 static 块来加载我们的 Native 库。可以通过如下代码,其中 loadLibrary 的参数是在 CMakeList.txt 中定义的 Native 库的名称


static {


System.loadLibrary("native-lib");


}


之后,我们便可以在这个类中声明 Native 方法


public native String getStringFromJNI();

创建 CMakeList.txt

我们还需要在 src 中创建一个 CMakeList.txt 文件,这个文件约束了 Native 语言源文件的编译规则。比如下面


cmake_minimum_required(VERSION 3.4.1)


add_library(native-lib SHARED src/main/cpp/native-lib.cpp)


find_library(log-lib log)


target_link_libraries(native-lib ${log-lib})


add_library方法中定义了一个 so 库,它的名称是 native-lib,也就是我们在 Java 文件中用到的字符串,而后面则跟着这个库对应的 Native 文件的路径


find_library则是定义了一个路径变量,经过了这个方法,log-lib 这个变量中的值就是 Android 中 log 库的路径


target_link_libraries则是将 native-lib 这个库和 log 库连接了起来,这样我们就能在 native-lib 中使用 log 库的方法。

创建 Native 方法文件

在前面的 CMake 文件中可以看到,我们把文件放在了 src/main/cpp/,因此我们创建 cpp 这个目录,在里面创建 C++源文件 native-lib.cpp。


然后, 我们便可以开始编写如下的代码:


#include <jni.h>


#include <string>


extern "C"{


JNIEXPORT jstring JNICALL


Java_com_n0texpecterr0r_ndkdemo_Ma


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


inActivity_getStringFromJNI(


JNIEnv* env,


jobject) {


std::string hello = "IG 牛逼";


return env->NewStringUTF(hello.c_str());


}


}


此处我们使用的是 C++语言,让我们来看看具体的代码。


首先我们引入了 jni 需要的 jni.h,这个头文件中声明了各个 jni 需要用到的函数。同时我们引入了 C++中的 string.h。


然后我们看到 extern "C"。为了了解这里为什么使用了 extern "C",我们首先需要知道下面的知识:


在 C 中,编译时的函数签名仅仅是包含了函数的名称,因此不同参数的函数都是同样的签名。这也就是为什么 C 不支持重载。


而 C++为了支持重载,在编译的时候函数的签名除了包含函数的名称,还携带了函数的参数及返回类型等等。


试想此时我们有个 C 的函数库要给 C++调用,会因为签名的不同而找不到对应的函数。因此,我们需要使用extern "C"来告诉编译器使用编译 C 的方式来连接。


接下来我们看看 JNIEXPORT 和 JNICALL 关键字,这两个关键字是两个宏定义,他主要的作用就是说明该函数为 JNI 函数。


而 jstring 则对应了 Java 中的 String 类,JNI 中有很多类似 jstring 的类来对应 Java 中的类,下面是 Java 中的类与 JNI 类型的对照表



我们继续看到函数名Java_com_n0texpecterr0r_ndkdemo_MainActivity_getStringFromJNI。其实函数名中的_相当于 Java 中的 . 也就是这个函数名代表了java.com.n0texpecterr0r.ndkdemo.MainActivity.java中的getStringFromJNI方法,也就是我们之前定义的 native 方法。


格式大概如下:


Java_包名_类名_需要调用的方法名


其中,Java 必须大写,包名里的.要改成__要改成_1


接下来我们看到这个函数的两个参数:


  • JNIEnv* env:代表了 JVM 的环境,Native 方法可以通过这个指针来调用 Java 代码

  • jobject obj:它就相当于定义了这个 JNI 方法的类 (MainActivity) 的 this 引用


然后可以看到后面我们创建了一个 string hello,之后通过env->NewStringUTF(hello.c_str())方法创建了一个 jstring 类型的变量并返回。

在 Java 代码中调用 native 方法

接着,我们便可以在 MainActivty 中像调用 Java 方法一样调用这个 native 方法


TextView tv = findViewById(R.id.sample_text);


tv.setText(getStringFromJNI());


我们尝试运行,可以看到,我们成功用 C++构建了一个字符串并返回给 Java 调用:


[图片上传失败...(image-cf0641-1628687485525)]


CMake




我们在 NDK 开发中使用 CMake 的语法来编写简单的代码描述编译的过程,由于这篇文章是讲 NDK 的,所以关于 CMake 的语法就不再赘述了。。。如果想要了解 CMake 语法可以学习这本书《CMake Practice


JNI 与 Java 代码交互



方法签名

概念


在我们 JNI 层调用一个方法时,需要传递一个参数——方法签名。


为什么要使用方法签名呢?因为在 Java 中的方法是可以重载的,两个方法可能名称相同而参数不同。为了区分调用的方法,就引入了方法签名的概念。


签名规则

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
JNI 与 NDK 入门(一),设计思想与代码质量优化+程序性能优化+开发效率优化