Android NDK(JNI)學習總結一:Java程式碼中申明native函式-Java呼叫C函式,並在C函式中訪問java類和方法、屬性
本文不涉及android-ndk開發環境搭。
步驟一:新建一個APP,名稱為HelloJNI,然後定義一個類(將會在native程式碼中呼叫和訪問該類):
package com.example.hellojni;
public class JNITestBean {
private int initField;
public String firstStr = null;
public static String secondStr = null;
public JNITestBean(){
}
public String getFirstString (String str){
firstStr = str;
return firstStr;
}
public static String getSecondString(String str){
secondStr = str;
return secondStr;
}
public int getInitField(){
return initField;
}
}
步驟二:在MainActivity中定義native方法:
MainActivity的全路徑名是:com.example.hellojni.MainActivity;
native方法是:
private native JNITestBean createJNITestObjFromNative();
//Java載入.so動態庫的方式如下:
static{
System.loadLibrary(“HelloJNI”);
}
步驟三:使用javah命令生成native函式的.h標頭檔案
首先在HelloJNI目錄下,新建目錄jni(不能隨意命名,只能是jni)
javah -classpath bin\classes -d jni com.example.hellojni.MainActivity
注:
1)如何提示:cannot access android.app.Activity
javah -classpath D:\software\adt-bundle-windows-x86-20140702\sdk\platforms\android-18\android.jar;bin\classes -d jni com.example.hellojni.MainActivity
記得linux平臺時需要把上面的;bin\classes改成:bin/classes
2)bin\classes目錄是因為你的java程式碼編譯後在bin下classes下面,如果你的編譯後的程式碼直接在bin下面,就不需要新增classes目錄了。
步驟四:編寫C/C++程式碼
將生成的com_example_hellojni_MainActivity.h裡面的宣告函式,拷貝到自定義的C++函式中,
函式中有具體的註釋,需要說明幾點:
第一點:C++和C函式使用JNI函式略有不同,需要注意你用的是C++,還是C
第二點:本函式中使用了一個android native層的log函式,所以在模組編譯時,需要增加依賴庫
第三點:在native方法中所有對類的方法、屬性的操作,都是通過反射進行的,不論是靜態時還是非靜態是,私有屬性,公有屬性都可以通過反射進行操作,但是操作的時候,需要繼續名稱(name)和簽名(signature)。可以通過下面命令檢視簽名信息:
javap -s -p 類名
/*
* mynative.cpp
*
*/
#include "com_example_hellojni_MainActivity.h"
#include "stdio.h"
#include "string.h"
#include "android/log.h"
#define LOG_TAG "========MYNATIVE=========="
//#define LOGI(...)_android_log_print(ANDROID_LOG_INFO, LOG_TAG,...)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , LOG_TAG, __VA_ARGS__)
//#define LOGE(...)_android_log_print(ANDROID_LOG_ERROR, LOG_TAG,...)
//extern int addSum(int , int);
#ifdef __cplusplus
extern "C"
{
#endif
//如果自定義的函式在“main”函式後面,則需要提前宣告該函式,否則會 提示error: was not declared in this scope
int addSum(int , int );
/*
*env函式指標,指向的是jni開發環境(jni開發所有的所有函式呼叫都靠他)
* jobject是MainActivity呼叫這個方法是,傳入的。本方法在Java中宣告的是一個static native 這個變數就是jclass物件。一般是呼叫此方法的當前物件或者當前類
*/
JNIEXPORT jobject JNICALL Java_com_example_hellojni_MainActivity_createJNITestObjFromNative
(JNIEnv* env , jobject thizz)
{
jclass targetClass;
jmethodID mid;
jobject newObj;
jfieldID fid;
jstring str, str1;
jint main_acti_value;
//此處兩行程式碼的作用是得到MainActivity的域
//targetClass = env->FindClass("MainActivity");
targetClass = env->GetObjectClass(thizz);
fid = env->GetStaticFieldID(targetClass, "filed", "I");
//通過object和方法實體,得到該方法的值
main_acti_value = env->GetStaticIntField(targetClass, fid);
LOGI("在C++方法中訪問java域,得到的值是: %d \n", main_acti_value);
//下面的程式碼將生成JNITest物件,並返回
targetClass = env->FindClass("com/example/hellojni/JNITestBean");
if(targetClass == NULL){
return NULL;
}
//得到JNITest類的構造方法
mid = env->GetMethodID(targetClass, "<init>", "()V");
LOGI("[CPP] JNITest物件 生成 \n");
newObj = env->NewObject(targetClass, mid);
//呼叫物件的方法
mid = env->GetStaticMethodID(targetClass, "getSecondString", "(Ljava/lang/String;)Ljava/lang/String;");
LOGI("查詢JNITest物件的getsecondstring這個方法");
int result = addSum(10 , 20);
if(mid != NULL){
LOGI("找到JNITest物件的getsecondstring這個方法");
char* tempchar = "method invoke from native\n";
str1 = env->NewStringUTF(tempchar);
//const char* strch = env->GetStringUTFChars(str, NULL);
//env->call
//將jobject物件,轉化成jstring物件
str = (jstring) env->CallStaticObjectMethod(targetClass, mid, str1);
//將java中的string轉化成char字元陣列,以便列印輸出
const char* strch = env->GetStringUTFChars(str, NULL);
LOGI("在native呼叫java的static方法,方法返回值是: %s \n", strch);
//釋放資源
//env->ReleaseStringUTFChars(str1, tempchar);
//env->ReleaseStringUTFChars(str, strch);
}else{
LOGI("沒有找到JNITest物件的getsecondstring這個方法");
}
LOGI("查詢JNITest物件的initField這個屬性");
//設定JNITest中的私有變數的值
fid = env->GetFieldID(targetClass, "initField", "I");
if(fid != NULL){
LOGI("設定jnitest的變數的值initfield的值為200");
main_acti_value = 200;
env->SetIntField(newObj, fid, main_acti_value);
}else{
LOGI("沒有找到initfield這個屬性");
}
return newObj;
}
int addSum(int a, int b)
{
return a + b;
}
#ifdef __cplusplus
}
#endif
步驟五:編寫Android.mk的makefile檔案
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := HelloJNI
LOCAL_SRC_FILES := mynative.cpp
LOCAL_LDLIBS := -lm -llog -ljnigraphics
include $(BUILD_SHARED_LIBRARY)
LOCAL_MODULE := HelloJNI 此配置表示編譯生成libHelloJNI.so的動態連結庫
LOCAL_SRC_FILES := mynative.cpp 此配置表示需要編譯的C/C++函式
LOCAL_LDLIBS := -lm -llog -ljnigraphics 此配置表示android log函式需要依賴的庫
步驟五:在專案根目錄下(HelloJNI),通過ndk-build命令編譯生成共享庫