金戈铁马 Android NDK 实战篇:男人之间的那些事
锲
对一个女人而言,和心爱的人在一起的幸福就是最重大的了。女人永远不需要悲悲壮壮的轰轰烈烈,只要温温柔柔地在一起开开心心。
正文
相对于 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 28defaultConfig {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 对应的 classjclass jclazz = env->GetObjectClass(instance);// 获取到域 IDjfieldID 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) {// TODOjclass jclazz = env->GetObjectClass(instance);// 获取到静态域 IDjfieldID 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");}// 获取到构造方法 IDjmethodID 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 的方法 IDjmethodID 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) {// TODOjclass jclazz = env->GetObjectClass(instance);// 获取到方法 idjmethodID 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() {@Overridepublic 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;}// 再获取类中的方法 idjmethodID 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);// TODOif (str == NULL || strlen(str) == 0) {// encrypt string not emptyreturn env->NewStringUTF("");}// 注,字符数组的长度 +1char 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
金戈铁马 Android NDK 实战篇:男人之间的那些事相关推荐
- Android NDK开发篇(一):新版NDK环境搭建(免Cygwin,超级快)
以前做Android的项目要用到NDK就必须要 下载NDK,下载安装Cygwin(模拟Linux环境用的),下载CDT(Eclipse C/C++开发插件),还要配置编译器,环境变量... 麻烦到不想 ...
- Android NDK开发篇(四):Java与原生代码通信(原生方法声明与定义与数据类型)
Java与原生代码通信涉及到原生方法声明与定义.数据类型.引用数据类型操作.NIO操作.訪问域.异常处理.原生线程 1.原生方法声明与定义 关于原生方法的声明与定义在上一篇已经讲一点了,这次具体分析一 ...
- 指尖上的Android之实战篇(六)
本来准备周末看NBA,可是这两天都没比赛,要到礼拜一,悲吹的上班族 最近来感觉进步缓慢,难道是天气越来越热的缘故,开玩笑了,今天冒雨来公司加班,接着完善这个的项目的其他的功能模块. 今天说的模块是:查 ...
- 腾讯Android自动化测试实战
腾讯Android自动化测试实战 丁如敏 盛娟 等著 图书在版编目(CIP)数据 腾讯Android自动化测试实战 / 丁如敏等著. -北京:机械工业出版社,2016.10 ISBN 978-7-11 ...
- Android开发系列——实战篇5:自适应屏幕尺寸(超详细教程)
在实战篇4中构建了界面之后,在模拟器中完好的布局,在实际下载到手机上的时候,却出现了布局不协调的问题. 在模拟器Nexus6上的布局界面: 在真机HUWEI P10 Plus上的布局界面: 在真机HU ...
- 大叔也说Xamarin~Android篇~Activity之间传递数组
大叔也说Xamarin~Android篇~Activity之间传递数组 原文:大叔也说Xamarin~Android篇~Activity之间传递数组 我们在开发应用程序时,不可能只使用一个Layout ...
- Android项目实战--【谁是歌手-布局篇】
项目简介: 项目设定游戏时间为60秒,在这60秒内,播放音乐,并且给出六张歌手的图片(其中有一张是正确是,其这5张是随机的干扰歌手图片,且每首歌都会随机选5张与正确那张组合),让用户选择 ...
- Android项目实战--【谁是歌手-逻辑实现篇】
上篇Android项目实战--[谁是歌手-布局篇],对项目做了整体介绍,并实现了项目的界面布局,本篇开始实现所有的功能逻辑. 歌曲数据模型对象 根据项目需求,我们要用到歌曲来播放,要用到歌手图片,以方 ...
- C# Xamarin For Android移动开发项目实战篇
一.课程介绍 在前面阿笨的<C# Xamarin移动开发基础进修篇>课程中,大家已经熟悉和了解了Xamarin移动App开发的基础知识和原理.本次分享课<C# Xamarin移动开发 ...
- Android逆向之玩转Xposed模块以劫持登录为例(实战篇)
上一篇文章<Android逆向之玩转Xposed模块以劫持登录为例(Demo篇)>自编自导了一款劫持登录的Xposed模块,如果仅满于破解自己的APP是多么的悲哀,毕竟市场上的app都是经 ...
最新文章
- 自己写的Treeview控件绑定数据源
- jemter安装Transactions per Second和Response Times Over Time插件
- 咏南中间件集群解决方案
- 如何理解 ListT和 DictionaryK,V 的扩容机制 ?
- 中fuse_一种用于将mRNA快速转染到活细胞细胞质中的融合试剂
- python 连接sql server
- IT知识点及书籍推荐
- Parallels将Win10引入Apple Silicon,实测运行效果糟糕
- java imageio 使用_java – 使用ImageIO发送图像流?
- 常用网络测试软件,常用的网络故障检测工具有哪些
- SSM酒店预订客房管理系统(包含数据库及项目说明)
- vi编辑器怎么不保存退出?
- omap4430驱动
- 如何自动隐藏win10任务栏
- Win10清理C盘方法
- 【opencv4.3.0教程】06之基础结构3之Scalar_结构详解
- Spark入门(五)——Spark Streaming
- mac压缩文件有多余文件怎么办 mac压缩文件软件哪个好
- Anima2D官方中文使用手册(对应Anima2D1.1.4)
- JS安全防护算法与逆向分析——新浪微博登录JS加密算法
热门文章
- 当三代测序遇到肿瘤基因组研究
- markdown生成目录
- SQL查询语句可以执行,但是提示对象名无效
- 物联网linux系统设计,Ostro:面向物联网优化的基于 Linux的开源操作系统
- 谁是史上最强-用爬虫分析IMDB TOP250电影数据
- java是要在安装的盆运行吗,Java程序员(单身30年):告诫各位,千万不要和女程序员做同事!...
- ThinkPHP3.1.3 { Fast Simple OOP PHP Framework } — [ WE CAN DO IT JUST THINK ] 报错解决办法。...
- 嵌入Quicktime
- 2022.4.21 python中关于sklearn 0.18的错误—— cannot import name comb
- 【Django】第一课 基于Django图书借阅管理网站平台