Android游戲開發(fā)實踐(1)之NDK與JNI開發(fā)01

Android游戲開發(fā)實踐(1)之NDK與JNI開發(fā)01

NDK是Native Developement Kit的縮寫,顧名思義,NDK是Google提供的一套原生Java代碼與本地C/C++代碼“交互”的開發(fā)工具集。而Android是運行在Dalvik虛擬機之上,支持通過JNI的方式調用本地C/C++動態(tài)鏈接庫。C/C++有著較高的性能和移植性,通過這種調用機制就可以實現(xiàn)多平臺開發(fā)、多語言混編的Android應用了。當然,這些都是基于JNI實現(xiàn)的。在游戲開發(fā)中,這種需求更是必不可少。

1、認識JNI

JNI是Java Native Interface的縮寫,也稱為Java本地接口。是JVM規(guī)范中的一部分,因此,我們可以將任何實現(xiàn)了JVM規(guī)范的JNI程序在Java虛擬機中運行。這里的本地接口,主要指的是C/C++所現(xiàn)實的接口。因此,也使得我們可以通過這種方式重用C/C++開發(fā)的代碼或模塊。
具體關于JNI的詳細介紹,可以參見JNI的官方文檔。

Java Native Interface Specification:
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html

2、JNI的類型和數(shù)據(jù)結構

實現(xiàn)原生Java代碼與本地C/C++代碼,一個重要的環(huán)節(jié)是將原生Java的類型和數(shù)據(jù)結構映射成本地C/C++支持的相應的類型和數(shù)據(jù)結構。

(1)Java基本數(shù)據(jù)類型與原生C/C++類型對應關系如下:

Java類型 本地類型 說明
boolean jboolean 無符號,8位
byte jbyte 無符號,8位
char jchar 無符號,16位
short jshort 有符號,16位
int jint 有符號,32位
long jlong 有符號,64位
float jfloat 32位
double jdouble 64位
void void N/A

(2)Java引用數(shù)據(jù)類型與原生C/C++類型對應關系如下:

Java類型 本地類型
Object jobject
Class jclass
String jstring
Object[] jobjectArray
boolean[] jbooleanArray
byte[] jbyteArray
char[] jcharArray
short[] jshortArray
int[] jintArray
long[] jlongArray
float[] jfloatArray
double[] jdoubleArray

通過上面的對應關系可以發(fā)現(xiàn),本地類型的命名基本上是在Java原生類型明明的前面加上了個j,組成j-type格式的新類型命名,還是很直觀的。

(3)JNI引用類型的類關系圖,如下:


(上圖源自:Java Native Interface Specification文檔)

3、JNI函數(shù)的簽名

在函數(shù)的聲明中,由函數(shù)的參數(shù),返回值類型共同構成了函數(shù)的簽名。因此,將Java函數(shù)映射到本地C/C++中的對應也要遵循相應的規(guī)則。

(1)函數(shù)數(shù)據(jù)類型的簽名關系如下:

