準(zhǔn)備工作
-
1.開(kāi)發(fā)環(huán)境
developEnvironment.png -
2.AndroidStudio版本
ASVersion.png
-
3.在SDKManager中下載NDK工具
ndk.png
靜態(tài)注冊(cè)Native函數(shù)
- 創(chuàng)建Android項(xiàng)目
2.創(chuàng)建native方法的工具類(lèi)JniTest
距糖,代碼如下
package com.xc.jnitest.exercise;
public class JniTest {
public native String get();
public native void set(String str);
}
3.修改MainActivity以及l(fā)ayout文件
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textview1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
TextView mText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mText = findViewById(R.id.textview1);
JniTest jniTest = new JniTest();
mText.setText(jniTest.get());
}
}
4.生成.class
文件
- 可以在Terminal中使用 “
javac com/xc/jnitest/exercise/JniTest.java
”命令生成.class
文件 - 在AndroidStudio中可以直接
Build
->Make Project
生成,如下圖
make_project.png
使用Terminal
生成的.class
文件在同級(jí)目錄下岭皂,使用Build
->Make project
生成的在build
目錄下浊猾,如下圖
5.在Terminal
中cd
到相應(yīng)目錄下铅碍,執(zhí)行javah -jni
命令生成.h
頭文件
- 在
java
目錄下執(zhí)行javah -jni com.xc.jnitest.exercise.JniTest
命令 - 在
build/intermediates/javac/debug/compileDebugJavaWithJavac/classes
目錄下執(zhí)行javah -jni com.xc.jnitest.exercise.JniTest
命令
生成對(duì)應(yīng)的.h
頭文件如下圖
.h
文件內(nèi)容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xc_jnitest_exercise_JniTest */
#ifndef _Included_com_xc_jnitest_exercise_JniTest
#define _Included_com_xc_jnitest_exercise_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_xc_jnitest_exercise_JniTest
* Method: get
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_xc_jnitest_exercise_JniTest_get
(JNIEnv *, jobject);
/*
* Class: com_xc_jnitest_exercise_JniTest
* Method: set
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_xc_jnitest_exercise_JniTest_set
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
JNIEnv*
:表示一個(gè)紙箱JNI
環(huán)境的指針,可以通過(guò)它來(lái)訪問(wèn)JNI
提供的接口方法捌治;
jobject
:表示Java對(duì)象中的this
JNIEXPORT
和JNICALL
:他們是JNI
中所定義的宏岗钩,可以在jni.h
這個(gè)頭文件中查找到。
jstring
: 是返回值類(lèi)型
Java_com_xc_jnitest_exercise
是包名
JniTest
: 是類(lèi)名
get
: 是方法名
- 上邊只是讓你更理解創(chuàng)建步驟具滴,如果嫌麻煩凹嘲,可以直接使用
javah -d ../jni com.xc.jnitest.exercise.JniTest
創(chuàng)建.h
頭文件,當(dāng)然這里就可以省略生成.class
文件的步驟了构韵,其中
-d ../jni
是指定要生成的頭文件到那個(gè)目錄下
6.在main
目錄下創(chuàng)建一個(gè)jni
文件夾周蹭,將剛才生成的.h
文件剪切過(guò)來(lái)。在jni目錄下新建一個(gè)c++
文件疲恢。命名為jni-test.cpp
凶朗。(注:c
文件的后綴為.c
,c++
文件后綴為.cpp
)
如下圖:
7.編寫(xiě)jni-test.cpp
文件
#include <jni.h>
#include <stdio.h>
#include "com_xc_jnitest_exercise_JniTest.h"
JNIEXPORT jstring JNICALL Java_com_xc_jnitest_exercise_JniTest_get
(JNIEnv *env, jobject obj ){
return env->NewStringUTF("Hello from JNI !");
}
/*
* Class: com_xc_jnitest_exercise_JniTest
* Method: set
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_xc_jnitest_exercise_JniTest_set
(JNIEnv *env, jobject obj, jstring string){
char* str = (char*) env->GetStringUTFChars(string,NULL);
env->ReleaseStringUTFChars(string,str);
}
-
#inclued "com_xc_jnitest_exercise_JniTest.h"
添加頭文件 - 實(shí)現(xiàn)兩個(gè)方法方法
- 在
jni
目錄下添加Android.mk
文件
LOCAL_PATH := $(call my-dir) //
include $(CLEAR_VARS)
LOCAL_MODULE := jni-test
LOCAL_SRC_FILES := jni-test.cpp
include $(BUILD_SHARED_LIBRARY)
-
LOCAL_PATH := $(call my-dir)
:每個(gè)Android.mk文件必須以定義開(kāi)始。它用于在開(kāi)發(fā)tree中查找源文件显拳。宏my-dir
則由Build System 提供棚愤。返回包含Android.mk目錄路徑。 -
include $(CLEAR_VARS)
:CLEAR_VARS
變量由Build System提供。并指向一個(gè)指定的GNU Makefile宛畦,由它負(fù)責(zé)清理很多LOCAL_xxx瘸洛。例如LOCAL_MODULE,LOCAL_SRC_FILES次和,LOCAL_STATIC_LIBRARIES等等反肋。但不是清理LOCAL_PATH。這個(gè)清理是必須的踏施,因?yàn)樗械木幾g控制文件由同一個(gè)GNU Make解析和執(zhí)行石蔗,其變量是全局的。所以清理后才能便面相互影響畅形。 -
LOCAL_MODULE := jni-test
:LOCAL_MODULE模塊必須定義养距,以表示Android.mk中的每一個(gè)模塊。名字必須唯一且不包含空格日熬。Build System 會(huì)自動(dòng)添加適當(dāng)?shù)那熬Y和后綴棍厌。例如,demo竖席,要生成動(dòng)態(tài)庫(kù)浴骂,則生成libdemo.so蹦渣。但請(qǐng)注意:如果模塊名字被定義為libabd钠惩,則生成libabc.so秕狰。不再添加前綴帘皿。 -
LOCAL_SRC_FILES := ndkdemotest.c
:這行代碼表示將要打包的C/C++源碼东跪。不必列出頭文件,build System 會(huì)自動(dòng)幫我們找出依賴(lài)文件鹰溜。缺省的C++ 源碼的擴(kuò)展名為.cpp虽填。 -
include $(BUILD_SHARED_LIBRARY)
:BUILD_SHARED_LIBRARY
是Build System提供的一個(gè)變量,指向一個(gè)GUN Makefile Script曹动。它負(fù)責(zé)收集自從上次調(diào)用include $(CLEAR_VARS)
后的所有LOCAL_xxxxinx斋日。并決定編譯什么類(lèi)型-
BUILD_STATIC_LIBRARY
:編譯為靜態(tài)庫(kù) -
BUILD_SHARED_LIBRARY
:編譯為動(dòng)態(tài)庫(kù) -
BUILD_EXECUTABLE
:編譯為Native C 可執(zhí)行程序 -
BUILD_PREBUILT
:該模塊已經(jīng)預(yù)先編譯
-
9.配置app module的build.gradle
文件
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.xc.jnitest"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk{
moduleName "jni-test"
abiFilters "armeabi-v7a", "x86"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
sourceSets.main {
jni.srcDirs = []
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
配置后就可以生成.so
文件了(PS:項(xiàng)目Build之后)
- 加載
so
庫(kù)
package com.xc.jnitest.exercise;
public class JniTest {
static{
System.loadLibrary("jni-test");
}
public static native String get();
public static native void set(String str);
}
11.運(yùn)行項(xiàng)目
動(dòng)態(tài)注冊(cè)Native函數(shù)
- 不必忍受冗長(zhǎng)的函數(shù)名,自由命名函數(shù)名墓陈,在
JNI_OnLoad
方法里進(jìn)行注冊(cè)恶守。
1.修改生成后的.h
頭文件名為jni-test.h
,如下
修改
.h
文件方法名
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xc_jnitest_exercise_JniTest */
#ifndef _Included_com_xc_jnitest_exercise_JniTest
#define _Included_com_xc_jnitest_exercise_JniTest
#ifdef __cplusplus
extern "C" {
#endif
jstring get (JNIEnv *, jobject);
void set (JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
2.修改.cpp
文件如下
#include <jni.h>
#include <stdio.h>
#include "jni-test.h"
jstring get(JNIEnv *env, jobject obj) {
return env->NewStringUTF("Hello from JNI !");
}
void set(JNIEnv *env, jobject obj, jstring string) {
char *str = (char *) env->GetStringUTFChars(string, NULL);
env->ReleaseStringUTFChars(string, str);
}
3.添加參數(shù)映射函數(shù)
//參數(shù)映射表
static JNINativeMethod getMethods[] = {
{"get", "()Ljava/lang/String;", (void *) get},
{"set", "(Ljava/lang/String;)", (void *) set},
};
它的返回值是JNINativeMethod
類(lèi)型:
JNI允許我們提供一個(gè)函數(shù)映射表,注冊(cè)給Java虛擬機(jī)贡必,這樣JVM就可以用函數(shù)映射表來(lái)調(diào)用相應(yīng)的函數(shù)兔港。這樣就可以不必通過(guò)函數(shù)名來(lái)查找需要調(diào)用的函數(shù)了。Java與JNI通過(guò)JNINativeMethod
的結(jié)構(gòu)來(lái)建立聯(lián)系仔拟,它被定義在jni.h中衫樊,其結(jié)構(gòu)內(nèi)容如下:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
- 第一個(gè)變量
name
,代表的是Java中的函數(shù)名 - 第二個(gè)變量
signature
,代表的是Java中的參數(shù)和返回值 - 第三個(gè)變量
fnPtr
科侈,代表的是的指向C函數(shù)的函數(shù)指針
第二個(gè)變量signature
定義如下:
(參數(shù)1類(lèi)型標(biāo)示载佳;參數(shù)2類(lèi)型標(biāo)示;參數(shù)3類(lèi)型標(biāo)示...)返回值類(lèi)型標(biāo)示
當(dāng)參數(shù)為引用類(lèi)型的時(shí)候臀栈,參數(shù)類(lèi)型的標(biāo)示的根式為"L包名"蔫慧,其中包名的.(點(diǎn))要換成"/",比如String
就是Ljava/lang/String
挂脑,Menu
為Landroid/view/Menu
,如果返回值是void
,對(duì)應(yīng)的簽名就是V
藕漱。
如果是基本類(lèi)類(lèi)型,其簽名如下(除了boolean和long崭闲,其他都是首字母大寫(xiě)):
類(lèi)型標(biāo)示 | Java類(lèi)型 |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
數(shù)組類(lèi)型:
類(lèi)型標(biāo)示 | Java類(lèi)型 |
---|---|
[簽名 | 數(shù)組 |
[i | int[] |
[Ljava/lang/Object | String[] |
- 可以使用JDK的
javap -s com.xc.jnitest.exercise.JniTest
直接查看他的signature
肋联,這里的com.xc.jnitest.exercise.JniTest
為class
文件路徑
4.注冊(cè)native
方法
//native類(lèi)路徑
static const char *className = "com/com/xc/jnitest/exercise/JniTest";
//注冊(cè)native方法
static int registerNatives(JNIEnv *engv) {
jclass clazz;
clazz = engv->FindClass(className); //找到native類(lèi)
if (clazz == NULL) {
return JNI_FALSE;
}
//int len = sizeof(methods) / sizeof(methods[0]);
if (engv->RegisterNatives(clazz, getMethods,
sizeof(getMethods) / sizeof(getMethods[0])) <
0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return result;
}
assert(env != NULL);
//為了方便管理我們將不同java類(lèi)中的native方法分別注冊(cè)
if (registerNatives(env) < 0) { //注冊(cè)native方法
return result;
}
//如果還有別的native類(lèi),可繼續(xù)在此進(jìn)行注冊(cè)
return JNI_VERSION_1_6;
}
jni-test.cpp
完整代碼如下
#include <jni.h>
#include <stdio.h>
#include <assert.h>
#include "jni-test.h"
jstring get(JNIEnv *env, jobject obj) {
return env->NewStringUTF("Hello from JNI !");
}
void set(JNIEnv *env, jobject obj, jstring string) {
char *str = (char *) env->GetStringUTFChars(string, NULL);
env->ReleaseStringUTFChars(string, str);
}
//參數(shù)映射表
static JNINativeMethod getMethods[] = {
{"get", "()Ljava/lang/String;", (void *) get},
{"set", "(Ljava/lang/String;)V", (void *) set},
};
//native類(lèi)路徑
static const char *className = "com/xc/jnitest/exercise/JniTest";
//注冊(cè)native方法
static int registerNatives(JNIEnv *engv) {
jclass clazz;
clazz = engv->FindClass(className); //找到native類(lèi)
if (clazz == NULL) {
return JNI_FALSE;
}
//int len = sizeof(methods) / sizeof(methods[0]);
if (engv->RegisterNatives(clazz, getMethods,
sizeof(getMethods) / sizeof(getMethods[0])) <
0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return result;
}
assert(env != NULL);
//為了方便管理我們將不同java類(lèi)中的native方法分別注冊(cè)
if (registerNatives(env) < 0) { //注冊(cè)native方法
return result;
}
//如果還有別的native類(lèi)刁俭,可繼續(xù)在此進(jìn)行注冊(cè)
return JNI_VERSION_1_6;
}
5.齊活橄仍,運(yùn)行!
native代碼反調(diào)用Java層代碼
1.獲取class
對(duì)象:
-
jclass FindClass(const char* clsName)
:
通過(guò)類(lèi)的名稱(chēng)(類(lèi)的全名牍戚,這時(shí)候包名不是用'"."點(diǎn)號(hào)而是用"/"來(lái)區(qū)分的)來(lái)獲取jclass侮繁。比如:
jclass jcl_string=env->FindClass("java/lang/String");
-
jclass GetObjectClass(jobject obj)
通過(guò)對(duì)象實(shí)例來(lái)獲取jclass,相當(dāng)于Java中的getClass()函數(shù) -
jclass getSuperClass(jclass obj)
通過(guò)jclass可以獲取其父類(lèi)的jclass對(duì)象
2.獲取屬性方法
在Native本地代碼中訪問(wèn)Java層的代碼如孝,一個(gè)常用的常見(jiàn)的場(chǎng)景就是獲取Java類(lèi)的屬性和方法宪哩。所以為了在C/C++獲取Java層的屬性和方法,JNI在jni.h頭文件中定義了jfieldID和jmethodID這兩種類(lèi)型來(lái)分別代表Java端的屬性和方法第晰。在訪問(wèn)或者設(shè)置Java某個(gè)屬性的時(shí)候锁孟,首先就要現(xiàn)在本地代碼中取得代表該Java類(lèi)的屬性的jfieldID,然后才能在本地代碼中進(jìn)行Java屬性的操作茁瘦,同樣品抽,在需要調(diào)用Java類(lèi)的某個(gè)方法時(shí),也是需要取得代表該方法的jmethodID才能進(jìn)行Java方法操作甜熔。
-
GetFieldID/GetMethodID
獲取某個(gè)屬性/某個(gè)方法 -
GetStaticFieldID/GetStaticMethodID
獲取某個(gè)靜態(tài)屬性/靜態(tài)方法
方法實(shí)現(xiàn)如下:
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
3.構(gòu)造對(duì)象
常用的JNI中創(chuàng)建對(duì)象的方法如下:
jobject NewObject(jclass clazz, jmethodID methodID, ...)
比如有我們知道Java類(lèi)中可能有多個(gè)構(gòu)造函數(shù)圆恤,當(dāng)我們要指定調(diào)用某個(gè)構(gòu)造函數(shù)的時(shí)候,會(huì)調(diào)用下面這個(gè)方法
jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
obj = (*env)->NewObject(env, cls, mid);
即把指定的構(gòu)造函數(shù)傳入進(jìn)去即可腔稀。
現(xiàn)在我們來(lái)看下他上面的兩個(gè)主要參數(shù)
- clazz:是需要?jiǎng)?chuàng)建的Java對(duì)象的Class對(duì)象
- methodID:是傳遞一個(gè)方法ID
簡(jiǎn)化代碼如下:
jobject NewObjectA(JNIEnv *env, jclass clazz,
jmethodID methodID, jvalue *args);
這里多了一個(gè)參數(shù)盆昙,即jvalue *args,這里是args代表的是對(duì)應(yīng)構(gòu)造函數(shù)的所有參數(shù)的焊虏,我們可以應(yīng)將傳遞給構(gòu)造函數(shù)的所有參數(shù)放在jvalues類(lèi)型的數(shù)組args中弱左,該數(shù)組緊跟著放在methodID參數(shù)的后面。NewObject()收到數(shù)組中的這些參數(shù)后炕淮,將把它們傳給編程任索要調(diào)用的Java方法拆火。
如果參數(shù)不是數(shù)組怎么處理:
jobject NewObjectV(JNIEnv *env, jclass clazz,
jmethodID methodID, va_list args);
這個(gè)方法和上面不同在于,這里將構(gòu)造函數(shù)的所有參數(shù)放到在va_list類(lèi)型的參數(shù)args中,該參數(shù)緊跟著放在methodID參數(shù)的后面们镜。
總結(jié)
1.靜態(tài)注冊(cè)native
函數(shù)
- 第1步:在Java中先聲明
native
方法 - 第2步:編譯Java源文件
javac
得到.class
文件 - 第3步:通過(guò)
javah -jni
命令導(dǎo)出JNI的.h
頭文件币叹,添加.c
或.cpp
文件,實(shí)現(xiàn)函數(shù)方法 - 第4步:使用Java需要交互的本地代碼模狭,實(shí)現(xiàn)在Java中聲明的Native方法
- 第5步:添加
Android.mk
文件颈抚,修改build.gradle
文件,將本地代碼編譯成動(dòng)態(tài)庫(kù) - 第6步:通過(guò)Java命令執(zhí)行Java程序嚼鹉,最終實(shí)現(xiàn)Java調(diào)用本地代碼贩汉。
2.動(dòng)態(tài)注冊(cè)native
函數(shù), 在靜態(tài)注冊(cè)的基礎(chǔ)上:
- 修改簡(jiǎn)化
.h
文件的方法名與方法 - 修改簡(jiǎn)化
.cpp
文件的方法名 - 添加映射函數(shù)與注冊(cè)函數(shù)
項(xiàng)目git地址:https://github.com/x-fp/JniTest
參考文章:http://www.reibang.com/p/87ce6f565d37
這篇文章寫(xiě)得非常詳細(xì)锚赤,感謝作者的幫助