1. 程式人生 > >Android NDK開發入門

Android NDK開發入門

  接觸Android有一段時間了,NDK算是一道坎,因為對c語言不熟悉,所以也沒怎麼去接觸這一塊,但是最近的學習,越發的覺得,如果想讓app能有更高的效率,比如在影象的處理方面,遊戲開發方面,甚至於要做一些涉及系統層面的操作時,不可避免的要去用到NDK。通過利用c/c++的能力來彌補java的缺點。

NDK的好處:
1. 提高程式的執行效率
2. 程式碼保護,java層容易被反編譯,但是ndk層較難
3. 便於移植,而且也能方便使用c/c++開源庫

這個是Android Studio的ndk入門版,於eclipse的不同,使用的是gradle進行構建。
下面就從hello ndk開始進入ndk的世界。這個sample可以在下載了ndk之後在ndk資料夾中的sample資料夾下找到,匯入Android Stuido就可以開始學習了。

Android Studio中有兩種方法去使用c/c++的程式碼:
1. 使用現有的.so庫
2. 使用專案中當前的c/c++程式碼

使用現有的.so庫

這一步是比較簡單的,只要把所有的.so檔案拷貝到對應的目錄檔案下就可以了。要對應好不同的平臺。
這裡寫圖片描述
接著,在要使用的地方加上載入庫的程式碼,就可以使用當前的.so庫了。

String libName = "helloNDK"; // 載入的module的名字,不用加.so
System.loadLibrary( libName );

庫的名字構成是由,lib + moduleName + .so,當去載入庫時,只需要使用moduleName不用加字首或字尾。

如果想改變使用的jnilibs的路徑,可以在gradle中定義如下的程式碼,既可以改變引用的.so的檔案路徑。

android {
    sourceSets.main {
        jniLibs.srcDir 'src/main/filePath' // 設定有so庫的位置
    }
}

使用當前專案中的c/c++程式碼

這個是很方便的一個開發方式,而且通過gradle去配置,將原本eclipse的複雜度降低了。主要要做以下的事情
1. 配置ndk的路徑
2. 在gradle中配置ndk的module
3. 新增c/c++檔案

配置ndk的路徑

  可以通過在專案的ModuleSetting中設定,右鍵專案,選擇ModuleSetting,在sdk location中可以選擇下載或者是配置路徑,下載的話去官網就有的下載了。
  也可以在工程根目錄下的local.properties中增加ndk的配置。

ndk.dir=/Users/ndkPath

在gradle中配置ndk module

指明將要編譯的c/c++程式碼

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        ...
        ndk {
            moduleName "modulename"
        }
    }
}

當然可以在增加一些指定的配置,類似於cFlags, stl, ldLibs.

ndk {
    moduleName "myEpicGameCode"
    cFlags "-DANDROID_NDK -D_DEBUG DNULL=0" // 定義巨集變數
    ldLibs "EGL", "GLESv3", "dl", "log"         // 連結這些庫
    stl "stlport_shared"                        // Use shared stlport library
}

新增c/c++檔案

配置好上述步驟之後,我們就可以開始開發我門的c/c++程式碼了。c/c++程式碼的位置是在src/main/jni目錄下,檔案包括
1. Android.mk
2. Application.mk
3. c/c++檔案包括標頭檔案

當然和引入so庫相似,對於c/c++的程式碼的位置也是可以改變,通過在gradle中進行配置

android {
    sourceSets.main {
        jni.srcDirs 'src/main/path' // your path
    }
}

可以通過productFlavors去設定對於不同平臺時候的不同的變數,比如通過設定全域性變數指定不同平臺去載入不同的標頭檔案。
又或者編譯製定平臺的so庫而不是所有的平臺。通過這個方法來得到我們想要的結果。

android {

    productFlavors {
        x86 {
            ndk {
                abiFilter "x86"
            }
        }
        arm {
            ndk {
                abiFilter "armeabi-v7a"
            }
        }
    }

}

c/c++ 檔案

這裡開始真正的關於Hello NDK的程式碼編寫。首先在MainActivity.java中先定義好我們的native的方法,使用的native的關鍵字。

private native String sayHello();
// 在btn的setListener中設定toast
Toast.makeText(MainActivity.this, sayHello(), Toast.LENGTH_SHORT).show();

在src/main/jni目錄下建立hello-jni.c檔案,方法名字的定義要注意,否則會引起方法找不到的錯誤,方法名稱為Java_+完整類路徑+方法。當然,為了防止手動的錯誤,可以使用javah的命令生成,具體請繼續往下看。

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

jstring Java_com_yxp_hellondk_MainActivity_sayHello( JNIEnv* env, jobject thiz ) {
    return (*env)->NewStringUTF(env, "Hello NDK!");
}

使用javah生成標頭檔案

# 進入src/main/java資料夾,-d指明輸出檔案路徑
javah -d ../jni com.example.MainActivity

在gradle指定ndk module

defaultConfig {
        ...
        ndk {
            moduleName "hello-jni"
        }
    }

回到MainActivity.java中,引入ndk library,library的名稱也就是module name無需加字首或者字尾。

static {
    System.loadLibrary("hello-jni");
}

Application.mk(官網

Application.mk檔案的路徑是在src/main/jni目錄下,用來描述工程中需要用到的native庫,包括靜態庫,共享庫和可執行檔案。在HelloNdk專案中的Application.mk內容如下:

APP_ABI := all

預設情況下,NDK的編譯系統根據 “armeabi” ABI生成機器程式碼。可以使用APP_ABI 來選擇一個不同的ABI。

比如:為了在ARMv7的裝置上支援硬體FPU指令。可以使用:

APP_ABI := armeabi-v7a

當然,當有多個定義時,以空格分開。

Android.mk(官網

Application.mk檔案的路徑是在src/main/jni目錄下,Android.mk檔案是用來描述ndk相關原始檔,便於構建工程。在構建的過程中它會被解析多次,以完成構建。在Android.mk檔案中,可以定義一個到多個模組,模組之間可以共享檔案。在檔案當中,不用列出具體的標頭檔案或者依賴,這些ndk編譯器都會自動完成。
ndk編譯系統的系統的變數都有如下的定義:

  • LOCAL_為字首的變數,比如LOCAL_MODULE
  • 以PRIVATE_、NDK_、APP_為字首的變數
  • 全小寫字母的變數

所以在Android.mk檔案中使用自定義變數的時候,建議使用MY_字首。
在這個HelloNdk工程中,Android.mk檔案定義如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

CLEAR_VARS變數,主要在構建的時候會清除所有的LOCAL_開頭的變數的定義,包括LOCAL_MODULE、LOCAL_SRC_FILES等,但是不包括LOCAL_PATH。由於在構建的時候這些變數都是全域性的變數,所以在一個module在構建的時候,應該不受其他module的變數影響。使用方式就是:

inclued $(CLEAR_VARS)

my-dir屬於一個方法(function)的全域性定義,返回當前Android.mk檔案的檔案路徑,值得注意的是,這個方法是包含最後一個構建檔案的路徑,所以這個定義要在開頭,尤其是包含其他的檔案的時候。使用的方法是

$(call my-dir

LOCAL_PATH變數是指明當前檔案的位置,必須定義在Android.mk檔案的開頭,定義方式:

LOCAL_PATH := $(call my-dir)

BUILD_SHARED_LIBRARY變數,收集從最近的CLEAR_VARS之後的LOCAL_字首的變數,並以此生成so庫。使用方法:

include $(BUILD_SHARED_LIBRARY)

參考文件: