一步一步學習JNI(一)

前言

       本篇的主要目的就是JNI開發入門,使大家對JNI開發流程有一個大致的瞭解,後續再進行深入學習。

       JNI不是Android特有的,JNI是Java Native Interface單詞首字母的縮寫,就是指用C或者C++開發的接口。JNI是JVM規範中的一部份,因此JNI程序在任何實現了JNI規範的Java虛擬機中都可以運行。

       作爲一個Android開發,這裏不大書特書學習JNI的必要性和重要性,很多博客,文章都有講述,這裏主要給出入門步驟,讓大家可以根據一步一步操作進行學習。

       俗話說學習動最快的方式,就是找一個會的人,照着學,跟着做。照貓畫虎,也能描出一個大概!

準備工作

       我的開發環境爲Mac os x 10.10.5,Eclipse 4.4.1(也可以不需要,有這個只是方便寫代碼,記事本,Sublime都可以)。由於環境爲Mac,因此下面大部分的配置都是針對Mac的。

1,Java環境變量

       由於需要Java環境,因此必須要設置Java環境變量,網上有很多方式教大家怎麼設置。這裏給出mac的設置方式。進入terminal。

1,輸入cd ~進入當前用戶
2,ls -al 可以看到一個.bash_profile文件,這就是設置環境變量的地方
3,如果沒有輸入:touch .bash_profile (用來修改文件時間戳,或者新建一個不存在的文件。)
4,輸入 vim .bash_profile 輸入E(表示進入編輯模式),輸入I表示進入編輯輸入
5,輸入 export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home (這是我的目錄,你可以去Library下查看自己的JavaVirtualMachines)
6,按ESC鍵退出輸入模式,:wq 退出編輯模式
7,source .bash_profile (使配置生效)
8,echo $JAVA_HOME 如果有輸入表示設置生效

實例操作

1,編寫一個Java類

       這裏類名爲Hello,包名爲com.sunny(可以隨便設置),代碼如下:

package com.sunny;

public class Hello {

    static{
        //System.loadLibrary("Hello");
        System.load("/Users/doc/Jni/jniHello.jnilib");
    }

    public static native int getSum(int a, int b);

    public static void main(String[] args) {
        // 虛擬機掃描加載的lib路徑
        System.out.println(System.getProperty("java.library.path"));
        int sum = getSum(2, 5);
        System.out.println(sum);
    }

}

       從上面的代碼中我們設置了一個native函數,進行兩數字的求和,並且在靜態代碼塊中加載了動態鏈接庫代碼。這裏有兩種加載方式:

System.loadLibrary("Hello")

該方式不需要加入lib前綴,也不需要jnilib(不同的操作系統會有不同的後綴),虛擬機會自動從System.getProperty(“java.library.path”)路徑進行掃描加載。後面會講述怎麼將自己的動態鏈接庫加入到掃描路徑。如果不設置會拋出UnsatisfiedLinkError異常。

System.load("/Users/doc/Jni/jniHello.jnilib");

該方式是設置的全路徑,他會從設置的路徑進加載。

2,生成.class文件

       這裏我們對上述的Java類編譯生成 .class文件,我上述的代碼在/Users/doc/Documents/Eclipse/Jni路徑中,這裏如果你直接採用Eclipse->Run As -> Java Application是不能運行的,因爲這裏還沒有動態鏈接庫,會拋出以下的異常。

Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load library: /Users/doc/Jni/jniHello.jnilib
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1827)
    at java.lang.Runtime.load0(Runtime.java:809)
    at java.lang.System.load(System.java:1086)
    at com.sunny.Hello.<clinit>(Hello.java:7)

       輸入命令:

javac src/com/sunny/Hello.java -d bin

-d 表示將生成的.class文件放到bin目錄下,你可以去本地看看是否已經有該文件。

3,生成.h文件

       輸入命令:

javah -jni -classpath bin -d jni com.sunny.Hello

上述生成的.h文件爲com_sunny_Hello.h,生成規則爲包名加類名,.轉爲_,尾綴爲.h。
-jni爲可選參數
-classpath 類查找路徑,當前查找路徑爲bin目錄
-d 與上述相同,表示將生成的.h放置到jni目錄。

       也可以通過-o參數生成指定的Hellox.h,該參數與-d互斥。命令如下:

javah -jni -classpath bin -o Hellox.h com.sunny.Hello 

       生成的.h文件如下:

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

