Android的JNI調用(一)


  Android提供NDK開發包來提供Android平台的C++開發,用來擴展Android SDK的功能。主要包括Android NDK構建系統和JNI實現與原生代碼通信兩部分。

一、Android NDK構建系統

 1.1 構建庫

   Android NDK的構建系統是基於GNU Make的。Android GNU Make系統除了需要一些內部的GNU片段外,還需要兩個文件:Android.mk和Application.mk。Android NDK源碼給了很多的例子,以HelloJni為例,Android.mk源碼: 

#Android.mk必須以LOCAL_PATH變量開頭
LOCAL_PATH := $(call my-dir) #清除除了LOCAL_PATH以外的LOCAL_<name>變量,例如LOCAL_MODULE與LOCAL_SRC_FILES等 include $(CLEAR_VARS) #每一個原生組件被稱為一個模塊 LOCAL_MODULE := hello-jni
#源文件 LOCAL_SRC_FILES :
= hello-jni.c #編譯為共享庫,即后綴名為.so include $(BUILD_SHARED_LIBRARY)

  Application.mk源碼: 

 #一般選擇APP_ABI := armeabi-v7a就夠了
 APP_ABI := all

  為了建立可供主應用程序使用的模塊,必須將該模塊變成共享庫。按照上述必不可少的步驟,可以繼續編譯多個共享庫。

  Android也可以編譯靜態庫(后綴名為.a),但是實際的Android應用程序並不直接使用靜態庫,並且應用程序包中也不包含靜態庫。靜態庫可以用來構建共享庫。但是,當靜態庫與多個共享庫相連時,應用程序包中會包含靜態庫的多個副本,徒增應用程序包的大小。這種情況下,可以不構建靜態庫,而是將通用模塊作為共享庫建立起來,動態連接依賴模塊以消除重復的副本。如下Android.mk實現的是共享庫之間的代碼共享。 

LOCAL_PATH := $(call my-dir)

#第三方AVI庫
include $(CLEAR_VARS)
LOCAL_MODULE    := avilib
LOCAL_SRC_FILES := avilib.c
include $(BUILD_SHARED_LIBRARY)

#原生模塊1
include $(CLEAR_VARS)
LOCAL_MODULE    := module1
LOCAL_SRC_FILES := module1.c

LOCAL_SHARED_LIBRARIES := avilib
include $(BUILD_SHARED_LIBRARY)

#原生模塊2
include $(CLEAR_VARS)
LOCAL_MODULE    := module2
LOCAL_SRC_FILES := module2.c

LOCAL_SHARED_LIBRARIES := avilib
include $(BUILD_SHARED_LIBRARY) 

1.2 Prebuilt庫

  共享模塊編譯時要求有源代碼,為此Android提供了Prebuilt庫,以下場合,Prebuilt庫是非常有用的:

  1. 想在不發布源代碼的情況下將你的模塊發布給他人;
  2. 想使用共享模塊的預建版來加速構建過程。

  其他構建系統變量:

  LOCAL_CFLAGS:一組可選的編譯器標志,在編譯C和C++源文件的時候會被傳送給編譯器;

  LOCAL_CPP_FLAGS:一組可選的編譯器標志,在只編譯C++源文件時被傳送給編譯器;

  LOCAL_LDLIBS:鏈接標志的可選列表,它主要用於傳送要進行動態鏈接的系統庫列表。如鏈接日志庫:

  LOCAL_LDFLAGS += -llog 

  APP_CPPFLAGS:編譯器標志,在編譯任何模塊的C++源文件時這些標志都會被傳送給編譯器。 

  nkd-build腳本命令:

ndk-build –C /project path
ndk-build –B
ndk-build clean 

二、JNI實現與原生代碼通信 

  Java層代碼如下: 

