Android Studio NDK開發案例一 JNI開發入門

        最近在做Android的項目,用到了JNI,現將NDK的開發流程和方法整理出來,希望能夠讓剛接觸的小夥伴們少走一些彎路。

NDK

        Native Development Kit(NDK)是一系列工具的集合。它提供了一系列的工具,幫助開發者快速開發C/C++的動態庫,並能自動將so和java一起打包成apk。

JNI

        Java Native Interface(JNI)標準是java平臺的一部分,JNI是Java語言提供的Java和C/C++相互溝通的機制,Java可以通過JNI調用C/C++代碼,C/C++的代碼也可以調用java代碼。我們知道,不管是linux還是windows亦或是mac os,這些操作系統,都是依靠C/C++寫出來的,還包括一些彙編語言寫的底層硬件驅動 。java和C/C++不同 ,它不會直接編譯成平臺機器碼,而是編譯成虛擬機可以運行的java字節碼的.class文件,所以效率就比不上C/C++代碼,但是通過JNI技術可以即時編譯成本地機器碼。JNI調用示意圖:


        從上圖可以得知,JNI技術通過JVM調用到各個平臺的API,雖然JNI可以調用C/C++,但是JNI調用還是比C/C++編寫的原生應用還是要慢一點,不過對高性能計算來說,這點算不得什麼,享受它的便利,也要承擔它的弊端 。

JNI與NDK的關係

        NDK可以爲我們生成了C/C++的動態鏈接庫,JNI是java和C/C++溝通的接口,兩者與android沒有半毛錢關係,只因爲安卓是java程序語言開發,然後通過JNI又能與C/C++溝通,所以我們可以使用NDK+JNI來實現“Java+C”的開發方式。

爲什麼要NDK開發

        NDK開發具有以下優點: 

       1. 項目需要調用底層的一些C/C++的一些東西(java無法直接訪問到操作系統底層(如系統硬件等)),或者已經在C/C++環境下實現了功能代碼(大部分現存的開源庫都是用C/C++代碼編寫的。),直接使用即可。NDK開發常用於驅動開發、無線熱點共享、數學運算、實時渲染的遊戲、音視頻處理、文件壓縮、人臉識別、圖片處理等。 
        2. 爲了效率更加高效些。將要求高性能的應用邏輯使用C/C++開發,從而提高應用程序的執行效率。但是C/C++代碼雖然是高效的,在java與C/C++相互調用時卻增大了開銷; 
        3. 基於安全性的考慮。防止代碼被反編譯,爲了安全起見,使用C/C++語言來編寫重要的部分以增大系統的安全性,最後生成so庫(用過第三方庫的應該都不陌生)便於給人提供方便。(任何有效的代碼混淆對於會smail語法反編譯你apk是分分鐘的事,即使你加殼也不能倖免高手的攻擊) 
        4. 便於移植。用C/C++寫得庫可以方便在其他的嵌入式平臺上再次使用。

NDK開發案例一   JNI開發入門

開發工具:Android Studio 2.3.3

NDK:android-ndk-r14b

 

1、打開Android Studio,選擇“Start a new Android Studio procet”創建android工程,如下圖:

2、根據項目需要修改"項目名稱"、"公司域名"、"app包名"、"工程路徑",勾選“Include C++ support”選項,該選項用於支持JNI開發,如下圖:

3、默認勾選“Phone and Tablet”,根據項目需求選擇兼容的Android最小版本。可點擊“Help me choose”查看各版本功能。如下圖:

4、選擇項目活動類型,該案例默認選擇“Basic Activity”模板,如下圖:

5、設置“活動名稱”、“界面佈局名稱"、"界面標題等",如下圖:

6、設置JNI支持標準(有“Toolchain Default”和“C++11”兩種標準可供選擇),此項根據項目需求自行選擇,該案例默認選擇“Toolchain Default”,如下圖:

選擇“Finish”,等待工程創建完成,如下圖:

可以看到編譯窗口有報錯:Error:A problem occurred configuring project ':app'.

先不去管錯誤,執行步驟7,配置NDK。

7、配置NDK。

        下載安裝NDk後,在Android Studio菜單項選擇File->Project Structure,配置NDK路徑,如下圖:

打開gradle.properties,添加

android.useDeprecatedNDK=true

如下圖:

重新編譯工程,報錯(如上圖):

        Error:(38, 13) Failed to resolve: com.android.support:design:28.+

        ......

        Error:(36, 13) Failed to resolve: com.android.support:appcompat-v7:28.+

        ......

 

Error解決辦法:

        打開\MyApplication\app\build.gradle,在buildTypes {...}中添加代碼repositories {...}

buildTypes {
    repositories {
        maven{
            url "https://maven.google.com"
        }
    }
    
    ......
}

如下圖:

重新編譯工程,未報錯。

8、配置生成so庫類型。

        打開\MyApplication\app\build.gradle,在cmake {...}中添加代碼abiFilters "armeabi", "armeabi-v7a", "x86",如下:

    defaultConfig {
        ......

        externalNativeBuild {
            cmake {
                ......

                abiFilters "armeabi", "armeabi-v7a", "x86"//cpu的類型
            }
        }
    }

