之前有寫過一篇如何導入so庫的文章,那個studio0.8的時候用的,並且那個是針對百度地圖so,友盟so之類的開發工具用的。如果我們自己生成了so庫,那麼項目如何使用so庫呢?
上一篇我講了如何在ubuntu下利用ndk,build了ndk提供的測試項目hello-jni,這裏將使用這個libhello-jni.so
sudo sh studio.sh
打開studio,新建項目。
這裏新建項目是有講究的,一開始不清楚的情況下,我試過按照自己的喜好命名,發現是出現了這樣的錯誤:
Trying to load lib /data/data/factorytest.android.com/lib/libhello-jni.so 0x42117dc0
Added shared lib /data/data/factorytest.android.com/lib/libhello-jni.so 0x42117dc0
No JNI_OnLoad found in /data/data/factorytest.android.com/lib/libhello-jni.so 0x42117dc0, skipping init
No implementation found for native Lcom/example/hellojni/hellotest;.stringFromJNI:()Ljava/lang/String;
D/AndroidRuntime(16064): Shutting down VM
這說明了so庫沒找到,這也是和我之前介紹導入百度地圖so之類的so庫的不同點之一。可能百度地圖的demo裏面應該做好了這些準備,不需要用戶自己去設置了。
庫沒找到,看似和JNI_Onload()有關。這麼說是有原因的,現在先不說命名問題,先說一下在一個項目中,如何導入so
首先,打開app的gradle,加入:
sourceSets {
main{
jniLibs.srcDirs = ['libs']
}
}
完整代碼:
好了,做好這一步,sync now。
然後會發現項目中出現jniLibs文件夾
然後在jniLibs下新建armeabi,把so庫複製粘貼進去。
使用so庫的時候,在使用的那個類文件裏面寫入代碼
public native String stringFromJNI();
static {
System.loadLibrary("hello-jni");
}
爲什麼是stringFromJNI()而不是其他方法名呢,我們看看這個C文件的源碼
#include <string.h>
#include <jni.h>
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
jobject thiz )
{
#if defined(__arm__)
#if defined(__ARM_ARCH_7A__)
#if defined(__ARM_NEON__)
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a/NEON (hard-float)"
#else
#define ABI "armeabi-v7a/NEON"
#endif
#else
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a (hard-float)"
#else
#define ABI "armeabi-v7a"
#endif
#endif
#else
#define ABI "armeabi"
#endif
#elif defined(__i386__)
#define ABI "x86"
#elif defined(__x86_64__)
#define ABI "x86_64"
#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */
#define ABI "mips64"
#elif defined(__mips__)
#define ABI "mips"
#elif defined(__aarch64__)
#define ABI "arm64-v8a"
#else
#define ABI "unknown"
#endif
return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI ".");
}
對於傳統的JNI編程來說,JNI方法跟Java類方法的名稱之間有一定的對應關係,要遵循一定的命名規則,如下:
1) 前綴: Java_
2) 類的全限定名,用下劃線進行分隔(_):com_example__hellojni_HelloJni
(包_ 類 _方法)
3) 方法名:stringFromJNI
3) jni函數指定第一個參數: JNIEnv *
4) jni函數指定第二個參數: jobject
5) 實際Java參數: jstring, jint ….
6) 返回值的參數 : jstring, jint…. 所以對於在Java類
其對應的jni層的方法如下:
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
jobject thiz )
如果不這樣命名,當把動態庫加載進DVM的時候,通過JNIEnv *指針去查找Java Native方法對應的JNI方法的時候,就會找不到了。
注意,我們也可以利用函數註冊的方法,將Java層的方法名跟JNI層的方法名的對應關係保存起來,註冊到DVM中,就不需要這樣的命名規範了。
如果方法要帶有參數比如一個string參數,那麼可以這樣寫
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,jobject thiz , jstring str);
好了,如果就這樣運行的話,很有可能就出現剛剛提到的錯誤
No JNI_OnLoad found in /data/data/
解決方法:還是和命名有關,也就是java的代碼名和C的不匹配
studio默認情況下會生成MyApplication爲包名的項目,這樣就和剛剛那個C衝突,我們只要:
package com.example.hellojni;
public class HelloJni extends Activity
全部改成和C一樣的名字就行了。
當然了,我們也可以直接改C,改成MyApplication的也行。
最後,運行結果:
Hello From JNI !
以後會有專門介紹如何寫C生成so庫,以及需要哪些mk文件的文章