Java類型 類型簽名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
full-qualified-class(全限定的類) L
[] [
boolean[] [Z
byte[] [B
char[] [C
short[] [S
int[] [I
long[] [J
float[] [F
double[] [D

注意:

  1. full-qualified-class(全限定的類):指的是引用類型,用L加全類名表示。
  2. 數(shù)組類型的簽名,只取中括號左半邊。

(2)JNI函數(shù)簽名格式比較
Java函數(shù)原型:

   return-value fun(params1, params2, params3)

return-value:表示返回值
params:表示參數(shù)

對應函數(shù)簽名格式為:

   (params1params2params3)return-value

注意:

  1. JNI函數(shù)簽名中間都沒逗號,沒有空格
  2. 返回值在()后面
  3. 如果參數(shù)是引用類型,那么參數(shù)應該寫為:L加全類名加分號。例如:Ljava/lang/String;

根據(jù)這種規(guī)則,知道Java函數(shù)原型就能判斷出對應的JNI函數(shù)的簽名格式:

   // 原型為:
   boolean  isLoading();
   // 簽名格式為:
   ()Z
   // 原型為:
   void  setLevel(int level);
   // 簽名格式為:
   (I)V
   // 原型為:
   char  getCharFunc(int index, String str, int[] value);
   // 簽名格式為:
   (ILjava/lang/String;[I)C

4、JNI開發(fā)流程

1.簡要開發(fā)步驟

JNI的具體開發(fā)流程總結起來分為這么幾步:
(1)在原生java類中聲明native方法。native表明該方法為一個本地方法,由C/C++實現(xiàn)。
(2)使用javac命令將帶有聲明native方法的類,編譯成class字節(jié)碼。javac是jdk自帶的一個命令,一般在javapath/bin(javapath為java安裝目錄)路徑下。
(3)使用javah命令將編譯好的class生成本地C/C++代碼的.h頭文件。同樣,javah也是jdk自帶的一個命令。
(4)實現(xiàn).h頭文件中的方法。
(5)將本地代碼編譯成動態(tài)庫。注意,不同平臺的動態(tài)庫是不一樣的。
(6)在java工程中引用編譯好的動態(tài)庫。

2.開發(fā)實例

按照上面的開發(fā)步驟作為指導,來一步步實現(xiàn)個簡單的JNI的例子。
(1)新建名為HelloJNI的java工程,并新建一個聲明了native方法的類。(這里就以Eclipse作為開發(fā)IDE舉例了)

    package com.hellojni.test;

    public class HelloJni {

        public native void printJni();
        
        public static void main(String[] args) {

        }
    }

(2)使用javac編譯該HelloJni的類編譯為.class文件。當然,這步也可以由Eclipse來完成即可。

(3)使用javah命令生成頭文件。在命令行終端下輸入如下命令:

    javah -classpath E:\workplace\java\HelloJNI\src com.hellojni.test.HelloJni

classpath:是指定加載類的路徑
com.hellojni.test.HelloJni:為完整類名。注意,不需要帶java
具體javah的使用參數(shù)介紹,可以輸入javah -help。

如果,執(zhí)行成功,會在當前目錄下生成com_hellojni_test_HelloJni.h的頭文件。

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_hellojni_test_HelloJni */

    #ifndef _Included_com_hellojni_test_HelloJni
    #define _Included_com_hellojni_test_HelloJni
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
    * Class:     com_hellojni_test_HelloJni
    * Method:    printJni
    * Signature: ()V
    */
    JNIEXPORT void JNICALL Java_com_hellojni_test_HelloJni_printJni(JNIEnv *, jobject);

    #ifdef __cplusplus
    }
    #endif
    #endif

可以看到javah自動為我們生成了一個Java_com_hellojni_test_HelloJni_printJni的方法。格式是:Java_Packagename_Classname_Methodname。
首先,這里引入了jni.h的頭文件。這個是jdk自帶的一個頭文件,一般在javapath/include(javapath為java安裝目錄)。

(4)打開VS新建一個Win32控制臺應用程序,應用程序類型選擇DLL(Win平臺動態(tài)庫為.dll)。并將生成的Java_com_hellojni_test_HelloJni_printJni.h頭文件拷貝到該工程目錄下。
然后,再將該頭文件添加到工程中。如圖:


編譯生成一下。會提示找不到jni.h。因此,把jni.h拷貝到工程目錄下,并加入到項目中。jni.h一般在javapath/include(javapath為java安裝目錄)路徑下。

重新編譯生成下,會提示找不到jni_md.h。這個文件在,javapath/include/win32路徑下??截愒撐募偌尤牍こ?。并修改Java_com_hellojni_test_HelloJni_printJni.h頭文件。
將#include <jni.h>修改為#include "jni.h",在當前目錄下找jni.h頭文件。

新建一個hellojni.cpp的源文件。如下:

    #include "stdafx.h"
    #include <iostream>
    #include "com_hellojni_test_HelloJni.h"

    using namespace std;

    JNIEXPORT void JNICALL Java_com_hellojni_test_HelloJni_printJni(JNIEnv *env, jobject obj)
    {
        cout<<"Hello JNI"<<endl;
    }

(5)再將工程重新生成下,成功的話,會在工程的Debug目錄下生成一個HelloJni.dll的動態(tài)庫。將HelloJni.dll所在的路徑添加到環(huán)境變量,這樣每次重新生成,在任意目錄都能訪問。

(6)在java工程中引用剛生成的HelloJni.dll。并加入如下代碼:

    package com.hellojni.test;

    public class HelloJni {

        public native void printJni();
        
        public static void main(String[] args) {
            System.loadLibrary("HelloJni");
            
            HelloJni hello = new HelloJni();
            hello.printJni();
        }
    }

調用System.loadLibrary來加載動態(tài)庫。注意,動態(tài)庫的名字不需要加.dll。

運行java工程,這時候會提示Exception in thread “main” java.lang.UnsatisfiedLinkError: no HelloJni in java.library.path。這時候,需要重啟下Eclipse。因為,剛配置的環(huán)境變量。重啟下,Eclipse才能識別。

重啟完畢,運行java工程??刂婆_會輸入:
Hello JNI
表明整個JNI調用成功。

第一篇就介紹這么多,大體明白了JNI的整個開發(fā)流程及基本規(guī)則。下一篇將介紹下在Android NDK環(huán)境下的交叉編譯及調用過程。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容