最近需要使用JNI編程,學了下JNI,並且在Android Studio下實現了一個小demo。這期間有一些坑,還好都解決了,想分享出來,希望大家少走彎路。本文中採用的平臺是Windows,NDK環境已經搭建好,這方面資料很多,大家可以自行百度。
本文分爲兩個部分:
1.如何通過編寫Jni實現native方法的調用。
2.怎樣生成.so動態庫提供給第三方使用。
1.如何通過編寫Jni實現native方法的調用。
2.怎樣生成.so動態庫提供給第三方使用。
以下是正文:
一.編寫jni文件,實現本地方法
1. 建立一個新工程,只有一個MainActivity,裏面加載庫文件並且聲明和調用若干本地方法,然後build-makeProject生成MainActivity對應的.class文件。
public class MainActivity extends Activity {
private final String TAG = "JNITEST"
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String s=returnString();
Log.d(TAG,s);
int a=1000;
Log.d(TAG,sayhello(a));
}
//加載jni
static {
System.loadLibrary("nativeTest");
}
//聲明native方法
private native int sayhello(int t);
private native String returnString();
}
2. 生成.h頭文件,該文件即連接java和c(c++)的橋樑,裏面有Android工程裏本地方法的聲明。這個文件可以在控制檯用javah命令自動生成。注意,這裏可能會碰到問題,比如我第一次就出現了找不到app.activity ,即找不到類文件,這種問題一般是沒有理解javah的用法造成的。可以採用以下兩種方法:
方法1: cd到 E:\shijue\JniHello\app\src\main
然後輸入 javah -d jni -classpath I:\Andriod\AndroidSDK\platforms\android-15\android.jar;
E:\shijue\JniHello\app\build\intermediates\classes\debug com.example.machenike_pc.jnihello.MainActivity
說明:javah是生成頭文件的命令,深綠色爲生成文件夾jni,紫紅色爲android.jar所在的位置,淺綠色爲class文件的路徑+類全名(路徑最後一個文件夾是debug之後空格+類全名)
(這裏補充下-classpath的含義:javah操作是針對類文件,-bootclasspath和-classpath就是指定在哪裏進行類文件搜索。JDK搜索類文件先後順序如下:Bootstrap classes,User classes。Bootstrap默認的是JDK自帶的jar或zip文件,它包括jre\lib下rt.jar等文件,JDK首先搜索這些文件.可以通過-bootclasspath來設置它。文件之間用分號";"進行分割。User classes搜索順序爲當前目錄、環境變量CLASSPATH、-classpath。它們用於告知JDK搜索類文件根目錄名、jar文檔名、zip文檔名,用分號";"進行分隔。)
方法2: cd到E:\shijue\JniHello\app\build\intermediates\classes\debug目錄下,直接javah -d jni com.example.machnike_pc.jnihello.MainActivity 即可
3. 在生成的jni目錄下寫一個c或者c++文件,文件名隨意,實現本地方法 ,之後需要在該路徑下再加一個空的cpp或c文件(估計是軟件的bug,不加的話很可能出ndk錯誤),比如我加了個util.cpp的文件,裏面什麼都不寫。
下面是我的c++文件代碼
#include<jni.h>
#include<stdio.h>
#include<com_example_machenike_pc_jnitest2_MainActivity.h>
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jint JNICALL Java_com_example_machenike_1pc_jnitest2_MainActivity_sayhello
(JNIEnv *, jobject, jint);
JNIEXPORT jstring JNICALL Java_com_example_machenike_1pc_jnitest2_MainActivity_returnString
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
JNIEXPORT jint JNICALL Java_com_example_machenike_1pc_jnitest2_MainActivity_sayhello
(JNIEnv * env, jobject jobj, jint jnumber)
{
int modify=jnumber+1;
return modify;
}
JNIEXPORT jstring JNICALL Java_com_example_machenike_1pc_jnitest2_MainActivity_returnString
(JNIEnv *env, jobject jobj)
{
return env->NewStringUTF("I'm comes from to Native Function!");
}
4,如果ndk版本不是最新的,需要在gradle.properties文件下加入:
android.useDeprecatedNdk=true
5,配置ndk路徑,這裏也可以在AS的設置裏面配置。我採用的方法是在local.properties文件最後一行加入:
ndk.dir=I\:\\Andriod\\NDK\\android-ndk-r10b
6,build.gradle(app下):文件下加入:(defaultconfig裏面)
ndk{
moduleName "nativeTest"
}
此時運行程序已經可以實現本地方法了,之後可以再生成so庫文件,方便使用。
二,生成.so動態庫
(這裏說一下,貌似Android studio已經寫好了.mk文件,上面的步驟完成後,直接rebuild一下就自動生成爲了.so動態庫,下面的方法也能生成,可以看一下,很有用)
1,在jni文件夾下新建Android.mk文件,寫入以下內容:
LOCAL_PATH := $(call my-dir) //固定寫法,把路徑賦給LOCAL_PATH變量 include $(CLEAR_VARS) //清除其他LOCAL變量 LOCAL_MODULE := nativeTest //這個模塊的名字,最後生成的.so的名字就是它,要跟java裏面的loadLibray的名字一樣。 LOCAL_SRC_FILES := nativeTest.cpp\ //這裏是要編譯的文件,\ 符號是換行 util.cpp include $(BUILD_SHARED_LIBRARY) //SHARED_LIBRARY就是動態庫,即.so文件
這裏的寫法是最簡單的一個例子,用的時候把註釋去掉。每一行都是很關鍵,不能省略。至於makefile怎麼編寫內容比較多,此處不贅述。
2,在工程根目錄下新建application.make文件,寫入以下內容:
APP_PROJECT_PATH := $(call my-dir) APP_MODULES := nativeTest
3,在命令行下,cd到jni目錄(就是之前javah -d jni生成的那個文件夾)下,輸入指令: ndk-build,等一會即可生成.so文件。位於lib目錄下,將其放到app/src/main/jniLibs目錄下就能用了。
FAQ:
1,生成的so文件在使用時需要注意:包名不能變,拿上文舉例,本地方法位於com_example_machenike_pc_jnitest2_MainActivity這個類下,如果在別的地方用,需要完整的建立這個包名和類。
2,c和cpp文件均可以用來寫jni,寫法上略有不同。
3,需要注意java裏面成員方法和靜態方法通過javah生成的頭文件略有不同,一個參數是jclass,另一個是jobject。
4,不用javah生成頭文件也行,推薦第一次寫的時候用javah生成,後面修改的時候(比如參數改變)可以直接在c文件裏手動修改。