1. 程式人生 > >Android NDK(JNI)學習總結一:Java程式碼中申明native函式-Java呼叫C函式,並在C函式中訪問java類和方法、屬性

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

(無法找到android.app.Activity類)時,需要給classpath增加android相應平臺的android.jar
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命令編譯生成共享庫