首先需要搞清楚几个概念,是先有jni,还是ndk?
很多人都会觉得先有ndk,然后才有的jni,其实不是的。JNI是JAVA提供的,即Java Native Interface,它是为了实现java和底层代码(例如c,c++)之间的交互而生的一套api。NDK是安卓的一套工具集(JNI,gcc,g++ ..... ),为了方便编译而集成的。
因此可以联系android系统架构的概念,

从上到下:上层应用层,如联系人,通话,短信,我们开发的apk应用等;
系统应用层,如四大组件,系统通知,manager等,这些都是java写的;
类库层, 大部分是c,c++写的,主要是jvm和一些三方库比如sqlite等;
linux内核层,主要是一些蓝牙,wifi,camara驱动等;
所以网上会有很多人都觉得,JNI是很伟大的,如果没有JNI,这个系统架构不复存在。
jni项目介绍
首先在as中新建一个c++项目,会自动生成CMakeList.txt native-lib.cpp ,代码如下,默认生成的那些注释我都删掉了,下面注释是我自己理解的:
CMakeList.txt
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED # 动态库 Linux .so Windows .dll
native-lib.cpp)
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib})
native-lib.cpp
#include <jni.h>
#include <string>
extern "C" // 支持C语言的代码
JNIEXPORT // Linux 和 Windows jni.h 内部定义全部都不一样,此宏代表我要暴露出去的标准形式定义
// 例如:在Windows中,对外暴露的标准就规定了,所以函数必须是Windows系统规则定义的
jstring
JNICALL // Linux 和 Windows jni.h 内部定义全部都不一样,此宏代表 当前函数 压栈规则(行参规则)
// 例如:Windows中:代表函数压栈 从右 到 左边
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
我们自己写一个jni方法
在MainActivity中,新建一个最简单的native方法,alt+enter自动生成还是很简单的,
public native void test01();
其中在native-lib.cpp中生成如下:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_test01(JNIEnv *env, jobject instance) {
//任何jni方法都会有这两个参数
//其中第一个参数 env,Java虚拟机自动携带过来的,就是为了让我们可以使用JNI的API
//其中第二个参数instance,谁调用就是指的谁,这里是java中的 MainActivity 这个实例
}
c,jni,java3个领域中数据类型的转换关系
// C领域中 JNI领域中 Java领域中
// int jint int
// const char * jstring String
// jclass class
// jmethodID Method
接下来就开始进行jni的基础学习了,在MainActivity中新建几个test方法,大家可以直接撸代码,注释步骤写的非常清楚:
jni基本方法调用
package com.example.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int[] ints = {1,2,3,4,5,6};
String[] strings = {"斯塔克", "史蒂夫"};
//第一个test1方法
test1(42,"a warm heart",ints,strings);
}
//第一个test方法
public native void test1(int number, String text, int[] intArray, String[] array);
}
#include <jni.h>
#include <string>
#include <android/log.h>
// 日志打印
#define TAG "ftd"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_test1(JNIEnv *env, jobject instance, jint number,
jstring text_, jintArray intArray_,
jobjectArray array) {
//1,打印第一个参数,int和jint可以随意切换,所以可以这样直接赋值
int my_number = number;
LOGD("my_number: %d\n", my_number);
//2,打印第二个参数
const char * my_text = env->GetStringUTFChars(text_,NULL);
//GetStringUTFChars方法中的第二个参数意思是,第一重意思:是否在内部完成Copy操作,NULL==0 false, 第二重意思:要给他一个值,让内部可以转起来,这个值,随意
LOGD("my_text: %s\n", my_text);
//这里一定要记得回收GetStringUTFChars
env->ReleaseStringUTFChars(text_,my_text);
//3,打印第三个参数,即int数组
jint * my_int_array = env->GetIntArrayElements(intArray_,NULL);
//由于这里遍历的时候不知道数组的大小,所有还需要获取一下数组的大小,同样也是jni的api
jsize my_int_array_length = env->GetArrayLength(intArray_);
for (int i = 0; i < my_int_array_length; i++) {
int result = *(my_int_array+i);//这里使用了指针遍历
LOGD("遍历IntArray里面的值:%d\n",result);
}
//还是要记着回收
env->ReleaseIntArrayElements(intArray_,my_int_array,0);//0代表刷新
//4,打印第四个参数,即String数组,引用类数据类型比较复杂
jsize my_string_array_length = env->GetArrayLength(array);
for (int i = 0; i < my_string_array_length; i++) {
//引用类数据类型都先用jobject接收
jobject jobject1 = env->GetObjectArrayElement(array,i);
jstring jstring1 = static_cast<jstring>(jobject1);
const char * itemStr = env->GetStringUTFChars(jstring1,NULL);
LOGD("遍历String Array 里面的值:%s\n", itemStr);
// 回收
env->ReleaseStringUTFChars(jstring1, itemStr);
}
}
运行结果如下

jni调用对象,是反射机制
新建Student实体类
public class Student {
private final static String TAG = Student.class.getSimpleName();
public String name;
public int age;
public String getName() {
return name;
}
public void setName(String name) {
Log.d(TAG, "Java setName: name:" + name);
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
Log.d(TAG, "Java setName: age:" + age);
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void myStaticMethod() {
Log.d(TAG, "myStaticMethod: ");
}
}
MainActivity中调用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int[] ints = {1,2,3,4,5,6};
String[] strings = {"斯塔克", "史蒂夫"};
//第一个test1方法
test1(42,"a warm heart",ints,strings);
Student student = new Student();
student.age = 27;
student.name = "托尼";
//第二个test2方法
test2(student);
}
//第一个test方法
public native void test1(int number, String text, int[] intArray, String[] array);
//第二个test方法
public native void test2(Student student);
native-lib中:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_test2(JNIEnv *env, jobject instance, jobject student) {
// 1.获取字节码(这里要注意是全包名,但是c++不认识. 所有要把.改成/)
const char * student_clss_str = "com/example/myapplication/Student";
jclass student_class = env->FindClass(student_clss_str);
// 2.拿到方法对象
const char * sig = "(Ljava/lang/String;)V";
jmethodID setName = env->GetMethodID(student_class, "setName", sig);
//签名原理:
//(1).首先先来一个()
//(2).然后括号后面跟方法的返回值 void--V
//(3).然后括号里面是方法的参数,String -- java/lang/String;
//(4).然后在括号后的第一位加L
sig = "(I)V";//基本数据类型简单,直接一个I就可以
jmethodID setAge = env->GetMethodID(student_class, "setAge", sig);
sig = "()V";//没有参数,直接就不写
jmethodID myStaticMethod = env->GetStaticMethodID(student_class, "myStaticMethod", sig);
// 3.调用对象
const char * str = "09";
jstring str2 = env->NewStringUTF(str);
env->CallVoidMethod(student, setName, str2);
env->CallVoidMethod(student, setAge, 888);
env->CallStaticVoidMethod(student_class, myStaticMethod);
env->DeleteLocalRef(student_class); // 回收
env->DeleteLocalRef(student); // 回收
}
运行结果为

这里要说些签名,按照原理写可以,也可以直接在命令行里面敲,首先需要把Student编译成class文件(也就是运行一下),在app-build-intermediates-javac-debug-compileDebugJavaWithJavac-classes里,打开命令行窗口,输入javap -s com.example.myapplication.Student,就可以获得Student下所有方法的签名了。

谢谢观看,有不对或者不详细的地方希望大家在评论区提出宝贵的意见。