锲
对一个女人而言,和心爱的人在一起的幸福就是最重大的了。女人永远不需要悲悲壮壮的轰轰烈烈,只要温温柔柔地在一起开开心心。
正文
相对于 Android NDK 的枯燥乏味,大家更热衷于男人之间的那些事。那么男人之间又有哪些事呢?
1. Hello from c++
我们一般需要了解运行一个 NDK
项目需要做什么,有哪些配置以及配置的具体含义。 Android Studio(2.2以上版本)提供两种方式编译原生库:CMake( 默认方式) 和 ndk-build。对于初学者可以先了解 CMake
编译的常见指令,所谓工欲善其事必先利其器。
申明一个 native
的方法非常简单 public native String getNDKString();
直接使用 AndroidStudio
预提示生成 cpp
文件,同时也可以通过 javah
命令生成 cpp
文件,如下:
public class NDKTest {
public native String getNDKString();
}
javah
命令,注意 cd
到 ...\app\src\main\java
,执行 javah 包名(.号连接)+.类名
D:\AndroidSpace\NDKDemo\app\src\main\java>javah com.demo.ndkdemo.NDKTest
这里列出项目中涉及 NDK
的内容或配置几点需要注意的地方:
.externalNativeBuild
文件是CMake
编译好的文件,显示支持的各种硬件平台的信息,如ARM
、x86
等;cpp
文件是放置native
文件的地方,名字可以修改成其他的(只要里面函数名字对应Java native
方法);
CMakeLists.txt
,AS自动生成的CMake
脚本配置文件;
# 指定cmake的最小版本号
cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library. ——> 生成函数库名称,so 库名称
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 ) # native-lib 的文件路径
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 )
target_link_libraries( # Specifies the target library. 目标库名称
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} ) # 需要链接库的名称 ${log-lib} 引用的变量 log-lib ,多个库的引用以逗号隔开
build.gradle
文件,注意两个 externalNativeBuild {}
的节点配置
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.demo.ndkdemo"
minSdkVersion 15
...
externalNativeBuild {
cmake {
// 如果使用 C++11 标准,则改为 "-std=c++11"
cppFlags ""
// 生成.so库的目标平台,注意 x86 已经不支持
abiFilters "armeabi-v7a", "armeabi"
}
}
}
...
externalNativeBuild {
cmake {
// 配置 CMake 文件的路径
path "CMakeLists.txt"
}
}
}
local.properties
新增了 ndk
路径的引用
ndk.dir=D\:\\AndroidSdk\\ndk-bundle
sdk.dir=D\:\\AndroidSdk
MainActivity
调用 so
库
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
// 注意静态修饰符
public native static String stringStaticFromJNI();
}
需要注意的是 JNI
的函数命名规则:JNIEXPORT 返回值 JNICALL Java_全路径类名_方法名_参数签名(JNIEnv* , jobject, 其它参数);
其中第一个参数 JNIEnv
是 jni.h
文件最重要的部分,它的本质是指向函数表指针的指针(JavaVM 也是),函数表里面定义了很多 JNI
函数,同时它也是区分 C
和 C++
环境的,在 C
语言环境中,JNIEnv
是 strut JNINativeInterface*
的指针别名。
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv; //C++中的 JNIEnv 类型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv; //C语言的 JNIEnv 类型
typedef const struct JNIInvokeInterface* JavaVM;
#endif
第二个参数,当 java native 方法为静态时,为 jclass,当为非静态方法时,为 jobject ,纸上得来终觉浅,绝知此事要躬行,接下来简单列举几个例子。
2. JNI日志打印
工欲善其事必先利其器,日志的重要性不言而喻,在 CMake
中引用了 log
库,那么可以引用头文件 #include <android/log.h>
对 log.h
进行封装
#ifndef NDKDEMO_LOG_H
#define NDKDEMO_LOG_H
#define LOG_TAG "JNI_LOG"
#include <android/log.h>
// 定义各种类型 Log 的函数别名
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG ,__VA_ARGS__)
#endif
引入头文件 #include "log.h"
就可以使用 LOG
函数进行日志打印。
3. JNI函数访问Java对象的变量
Java
对象的变量又分为静态变量与非静态变量,JNI
函数访问 Java
对象的变量步骤如下:
- 1)通过
env->GetObjectClass(jobject);
获取Java
对象的class
实例,返回一个jclass
; - 2)调用
env->GetFieldID(jclass clazz, const char* name, const char* sig)
得到该域(变量)的id
,返回一个jfieldID
;如果变量是静态的,则调用方法GetStaticFieldID
- 3)通过调用
Get{type}Field(jobject obj, jfieldID fieldID)
获取到该变量的值,其中{type}
为变量的类型( 基本数据类型与引用类型object
);如果是静态变量,则调用GetStatic{type}Field(jclass, fieldId)
,注意第一个参数是jclass
- 4)最后通过
3)
获取的变量值进行变换;也可以调用Set{type}Field(jobject obj, jfieldID fieldID, jint value)
设置该变量的值,如果变量是静态的,则调用SetStaticIntField
3.1访问某个非静态变量,返回处理后的值
java 层 native 方法定义和调用:
private int age = 18;
public native int ageFromJNI();
Log.e(TAG, "调用前:age = " + age);
Log.e(TAG, "调用后:age = " + ageFromJNI());
c++ 层:
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_ndkdemo_MainActivity_ageFromJNI(JNIEnv *env, jobject instance) {
// TODO
// 获取到 MainActivity 对应的 class
jclass jclazz = env->GetObjectClass(instance);
// 获取到域 ID
jfieldID ageFieldID = env->GetFieldID(jclazz, "age", "I");
// 获取到 FieldID 对应的值
jint age = env->GetIntField(instance, ageFieldID);
// env->SetIntField()
age++;
return age;
}
输出结果:
调用前:age = 18
调用后:age = 19
3.2访问一个静态变量,并对其修改
java 层 native 方法定义和调用:
private static String name = "hello android";
public native void nameFromJni();
Log.e(TAG, "调用前:age = " + name);
nameFromJni();
Log.e(TAG, "调用后:age = " + name);
c++ 层:
extern "C"
JNIEXPORT void JNICALL
Java_com_demo_ndkdemo_MainActivity_nameFromJni(JNIEnv *env, jobject instance) {
// TODO
jclass jclazz = env->GetObjectClass(instance);
// 获取到静态域 ID
jfieldID nameFieldID = env->GetStaticFieldID(jclazz, "name", "Ljava/lang/String;");
// 获取到 name 的值
jstring name = static_cast<jstring>(env->GetStaticObjectField(jclazz, nameFieldID));
// 字符串转换成字符
const char *str = env->GetStringUTFChars(name, JNI_FALSE);
char ch[30] = "google , ";
strcat(ch, str);
// 生成字符串
jstring name_str = env->NewStringUTF(ch);
// 重新设置 name 的值
env->SetStaticObjectField(jclazz, nameFieldID, name_str);
}
输出结果:
调用前:age = hello android
调用后:age = google , hello android
4.JNI反射调用Java方法
抛砖引玉,通过 JNI
反射调用 Java
的静态方法与非静态方法,步骤如下:
- 1)通过
env->FindClass(const char* name);
获取Java
对象的class
实例,返回一个jclass
,注,参数name
格式为包名(以"/"分割)+ 类名
,不能是L + 包名(以"/"分割)+ 类名 + ;
的形式 - 2)调用
env->GetMethodID(jclass clazz, const char* name, const char* sig)
得到构造函数方法id
,返回一个jmethodID
,如果是静态方法则不需要此步骤 - 3)通过调用
env->NewObject(jclass clazz, jmethodID methodID, ...)
新建类的实例对象,返回jobject
类型,如果是静态方法则不需要此步骤 - 4)通过调用
env->GetMethodID(jclass clazz, const char* name, const char* sig)
得到被调用的方法id
,返回一个jmethodID
类型,如果是静态方法则调用GetStaticMethodID
- 5)最后通过
env->Call{type}Method(jobject, jmethod, param...)
调用Java
的方法;如果是static
方法,则使用CallStatic{type}Method(jclass, jmethod, param...)
,使用的是jclass
4.1反射调用Java非静态方法
java 层 Student 类:
public class Student {
public String name;
public int score;
public Student() {
}
public String printScore(String name, int score) {
return "name = " + name + ", score= " + score;
}
public static String printName(String name) {
return "name = " + name;
}
}
java 层 native 方法定义和调用:
public native String studentScoreFromJni();
Log.e(TAG, "JNI反射调用Java非静态方法:" + studentScoreFromJni());
c++ 层:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_demo_ndkdemo_MainActivity_studentScoreFromJni(JNIEnv *env, jobject instance) {
// TODO
// 获取到 student 实例
jclass studentClass = env->FindClass("com/demo/ndkdemo/Student");
if (studentClass == NULL) {
return env->NewStringUTF("cannot find student class");
}
// 获取到构造方法 ID
jmethodID constructorMid = env->GetMethodID(studentClass, "<init>", "()V");
if (constructorMid == NULL) {
return env->NewStringUTF("not find student constructor method");
}
// 获取到 student 对象
jobject studentObject = env->NewObject(studentClass, constructorMid);
jmethodID scoreMid = env->GetMethodID(studentClass, "printScore", "(Ljava/lang/String;I)Ljava/lang/String;");
if (scoreMid == NULL) {
return env->NewStringUTF("not find student printScore method");
}
return static_cast<jstring>(env->CallObjectMethod(studentObject, scoreMid, env->NewStringUTF("mei"), 18));
}
输出结果:
JNI反射调用Java非静态方法:name = mei, score= 18
4.2反射调用Java静态方法
java 层 native 方法定义和调用:
public native String studentNameFromJni();
Log.e(TAG, "JNI反射调用Java静态方法:" + studentNameFromJni());
c++ 层:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_demo_ndkdemo_MainActivity_studentNameFromJni(JNIEnv *env, jobject instance) {
// TODO
// 获取到 student 实例
jclass studentClass = env->FindClass("com/demo/ndkdemo/Student");
if (studentClass == NULL) {
return env->NewStringUTF("cannot find student class");
}
// 获取到 name 的方法 ID
jmethodID nameMid = env->GetStaticMethodID(studentClass, "printName", "(Ljava/lang/String;)Ljava/lang/String;");
if (nameMid == NULL) {
return env->NewStringUTF("not find student printName method");
}
// 反射调用静态方法
return static_cast<jstring>(env->CallStaticObjectMethod(studentClass, nameMid, env->NewStringUTF("body")));
}
输出结果:
JNI反射调用Java静态方法:name = body
5.JNI异常处理
JNI
没有像 Java
一样有 try…catch…final
这样的异常处理机制,面且在本地代码中调用某个 JNI
接口时如果发生了异常,后续的本地代码不会立即停止执行,而会继续往下执行后面的代码。JNI
异常处理与 Java
类似,大致分为异常检测,异常处理,抛出异常
5.1异常检测
检测是否发生异常有两种方式:
- 1)通过特定的返回值来表示发生了一个错误,如
NULL
,有一个空指针异常需要处理 - 2)通过调用
JNI
提供的ExceptionCheck
来检测是否有异常发生
判空处理异常:
// 获取到 student 实例
jclass studentClass = env->FindClass("com/demo/ndkdemo/Student");
if (studentClass == NULL) {
LOGE("cannot find student class");
return env->NewStringUTF("cannot find student class");
}
同样也可以使用 ExceptionCheck
来检测异常:
jclass studentClass = env->FindClass("com/demo/ndkdemo/Student");
if(env->ExceptionCheck()){
// 如果发生异常,则返回字符提示
return env->NewStringUTF("cannot find student class exception");
}
5.2异常处理
由于 JNI
发生了异常,依旧会执行后续代码,所以我们需要对异常进行相应处理,否则会发生不可预知的错误,处理的步骤如下:
- 1)一旦发生了异常,立即返回,让调用者处理异常
- 2)通过
ExceptionClear
清除异常,释放资源,让调用者处理异常
如下示例:
const char *result = env->GetStringUTFChars(str, NULL);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
// 清除异常
env->ExceptionClear();
// 释放资源
env->ReleaseStringUTFChars(str,result);
// 直接返回
return ;
}
5.3抛出异常
当我们不需要处理异常时,需要抛出异常。抛出异常也分为两种方式:
- 1)抛出现有异常
env->Throw(env->ExceptionOccurred());
- 2)抛出新异常
env->ThrowNew(jclass clazz, const char* message)
java 层的方法:
private void nullPointerMethod() {
Student student = null;
student.printScore(name, age);
}
java 层 native 方法定义和调用:
public native void callJavaExceptionMethodFromJni();
try {
callJavaExceptionMethodFromJni();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "JNI发生了异常:" + e.getMessage());
}
c++ 层:
extern "C"
JNIEXPORT void JNICALL
Java_com_demo_ndkdemo_MainActivity_callJavaExceptionMethodFromJni(JNIEnv *env, jobject instance) {
// TODO
jclass jclazz = env->GetObjectClass(instance);
// 获取到方法 id
jmethodID mid = env->GetMethodID(jclazz, "nullPointerMethod", "()V");
// 判空处理
if (NULL == mid) {
LOGE("not find nullPointerMethod");
return;
}
env->CallVoidMethod(instance, mid);
// 异常检测
if (env->ExceptionCheck()) {
jthrowable throwable = env->ExceptionOccurred();
/**
* have a throw is occurred
*/
env->ExceptionDescribe();
// 清除异常
env->ExceptionClear();
// 可以直接抛出 env->Throw(throwable);
// 抛出新异常
jclass exceptionCls = env->FindClass("java/lang/NullPointerException");
if (NULL == exceptionCls) {
return;
}
env->ThrowNew(exceptionCls, "throw a new exception:NullPointerException");
// 释放资源
env->DeleteLocalRef(exceptionCls);
}
}
输出结果:
JNI发生了异常:throw a new exception:NullPointerException
6.JNI多线程
多线程就不得不考虑并发带来的数据同步问题,Java
提供了 synchronize
同步锁,JNI
同时也提供了监视器机制,用来对临界区进行保护性访问。为了更好理解临界区这个概念,我们可以先来看一下 Java
中最简单同步锁的例子:
synchronized (this) {
// 临界区
}
临界区的特点就是在同一时刻有且仅有一个线程运行在临界区内。其他线程会被阻塞在临界区外,直到临界区内没有任何线程运行。
在 JNI
中,通过调用 env->MonitorEnter(jobject);
函数进入一个临界区,当执行完需要同步的代码后,必须调用 env->MonitorExit(jobject);
函数退去临界区,否则程序将会发生死锁。注,MonitorEnter
与 MonitorExit
需成对出现。
// 进入同步代码块
if (env->MonitorEnter(instance) != JNI_OK) {
// 处理错误
}
/**
* 同步代码块业务处理
*/
/**
* 出现异常,结束同步代码块
*/
if (env->ExceptionOccurred()) {
if (env->MonitorExit(instance) != JNI_OK) {
return;
}
}
if (env->MonitorExit(instance) != JNI_OK) {
// 处理错误
}
实践才是检验真理的唯一标准,来看一个简单的案例,在 Java
中开启多个线程,并打印当前的索引值。
java 层 native 方法定义和调用:
public native void synchronizeThreadFromJni(int index);
for (int i = 0; i < 5; i++) {
final int index = i;
new Thread() {
@Override
public void run() {
super.run();
synchronizeThreadFromJni(index);
}
}.start();
}
c++ 层:
extern "C"
JNIEXPORT void JNICALL
Java_com_demo_ndkdemo_MainActivity_synchronizeThreadFromJni(JNIEnv *env, jobject instance,
jint index) {
// TODO
// 进入同步代码块
if (env->MonitorEnter(instance) != JNI_OK) {
// 处理错误
}
/**
* 同步代码块业务处理
*/
LOGE("current thread index = %d", index);
/**
* 出现异常,结束同步代码块
*/
if (env->ExceptionOccurred()) {
if (env->MonitorExit(instance) != JNI_OK) {
return;
}
}
if (env->MonitorExit(instance) != JNI_OK) {
// 处理错误
}
}
输出结果:
current thread index = 0
current thread index = 1
current thread index = 2
current thread index = 3
current thread index = 4
有线程的地方都应有相应的等待唤醒机制,比较简单直接的方式,直接调用 Java
的 object
类中的 wait
,notify
,notifyAll
方法,具体的实现如下:
void initThread(JNIEnv *env, jobject lock) {
jclass cls = env->GetObjectClass(lock);
THREAD_WAIT = env->GetMethodID(cls, "wait", "(J)V");
THREAD_NOTIFY = env->GetMethodID(cls, "notify", "(V)V");
THREAD_NOTIFY_ALL = env->GetMethodID(cls, "notifyAll", "(V)V");
}
void wait(JNIEnv *env, jobject lock, jlong timeout) {
// 调用 object 的 wait 方法
env->CallVoidMethod(lock, THREAD_WAIT, timeout);
}
void notify(JNIEnv *env, jobject lock) {
// 调用 object 的 notify 方法
env->CallVoidMethod(lock, THREAD_NOTIFY);
}
void notifyAll(JNIEnv *env, jobject lock) {
// 调用 object 的 notifyAll 方法
env->CallVoidMethod(lock, THREAD_NOTIFY_ALL);
}
看到这里,肯定有小伙伴心中会有疑问,JNI
中开启线程又是怎么样的姿势?一起来看下以下例子,JNI
中开启多线程,打印线程索引值:
java 层 native 方法定义和调用:
public native void multiThreadFromJni();
private void getIndexCurrentThread(int i) {
Log.e(TAG, "当前线程的索引值为: " + i);
}
c++层:
#include <pthread.h>
// 线程数
const int NUM_THREADS = 5;
// 全局变量
JavaVM *g_jvm = NULL;
jobject g_obj = NULL;
void *thread_fun(void *arg) {
JNIEnv *env;
jclass jclazz;
// TODO
// attach 主线程
if (g_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
LOGE("attach current thread fail");
return NULL;
}
if (NULL == env)return NULL;
// 获取类实例
jclazz = env->GetObjectClass(g_obj);
if (NULL == jclazz) {
LOGE("%s", "not find MainActivity class");
g_jvm->DetachCurrentThread();
return NULL;
}
// 再获取类中的方法 id
jmethodID mid = env->GetMethodID(jclazz, "getIndexCurrentThread", "(I)V");
if (mid == NULL) {
LOGE("not find getIndexCurrentThread method");
g_jvm->DetachCurrentThread();
return NULL;
}
// 最后调用方法
env->CallVoidMethod(g_obj, mid, arg);
if (g_jvm->DetachCurrentThread() != JNI_OK) {
LOGE("detach current thread fail");
}
pthread_exit(0);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_demo_ndkdemo_MainActivity_multiThreadFromJni(JNIEnv *env, jobject instance) {
int i;
pthread_t pt[NUM_THREADS];
// 保存全局的 JVM 对象,以便在子线程中使用
env->GetJavaVM(&g_jvm);
g_obj = env->NewGlobalRef(instance);
// 创建线程
for (i = 0; i < NUM_THREADS; i++) {
pthread_create(&pt[i], NULL, &thread_fun, (void *) i);
}
}
输出结果:
当前线程的索引值为: 0
当前线程的索引值为: 2
当前线程的索引值为: 4
当前线程的索引值为: 3
当前线程的索引值为: 1
注,g_jvm->AttachCurrentThread(JNIEnv** p_env, void* thr_args)
,g_jvm->DetachCurrentThread();
,两函数须成对出现。
7.NIO
NIO
用于大量数据传输,NIO
是直接地址访问,绕过了 JVM
操作极大地提高了程序的运行效率。
7.1新建直接字节缓冲区
原生代码可以创建 Java
应用程序直接使用直接字节缓冲区,该过程是以提供一个原生 C
字节数组为基础,如:
unsigned char *buffer = (unsigned char *) (malloc(1024));
jobject directBuffer;
// address:缓冲区指针
// capacity:缓冲区容量
env->NewDirectByteBuffer(buffer, 1024);
7.2获取直接字节缓冲区
地址:
unsigned char *buffer;
// 直接缓冲区的地址指针,发生异常时返回NULL
buffer = (unsigned char *) (env->GetDirectBufferAddress(directBuffer));
容量:
jlong capacity;
// 缓冲区容量,发生异常时返回-1
capacity = (env->GetDirectBufferCapacity(directBuffer));
8.动态注册JNI
我们不难发现,静态注册生成的 JNI
函数名太长,文件、类名、变量或方法重构时,需要重新修改头文件或 C/C++
内容代码(而且还是各个函数都要修改,没有一个统一的地方),动态注册 JNI
的方法就可以解决这个问题。
动态注册 JNI
的原理:通过使用 JNINativeMethod
结构来保存 Java native
方法和 JNI
函数关联关系。步骤如下:
- 先编写 Java 的 native 方法;
- 编写 JNI 函数的实现(函数名可以随便命名);
- 利用结构体 JNINativeMethod 保存Java native方法和 JNI 函数的对应关系;
- 利用 registerNatives(JNIEnv* env) 注册类的所有本地方法;
- 在 JNI_OnLoad 方法中调用注册方法;
- 在 Java中 通过 System.loadLibrary 加载完 JNI 动态库之后,会自动调用 JNI_OnLoad 函数,完成动态注册;
代码实例:
java 层 native 方法定义和调用:
// 动态注册 jni 类
public class DynamicRegisterJni {
public native String stringFromJni();
}
String strFromJni = new DynamicRegisterJni().stringFromJni();
Log.e(TAG, "动态注册返回的字符串: " +strFromJni);
c++动态注册 JNI 代码:
// 动态注册类名
static const char *dynamicClassName = "com/demo/ndkdemo/DynamicRegisterJni";
// 定义对用的 java native 方法的 c++ 函数,函数名可以随便命名
static jstring helloWorld(JNIEnv *env, jobject instance) {
const char *hello = "hello world";
return env->NewStringUTF(hello);
}
/**
* 定义对应的函数映射表 数组可以定义多对映射关系
* 参数1:java 方法名
* 参数2:方法描述符,也就是签名
* 参数3:c++ 定义对应的 java native 方法的函数名(这里定义的函数名为 helloWorld)
*
*/
static JNINativeMethod jni_methods_table[]{
{"stringFromJni", "()Ljava/lang/String;", (void *) helloWorld},
};
// 根据函数映射表映射函数
static int
registerNativeMethods(JNIEnv *env, const char *className, const JNINativeMethod *gMethods,
int numMethods) {
jclass clazz;
LOGI("registering %s natives\n", className);
clazz = env->FindClass(className);
if (clazz == NULL) {
LOGE("native register not find %s\n", className);
return JNI_ERR;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
LOGE("register natives failed %s \n", className);
return JNI_ERR;
}
// 删除本地引用
env->DeleteLocalRef(clazz);
return JNI_OK;
}
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
LOGI("call JNI_OnLoad");
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
return JNI_EVERSION;
}
// 调用注册映射函数
registerNativeMethods(env, dynamicClassName, jni_methods_table,
sizeof(jni_methods_table) / sizeof(JNINativeMethod));
return JNI_VERSION_1_4;
}
输出结果:
动态注册返回的字符串: hello world
9.JNI字符串加解密
字符串加解密是常见的需求,那么怎么用 JNI
来加解密字符串呢,相关代码如下:
java 层 native 方法定义和调用:
public native String encryptStringFromJni(String str);
String str = "hi beauty";
String encrypt = encryptStringFromJni(str);
String decrypt = decryptStringFromJni(encrypt);
Log.e(TAG, "加密后的字符串:" + encrypt);
Log.e(TAG, "解密后的字符串:" + decrypt);
c++层:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_demo_ndkdemo_MainActivity_encryptStringFromJni(JNIEnv *env, jobject instance,
jstring str_) {
const char *str = env->GetStringUTFChars(str_, NULL);
// TODO
if (str == NULL || strlen(str) == 0) {
// encrypt string not empty
return env->NewStringUTF("");
}
// 注,字符数组的长度 +1
char newStr[strlen(str) + 1];
int i;
for (i = 0; i < strlen(str); ++i) {
newStr[i] = str[i] ^ '0x01';
}
// 字符串是以 \0 结尾
newStr[strlen(str)] = 0;
env->ReleaseStringUTFChars(str_, str);
return env->NewStringUTF(newStr);
}
输出结果:
加密后的字符串:YXSTPDEH
解密后的字符串:hi beauty
10.小结
本文抛砖引玉,讲解了 JNI
相关的知识,希望对 JNI
薄弱的童鞋有所帮助,学习来不得半点马虎, 撸一撸,才能发现问题,踩踩坑,才能积累经验。
《魔道祖师》就是讲述的两个男人之间的事,我喜欢那个吹笛的,但打不过那个弹琴的;我喜欢那个吃糖的,但我打不过那个眼瞎的。
小编维护的超炫的动画库欢迎大家 star