首先需要搞清楚幾個(gè)概念,是先有jni,還是ndk?
很多人都會(huì)覺(jué)得先有ndk,然后才有的jni,其實(shí)不是的。JNI是JAVA提供的,即Java Native Interface,它是為了實(shí)現(xiàn)java和底層代碼(例如c,c++)之間的交互而生的一套api。NDK是安卓的一套工具集(JNI,gcc,g++ ..... ),為了方便編譯而集成的。
因此可以聯(lián)系android系統(tǒng)架構(gòu)的概念,

從上到下:上層應(yīng)用層,如聯(lián)系人,通話(huà),短信,我們開(kāi)發(fā)的apk應(yīng)用等;
系統(tǒng)應(yīng)用層,如四大組件,系統(tǒng)通知,manager等,這些都是java寫(xiě)的;
類(lèi)庫(kù)層, 大部分是c,c++寫(xiě)的,主要是jvm和一些三方庫(kù)比如sqlite等;
linux內(nèi)核層,主要是一些藍(lán)牙,wifi,camara驅(qū)動(dòng)等;
所以網(wǎng)上會(huì)有很多人都覺(jué)得,JNI是很偉大的,如果沒(méi)有JNI,這個(gè)系統(tǒng)架構(gòu)不復(fù)存在。
jni項(xiàng)目介紹
首先在as中新建一個(gè)c++項(xiàng)目,會(huì)自動(dòng)生成CMakeList.txt native-lib.cpp ,代碼如下,默認(rèn)生成的那些注釋我都刪掉了,下面注釋是我自己理解的:
CMakeList.txt
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED # 動(dòng)態(tài)庫(kù) 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語(yǔ)言的代碼
JNIEXPORT // Linux 和 Windows jni.h 內(nèi)部定義全部都不一樣,此宏代表我要暴露出去的標(biāo)準(zhǔn)形式定義
// 例如:在Windows中,對(duì)外暴露的標(biāo)準(zhǔn)就規(guī)定了,所以函數(shù)必須是Windows系統(tǒng)規(guī)則定義的
jstring
JNICALL // Linux 和 Windows jni.h 內(nèi)部定義全部都不一樣,此宏代表 當(dāng)前函數(shù) 壓棧規(guī)則(行參規(guī)則)
// 例如:Windows中:代表函數(shù)壓棧 從右 到 左邊
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
我們自己寫(xiě)一個(gè)jni方法
在MainActivity中,新建一個(gè)最簡(jiǎn)單的native方法,alt+enter自動(dòng)生成還是很簡(jiǎn)單的,
public native void test01();
其中在native-lib.cpp中生成如下:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_test01(JNIEnv *env, jobject instance) {
//任何jni方法都會(huì)有這兩個(gè)參數(shù)
//其中第一個(gè)參數(shù) env,Java虛擬機(jī)自動(dòng)攜帶過(guò)來(lái)的,就是為了讓我們可以使用JNI的API
//其中第二個(gè)參數(shù)instance,誰(shuí)調(diào)用就是指的誰(shuí),這里是java中的 MainActivity 這個(gè)實(shí)例
}
c,jni,java3個(gè)領(lǐng)域中數(shù)據(jù)類(lèi)型的轉(zhuǎn)換關(guān)系
// C領(lǐng)域中 JNI領(lǐng)域中 Java領(lǐng)域中
// int jint int
// const char * jstring String
// jclass class
// jmethodID Method
接下來(lái)就開(kāi)始進(jìn)行jni的基礎(chǔ)學(xué)習(xí)了,在MainActivity中新建幾個(gè)test方法,大家可以直接擼代碼,注釋步驟寫(xiě)的非常清楚:
jni基本方法調(diào)用
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 = {"斯塔克", "史蒂夫"};
//第一個(gè)test1方法
test1(42,"a warm heart",ints,strings);
}
//第一個(gè)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,打印第一個(gè)參數(shù),int和jint可以隨意切換,所以可以這樣直接賦值
int my_number = number;
LOGD("my_number: %d\n", my_number);
//2,打印第二個(gè)參數(shù)
const char * my_text = env->GetStringUTFChars(text_,NULL);
//GetStringUTFChars方法中的第二個(gè)參數(shù)意思是,第一重意思:是否在內(nèi)部完成Copy操作,NULL==0 false, 第二重意思:要給他一個(gè)值,讓內(nèi)部可以轉(zhuǎn)起來(lái),這個(gè)值,隨意
LOGD("my_text: %s\n", my_text);
//這里一定要記得回收GetStringUTFChars
env->ReleaseStringUTFChars(text_,my_text);
//3,打印第三個(gè)參數(shù),即int數(shù)組
jint * my_int_array = env->GetIntArrayElements(intArray_,NULL);
//由于這里遍歷的時(shí)候不知道數(shù)組的大小,所有還需要獲取一下數(shù)組的大小,同樣也是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,打印第四個(gè)參數(shù),即String數(shù)組,引用類(lèi)數(shù)據(jù)類(lèi)型比較復(fù)雜
jsize my_string_array_length = env->GetArrayLength(array);
for (int i = 0; i < my_string_array_length; i++) {
//引用類(lèi)數(shù)據(jù)類(lè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);
}
}
運(yùn)行結(jié)果如下

jni調(diào)用對(duì)象,是反射機(jī)制
新建Student實(shí)體類(lèi)
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中調(diào)用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int[] ints = {1,2,3,4,5,6};
String[] strings = {"斯塔克", "史蒂夫"};
//第一個(gè)test1方法
test1(42,"a warm heart",ints,strings);
Student student = new Student();
student.age = 27;
student.name = "托尼";
//第二個(gè)test2方法
test2(student);
}
//第一個(gè)test方法
public native void test1(int number, String text, int[] intArray, String[] array);
//第二個(gè)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.獲取字節(jié)碼(這里要注意是全包名,但是c++不認(rèn)識(shí). 所有要把.改成/)
const char * student_clss_str = "com/example/myapplication/Student";
jclass student_class = env->FindClass(student_clss_str);
// 2.拿到方法對(duì)象
const char * sig = "(Ljava/lang/String;)V";
jmethodID setName = env->GetMethodID(student_class, "setName", sig);
//簽名原理:
//(1).首先先來(lái)一個(gè)()
//(2).然后括號(hào)后面跟方法的返回值 void--V
//(3).然后括號(hào)里面是方法的參數(shù),String -- java/lang/String;
//(4).然后在括號(hào)后的第一位加L
sig = "(I)V";//基本數(shù)據(jù)類(lèi)型簡(jiǎn)單,直接一個(gè)I就可以
jmethodID setAge = env->GetMethodID(student_class, "setAge", sig);
sig = "()V";//沒(méi)有參數(shù),直接就不寫(xiě)
jmethodID myStaticMethod = env->GetStaticMethodID(student_class, "myStaticMethod", sig);
// 3.調(diào)用對(duì)象
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); // 回收
}
運(yùn)行結(jié)果為

這里要說(shuō)些簽名,按照原理寫(xiě)可以,也可以直接在命令行里面敲,首先需要把Student編譯成class文件(也就是運(yùn)行一下),在app-build-intermediates-javac-debug-compileDebugJavaWithJavac-classes里,打開(kāi)命令行窗口,輸入javap -s com.example.myapplication.Student,就可以獲得Student下所有方法的簽名了。

謝謝觀(guān)看,有不對(duì)或者不詳細(xì)的地方希望大家在評(píng)論區(qū)提出寶貴的意見(jiàn)。