如下圖:

        armeabi、armeabi-v7a和x86都表示CPU的類型。一般的手機或平板都是用arm的cpu。不同的cpu的特性不一樣,armeabi就是針對普通的或舊的。arm v5 cpu,armeabi-v7a是針對有浮點運算或高級擴展功能的arm v7 cpu。

        armeabi:默認選項,將創建以基於ARM* v5TE 的設備爲目標的庫。 具有這種目標的浮點運算使用軟件浮點運算。 使用此 ABI (二進制接口)創建的二進制代碼將可以在所有 ARM*設備上運行。所以armeabi通用性很強。但是速度慢。

        armeabi-v7a:創建支持基於 ARM* v7 的設備的庫,並將使用硬件 FPU 指令。armeabi-v7a是針對有浮點運算或高級擴展功能的arm v7 cpu。

        x86:支持基於硬件的浮點運算的。

        所以,如果項目只包含了 armeabi,那麼在所有android設備都可以運行;如果項目只包含了 armeabi-v7a,除armeabi架構的設備外都可以運行; 如果項目只包含了 x86,那麼armeabi架構和armeabi-v7a的Android設備是無法運行的;如果同時包含了 armeabi, armeabi-v7a和x86,所有設備都可以運行,程序在運行的時候去加載不同平臺對應的so,這是較爲完美的一種解決方案,同時也會導致包變大。

9、編寫JNI接口程序。

在路徑\MyApplication\app\src\main\cpp\下會看到文件native-lib.cpp,因爲我們在創建android項目時勾選了C++ support,所以自動生成了JNI接口文件native-lib.cpp並附帶一個JNI接口程序stringFromJNI,我們以後就在該文件裏面編程JNI接口程序,此案例暫時不對其做修改,用其提供的JNI接口做演示,如下圖:

//native-lib.cpp


#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_administrator_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

10、CMake

在Android Studio  2.2以上版本中,使用的是CMake來編譯我們的C代碼,如下圖:

 如果你在cpp文件夾中新建了cpp文件頭文件,就需要手動配置CMakeLists.txt文件。 IED自動生成的CMakeLists中默認添加的cpp文件只有native-lib.cppCMakeLists.txt中以#號開頭都是註釋,這裏把它們都刪了也就更清楚了,刪除註釋後的內容如下

add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

第一個參數爲庫名字,第三個參數爲cpp文件路徑。 如果你新建了一個Test.cpp,就需要把這個文件配置到CMakeLists中,但這樣很麻煩。有沒有一種可以自動包含cpp文件夾下源文件的方法呢?當然有,請看下面。

# 查找cpp目錄下的所有源文件
# 並將名稱保存到 DIR_LIB_SRCS 變量
aux_source_directory(src/main/cpp/ DIR_LIB_SRCS)

# 生成鏈接庫
add_library (native-lib SHARED ${DIR_LIB_SRCS})

# 導入cpp目錄下的所有頭文件
include_directories(src/main/cpp/)

你可以把IED生成的那段替換成這段,這樣你以後在cpp目錄下新建c++文件時,就不用手動配置了,只需要點擊菜單欄Build->Refresh Linked C++ Projects,刷新後就可以在Android視圖下的cpp中看到你新建的c++文件。

 

CMakeLists需要在\MyApplication\app\build.gradle中設置路徑才能生效,如下圖:

CMakeLists的路徑設置代碼是創建android項目時自動生成的,如果將CMakeLists移動到其它目錄,只需在這裏重新指定目錄既可。

cmake入門:https://blog.csdn.net/weixin_40779546/article/details/84821923

11、生成so庫文件。

編譯工程,在目錄\MyApplication\app\build\intermediates\cmake\debug\obj\下生成對應的so文件,如下圖:

12、Java調用C代碼,此處使用項目自動生成的例程做演示,如下圖:

13、創建虛擬設備,如下圖:

14、在選定的設備上運行app,顯示app調用jni的打印消息"Hello from C++",如下圖:

 

第三方so庫的使用

        除了調用自己生成的so庫外,我們還可能需要調用第三方的so庫,Android Studio中和Eclipse中使用so庫略有不同。 
我這裏以Android Studio 2.3.3爲例。 
       AS中默認是配置好so庫的路徑的,但是並沒有給你生成相應的文件夾,所以首先需要新建文件夾。 
切換到Project視圖,依次展開app->src->main,然後在main目錄下新建一個jniLibs的文件夾,注意大小寫和s,建議複製粘貼。 
然後點擊菜單欄Build->Make Project,在切換回Android視圖,就可以看到多出了一個jniLibs的目錄。

  1. 把so庫複製到jniLibs目錄下,當然注意是放到對應的平臺目錄下,一般第三方so庫提供的時候都會說明是那個平臺,比如一般都是armeabi平臺,即放到jniLibs/armeabi目錄下。
  2. 在java中聲明native方法,這裏和自己寫jni是一樣了,唯一的區別是自己寫的有cpp文件。所以這裏寫的native方法在IDE中會紅色標識表明找不到對應cpp中的函數,但不要緊張,這不影響編譯,也不影響運行。
     
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章