对一个女人而言,和心爱的人在一起的幸福就是最重大的了。女人永远不需要悲悲壮壮的轰轰烈烈,只要温温柔柔地在一起开开心心。

正文

相对于 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 编译好的文件,显示支持的各种硬件平台的信息,如 ARMx86 等;

    • 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, 其它参数); 其中第一个参数 JNIEnvjni.h 文件最重要的部分,它的本质是指向函数表指针的指针(JavaVM 也是),函数表里面定义了很多 JNI 函数,同时它也是区分 CC++ 环境的,在 C 语言环境中,JNIEnvstrut 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); 函数退去临界区,否则程序将会发生死锁。注,MonitorEnterMonitorExit 需成对出现。

// 进入同步代码块
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

有线程的地方都应有相应的等待唤醒机制,比较简单直接的方式,直接调用 Javaobject 类中的 waitnotifynotifyAll 方法,具体的实现如下:

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 实战篇:男人之间的那些事相关推荐

  1. Android NDK开发篇(一):新版NDK环境搭建(免Cygwin,超级快)

    以前做Android的项目要用到NDK就必须要 下载NDK,下载安装Cygwin(模拟Linux环境用的),下载CDT(Eclipse C/C++开发插件),还要配置编译器,环境变量... 麻烦到不想 ...

  2. Android NDK开发篇(四):Java与原生代码通信(原生方法声明与定义与数据类型)

    Java与原生代码通信涉及到原生方法声明与定义.数据类型.引用数据类型操作.NIO操作.訪问域.异常处理.原生线程 1.原生方法声明与定义 关于原生方法的声明与定义在上一篇已经讲一点了,这次具体分析一 ...

  3. 指尖上的Android之实战篇(六)

    本来准备周末看NBA,可是这两天都没比赛,要到礼拜一,悲吹的上班族 最近来感觉进步缓慢,难道是天气越来越热的缘故,开玩笑了,今天冒雨来公司加班,接着完善这个的项目的其他的功能模块. 今天说的模块是:查 ...

  4. 腾讯Android自动化测试实战

    腾讯Android自动化测试实战 丁如敏 盛娟 等著 图书在版编目(CIP)数据 腾讯Android自动化测试实战 / 丁如敏等著. -北京:机械工业出版社,2016.10 ISBN 978-7-11 ...

  5. Android开发系列——实战篇5:自适应屏幕尺寸(超详细教程)

    在实战篇4中构建了界面之后,在模拟器中完好的布局,在实际下载到手机上的时候,却出现了布局不协调的问题. 在模拟器Nexus6上的布局界面: 在真机HUWEI P10 Plus上的布局界面: 在真机HU ...

  6. 大叔也说Xamarin~Android篇~Activity之间传递数组

    大叔也说Xamarin~Android篇~Activity之间传递数组 原文:大叔也说Xamarin~Android篇~Activity之间传递数组 我们在开发应用程序时,不可能只使用一个Layout ...

  7. Android项目实战--【谁是歌手-布局篇】

    项目简介:        项目设定游戏时间为60秒,在这60秒内,播放音乐,并且给出六张歌手的图片(其中有一张是正确是,其这5张是随机的干扰歌手图片,且每首歌都会随机选5张与正确那张组合),让用户选择 ...

  8. Android项目实战--【谁是歌手-逻辑实现篇】

    上篇Android项目实战--[谁是歌手-布局篇],对项目做了整体介绍,并实现了项目的界面布局,本篇开始实现所有的功能逻辑. 歌曲数据模型对象 根据项目需求,我们要用到歌曲来播放,要用到歌手图片,以方 ...

  9. C# Xamarin For Android移动开发项目实战篇

    一.课程介绍 在前面阿笨的<C# Xamarin移动开发基础进修篇>课程中,大家已经熟悉和了解了Xamarin移动App开发的基础知识和原理.本次分享课<C# Xamarin移动开发 ...

  10. Android逆向之玩转Xposed模块以劫持登录为例(实战篇)

    上一篇文章<Android逆向之玩转Xposed模块以劫持登录为例(Demo篇)>自编自导了一款劫持登录的Xposed模块,如果仅满于破解自己的APP是多么的悲哀,毕竟市场上的app都是经 ...

最新文章

  1. 自己写的Treeview控件绑定数据源
  2. jemter安装Transactions per Second和Response Times Over Time插件
  3. 咏南中间件集群解决方案
  4. 如何理解 ListT和 DictionaryK,V 的扩容机制 ?
  5. 中fuse_一种用于将mRNA快速转染到活细胞细胞质中的融合试剂
  6. python 连接sql server
  7. IT知识点及书籍推荐
  8. Parallels将Win10引入Apple Silicon,实测运行效果糟糕
  9. java imageio 使用_java – 使用ImageIO发送图像流?
  10. 常用网络测试软件,常用的网络故障检测工具有哪些
  11. SSM酒店预订客房管理系统(包含数据库及项目说明)
  12. vi编辑器怎么不保存退出?
  13. omap4430驱动
  14. 如何自动隐藏win10任务栏
  15. Win10清理C盘方法
  16. 【opencv4.3.0教程】06之基础结构3之Scalar_结构详解
  17. Spark入门(五)——Spark Streaming
  18. mac压缩文件有多余文件怎么办 mac压缩文件软件哪个好
  19. Anima2D官方中文使用手册(对应Anima2D1.1.4)
  20. JS安全防护算法与逆向分析——新浪微博登录JS加密算法

热门文章

  1. 当三代测序遇到肿瘤基因组研究
  2. markdown生成目录
  3. SQL查询语句可以执行,但是提示对象名无效
  4. 物联网linux系统设计,Ostro:面向物联网优化的基于 Linux的开源操作系统
  5. 谁是史上最强-用爬虫分析IMDB TOP250电影数据
  6. java是要在安装的盆运行吗,Java程序员(单身30年):告诫各位,千万不要和女程序员做同事!...
  7. ThinkPHP3.1.3 { Fast Simple OOP PHP Framework } — [ WE CAN DO IT JUST THINK ] 报错解决办法。...
  8. 嵌入Quicktime
  9. 2022.4.21 python中关于sklearn 0.18的错误—— cannot import name comb
  10. 【Django】第一课 基于Django图书借阅管理网站平台