#ifndef _Included_com_sunny_Hello
#define _Included_com_sunny_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_sunny_Hello
 * Method:    getSum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_sunny_Hello_getSum
  (JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

4,編寫.c文件實現.h

       這裏可以採用VS來進行編寫,也可以用一個文本文檔來編寫。代碼如下:

#include "com_sunny_Hello.h"

#ifdef __cplusplus
extern "C" 
{
#endif
/*
 * Class:     com_sunny_Hello
 * Method:    getSum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_sunny_Hello_getSum
  (JNIEnv * env, jclass cls, jint a, jint b){
    return a+b;
  }

#ifdef __cplusplus
}
#endif

5,生成動態鏈接庫

       這裏先補充前面的不同操作系統生成不同的名稱。

Mac OS X : libHello.jnilib(又有前綴又有後綴)
Windows :Hello.dll(沒有lib前綴)
Linux/Unix:libHello.so(是不是發現了android中常用的.so了)

       Mac生成命令如下:

gcc -dynamiclib -o /Users/doc/Jni/libHello.jnilib Hello.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin

-dynamiclib表示生成動態鏈接庫
-o表示生成動態鏈接庫放置的位置於名字
-framework JavaVM -I:編譯JNI需要用到JVM的頭文件(jni.h),第一個目錄是平臺無關的,第二個目錄是與操作系統平臺相關的頭文件,這裏就是我們前面爲什麼要設置JAVA環境變量的原因。

       Windows生成方式如下:

       手頭沒有下載vs,因此從網上搜索了一下vs生成dll方式。開始菜單–>所有程序–>Microsoft Visual Studio 2012–>打開VS2012 X64本機工具命令提示,用cl命令編譯成dll動態庫:

cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD Hello.c -FeHello.dll  

-I :和mac一樣,包含編譯JNI必要的頭文件
-LD:標識將指定的文件編譯成動態鏈接庫
-Fe:指定編譯後生成的動態鏈接庫的路徑及文件名

       Linux生成命令如下:

       Linux生成,記住該生成方式,因爲後續Android底層是linux系統,因此最終生成使用的都是.so動態鏈接,生成的命令如下:

gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -shared Hello.c -o libHello.so  

       由於我本地下載的是mac版的jdk,編譯會出現的錯誤,因爲找不到對應linux平臺的相關信息。

In file included from Hello.c:1:
In file included from ./com_sunny_Hello.h:2:
//Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/include/jni.h:45:10: fatal error: 'jni_md.h' file not found
#include "jni_md.h"
         ^
1 error generated.

       這裏我們爲了演示怎麼用命令行生成.so,因此把上述命令改成如下方式,後續會用AndroidStudio來生成.so動態鏈接庫:

gcc -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin -fPIC -shared Hello.c -o libHello.so

-I 與-o與上述概念一致。
-fPIC: 編譯成與位置無關的獨立代碼
-shared:同Fe與dynamiclib,編譯成動態庫

6,運行結果

       這裏可以採用Eclipse->Run As -> Java Application來運行了, 還要用命令行來運行。輸入如下命令:

java -classpath bin com.sunny.Hello

輸出結果如下:

/Users/doc/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.
7

       上述採用的是System.load(“/Users/doc/Jni/jniHello.jnilib”)方式,這裏填入的是全路徑,因此編譯運行時,會從輸入的路徑去查找動態鏈接庫,輸出結果爲:第一行是java.library.path掃描的路徑,第二行7就是我們native返回的結果。

       前面我們說了兩種加載方式。上面編譯的是全路徑,不需要將當前的動態鏈接庫放置到掃描路徑下面。如果採用System.loadLibrary(“Hello”)方式,則需要將動態鏈接庫放置到Java虛擬機能找到的地方。如果不進行設置則會拋出如下異常:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no Hello in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
    at java.lang.Runtime.loadLibrary0(Runtime.java:870)
    at java.lang.System.loadLibrary(System.java:1122)
    at com.sunny.Hello.<clinit>(Hello.java:6)

       怎麼鏈接到動態鏈接庫吶?可以採用如下三種方式進行設置:

       方法1:則需要將剛纔生成的動態鏈接庫放置到java.library.path掃描到的任何一個路徑下面。

       方法2:項目郵件選擇properties
這裏寫圖片描述

       之後選擇Java Build Path - Libraries - JRE System Library - Native library location - edit
這裏寫圖片描述

       之後選擇本地生成的動態鏈接庫
這裏寫圖片描述

       方法3:給jvm添加“-Djava.library.path=動態鏈接庫搜索目錄”參數,命令如下:

 java -Djava.library.path=/Users/doc/Jni/ -classpath bin
 com.sunny.Hello

       設置了動態鏈接庫後,方法二,與方法三輸出結果如下:

/Users/doc/Jni/
7

       從結果看出,這裏輸出的java.library.path路徑與全路徑輸出已經不相同了,輸出從默認查找的路徑變成了設置的路徑。

總結

       這裏主要做了一個初步的入門,後續再進行深入學習。如果有錯誤請指出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章