public class HelloJni extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        TextView  tv = new TextView(this);
        tv.setText( stringFromJNI() );
        setContentView(tv);
    }

    /* A native method that is implemented by the
     * 'hello-jni' native library, which is packaged
     * with this application.
     */
    public native String  stringFromJNI();

    /* This is another native method declaration that is *not*
     * implemented by 'hello-jni'. This is simply to show that
     * you can declare as many native methods in your Java code
     * as you want, their implementation is searched in the
     * currently loaded native libraries only the first time
     * you call them.
     *
     * Trying to call this function will result in a
     * java.lang.UnsatisfiedLinkError exception !
     */
    public native String  unimplementedStringFromJNI();

    /* this is used to load the 'hello-jni' library on application
     * startup. The library has already been unpacked into
     * /data/data/com.example.hellojni/lib/libhello-jni.so at
     * installation time by the package manager.
     */
    static {
        System.loadLibrary("hello-jni");
    }
}  

  JNI代碼如下,其中Java方法stringFromJNI不帶任何參數,但是原生方法帶兩個參數:

jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !");
}

  第一個參數JNIEnv是指向可用JNI函數表的借口指針;第二個參數jobjects是HelloJni類實例的Java對象引用。  

  這里注意C與C++代碼稍有不同,C代碼如下: 

  return (*env)->NewStringUTF(env, "Hello from JNI ! "); 

  C++代碼如下: 

  return env->NewStringUTF("Hello from JNI ! "); 

  這是因為C++代碼中,JNIEnv實際上是一個C++類實例,JNI函數以成員函數的形式存在,因此JNI方法調用不要求JNIEnv實例作參數。

2.1 C/C++ 頭文件生成器:javah

  JDK自帶一個名為javah的命令行工具,該工具由Java類文件的原始定義生成原生函數名及其參數列表,這樣程序員避免編寫繁雜多余的定義。C/C++源文件只需要包含這個頭文件並提供原生方法實現。 

  javah的參數列表如下: 

C:\Users\jiayayao>javah 用法: javah [options] <classes> 其中, [options] 包括: -o <file>                輸出文件 (只能使用 -d 或 -o 之一) -d <dir> 輸出目錄 -v  -verbose 啟用詳細輸出 -h  --help  -? 輸出此消息 -version 輸出版本信息 -jni 生成 JNI 樣式的標頭文件 (默認值) -force 始終寫入輸出文件 -classpath <path> 從中加載類的路徑 -cp <path> 從中加載類的路徑 -bootclasspath <path> 從中加載引導類的路徑 <classes> 是使用其全限定名稱指定的 (例如, java.lang.Object)。

  生成頭文件的命令行參數如下:

 javah -classpath bin/classes com.example.hellojni.Hellojni  

2.2 數據類型

Java有兩種數據類型:基本數據類型和引用數據類型:基本數據類型中Java/JNI/C++的映射關系如下:

   引用類型的類型映射關系如下: 

  引用類型以不透明的引用方式傳遞給原生代碼,而不是以原生數據類型的的形式呈現,因此引用類型不能直接使用和修改。JNI提供了與這些引用類型密切相關的一組API。

  字符串操作: 

// 創建字符串
jstring javaString;
javaString = (*env)->NewStringUTF(env, "hello world!");
// 內存溢出時,會返回NULL,注意判空

// 將Java字符串轉換成C字符串
const jbyte* str;
jboolean isCopy;
str = (*env)->GetStringUTFChars(env, javaString, &isCopy);
if (0 != str) {

}

// 釋放字符串
(*env)->ReleaseStringUTFChars(env, javaString, str);

   數組操作: 

// 創建數組
jintArray javaArray;
javaArray = (*env)->NewIntArray(env, 10);
if(0!=javaArray) {
    
}

// Get<Type>ArrayRegion函數將給定的基本Java數組復制到給定的C數組中
// 將Java數組區復制到C數組中 jint nativeArray[10]; (*env)->GetIntArrayRegion(env, javaArray, 0, 10, nativeArray); // 從C數組向Java數組提交所做的修改 (*env)->SetIntArrayRegion(env, javaArray, 0, 10, nativeArray);

   原生方法的內存分配超出了虛擬機的管理范圍,且不能用虛擬機的垃圾回收器回收原生方法中的內存。

  原生代碼回到Java損耗性能,建議將所有需要的參數傳遞給原生代碼調用,而不是讓原生代碼回到Java中。

  先記錄這么多,以后接着補充。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM