Ubuntu下android studio如何使用ndk-build的so庫

之前有寫過一篇如何導入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文件的文章

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