Andorid Studio NDK開發-使用NDK庫

姜家志發表於2019-02-28

C語言是一個巨大的寶庫,Android是一個以Linux為基礎的開源作業系統,系統底層很多的實現都是基於C語言開發,比如影像處理,加密等。另一方面C語言的執行效率也比Java開發要高很多,因此為了高效率的執行有時候也會使用C語言開發一些功能。再Android上面使用C語言開發就需要使用NDK,在使用NDK開發的過程中會使用大量的庫,系統自帶的庫,第三方庫以及自己寫的庫等。
使用Android Studio呼叫NDK的庫是非常簡便,NDK內建了一些庫方便開發者使用比如:Log庫,還有一些比較常用的第三方庫比如:OpenSSL。下面會分別介紹下,這兩種庫的使用。

呼叫系統庫

Log是在Android開發過程用來除錯程式必備的工具之一,他會把日誌資訊輸入到Logcat中,如何在NDK中使用android.util.Log方便在Logcat中檢視JNI程式的執行情況呢?
這就需要在NDK開發中匯入Android系統的Log庫。首先需要在gradle中引入Log庫,引入的方式使用是在ldLibs中新增:

model{
 ....
 android {
        compileSdkVersion 23
        buildToolsVersion "23.0.2"
        ndk {
            moduleName "experiment"    
            ldLibs.addAll([ `log`]);
        }
   }
 }複製程式碼

直接gradle中的ldLIbs中加入log就可以了,如果還需要引入其他的系統庫,只要在陣列中直接增加即可。
下面來測試下log庫的使用,先定義一個native的方法:

public static native void callLogFromJni();

在JNI中呼叫Log庫:

//引入 log
#include <android/log.h>

JNIEXPORT void JNICALL
Java_com_jjz_NativeUtil_callLogFromJni(JNIEnv *env, jclass type) {

    __android_log_print(ANDROID_LOG_INFO,"jni-log","from jni log");

}複製程式碼

第一個引數,ANDROID_LOG_INFO是Log的級別他包含:

typedef enum android_LogPriority {
    ANDROID_LOG_UNKNOWN = 0,
    ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */
    ANDROID_LOG_VERBOSE,
    ANDROID_LOG_DEBUG,
    ANDROID_LOG_INFO,
    ANDROID_LOG_WARN,
    ANDROID_LOG_ERROR,
    ANDROID_LOG_FATAL,
    ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */
} android_LogPriority;複製程式碼

一般我們常用的是

  • ADNROID_LOG_VERBOSE->Log.v
  • ANDROID_LOG_DEBUG->Log.d
  • ANDROID_LOG_INFO-> Log.i
  • ANDROID_LOG_WARN->Log.w
  • ANDROID_LOG_ERROR->Log.e

第二個引數是tag,用來方便的對Log分類。
第三個引數是message,對應Log的具體資訊。

一般會採用巨集定義的方式,定義Log的輸出的方法,方便呼叫,例如:

#define LOG_TAG "jni-log"
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)複製程式碼

這裡定義了一個warning log的巨集,在程式碼裡面可以直接呼叫:

  LOGW("log from  define");複製程式碼

系統庫的呼叫比較簡單方便,使用第三方庫就比較麻煩些,第三方庫需要使用NDK重新編譯才能在JNI中呼叫。

呼叫第三方類庫

OpenSSL是最常用的加密庫,下面就以OpenSSL為例,介紹下在gradle-experimental中如何引入第三方類庫。
關於如何編譯Android下的OpenSSL詳見:Andorid Studio NDK 開發 – 編譯 OpenSSL 類庫
首先定義對於庫的repositories,用來指定庫的基本資訊,包括庫檔案的路徑,標頭檔案的路徑以及連結的方式等,詳見如下程式碼:

model {
   repositories{
        libs(PrebuiltLibraries) {
            // Configure one pre-built lib: static
            openssl {
                // 標頭檔案地址
                headers.srcDir "/usr/local/ssl/android-23/include"
                // 靜態連結庫的引用,
                binaries.withType(StaticLibraryBinary) {
                    staticLibraryFile = file("libs/libcrypto.a")
                }
                //動態連結庫的引用
//                binaries.withType(SharedLibraryBinary) {
//                    sharedLibraryFile = file("libs/libcrypto.so")
//                }
            }

        }

    }
  }複製程式碼

c語言的類庫分為靜態連結庫(.a)動態連結庫(.so),靜態類庫和動態類庫在引入方式上是不一樣的,分為對應:

  • StaticLibraryBinary->靜態庫
  • SharedLibraryBinary-> 動態連結庫

這裡引入的庫為靜態連結庫,庫的repositories名稱為:openssl.
定義好了一個repositories,現在就需要呼叫了,在gradle可以指定庫的依賴:

model{
 ......
 android{
  .....
   sources {
            main {
                jni {
                    dependencies{
                        //靜態連結庫
                        library `openssl` linkage `static`
                        //動態連結庫
                        //  library `openssl` linkage `shared`
                    }
                    source {
                        srcDir "src/main/jni"
                    }
                }
                jniLibs{
                    source{
                        srcDir "libs/"
                    }
                }
              ....
            }
        }
 }
}複製程式碼

model.android.sources.main中指定庫的依賴,依賴的是上面定義的openssl,linkage型別為static,如果是動態連結庫則linkage就是shared
因為在編譯OpenSSL設定了只支援arm結構的cpu,所以還需要指定abi為對應為arm架構,在model.android新增配置:

 ndk {
      moduleName "experiment"
      abiFilters.addAll([`armeabi`, `armeabi-v7a`])      
     }複製程式碼

定義了庫的連結,就可以在程式碼中測試下OpenSSL的使用了。
首先定義一個native方法,該方法的目的是從OpenSSL中讀取隨機數:

public static native byte[] getRandom();複製程式碼

對應的JNI方法:

//引入OpenSSL的rand
#include <openssl/rand.h>
JNIEXPORT jbyteArray JNICALL
Java_com_jjz_NativeUtil_getRandom(JNIEnv *env, jclass type) {
    unsigned char rand_str[128];
    //使用OpenSSL的方法
    RAND_seed(rand_str, 32);
    jbyteArray bytes = (*env)->NewByteArray(env, 128);
    (*env)->SetByteArrayRegion(env, bytes, 0, 128, rand_str);
    return bytes;

}複製程式碼

RAND_seed是OpenSSL的方法,能夠讀取隨機數。這段程式碼的意思就是讀取一個128位的隨機數,然後轉換為Java的byte陣列
在介面上面使用實現出讀取的隨機數內容:

tv2.setText(Base64.encodeToString(NativeUtil.getRandom(), Base64.DEFAULT));複製程式碼

執行之後可以在介面看到一段隨機的字串顯示:

Andorid Studio NDK開發-使用NDK庫
從openssl中讀取隨機數

可以看到使用gradle-experiment無論是呼叫系統庫還是第三方庫都比較簡單。
以上原始碼地址:github.com/jjz/android…

相關文章