使用NDK優雅的編譯靜態/動態庫

 前一陣子攢了點工程小經驗,忙的有點亂,梳理一下。

隨着Android Studio的興起跟Eclipse的沒落(僅限於Android開發用途吧),越來越多人傾向於使用Android Studio搞Android開發,我在之前的時候也是這樣。但是現在知道了,用NDK開發包通過ndk-build命令編譯庫更方便(NDK包 + Cygwin)。

進入正題,就是這麼直接。

一、安裝配置Cygwin開發環境

作爲一個程序猿,基本搜索的能力還是要有的,自己去官網下載Cygwin就好了。安裝的時候下載源找個順眼的(哈哈哈)就闊以了,然後最關鍵的是在下載環境包的時候,有兩個選擇:

1、勾選安裝Devel(點擊列表中Devel,將後面的Default改爲Install),這是爲了下載需要的NDK開發需要的環境包。


2、或者一個個勾選下載autoconf2.1、automake1.10、binutils、gcc-core、gcc- g++、gcc4-core、gcc4-g++、gdb、pcre、pcre-devel、gawk、make共12個包(注:版本號也許會不太一樣)。   

接下來就next下載安裝吧。

二、下載NDK

之前項目組編譯32位Android庫用的NDK r8e版本(此版本只支持32位庫編譯),編譯64位Android庫用的是NDK r10版本(此版本理論上支持編譯32位個64位庫,但是在實際中有些小問題,不知道是項目組內流傳的NDK r10包有問題還是NDK r10版本本身就有問題)。我後來選了NDK r10c版本,32位、64位庫都編譯木有問題,用着還不錯。

假如我推薦的話,就下載NDK r10c吧。這個直接解壓到想要的位置就好了,不需要配置什麼。如下圖,我下載的該版本的自解壓文件。


三、關聯

首先保證運行一次Cygwin(保證Cygwin生成必要的文件)。然後找到Cygwin安裝目錄,進入home文件夾。


你會看到有個以你電腦用戶名命名的文件夾,進入這個文件夾。以文本形式打開.bash_profile文件,在其末尾添加Shell環境變量:

NDK10C=/cygdrive/D/Usual/android-ndk-r10c
export NDK10C


其中“D/Usual/android-ndk-r10c”爲我NDK的目錄。 

設置Cygwin的shell變量的意義就在於用一個變量就代表了後面的一大串路徑,開發起來方便快捷。

四、使用

 

使用起來就是: 

1、將NDK編譯用到的Android.mkApplication.mk以及相關的jni層源文件、頭文件放在一個文件夾裏,這裏我的命名爲jni文件夾。

2、打開Cygwin, cd到jni文件夾, 然後用NDK開發包的ndk-build命令就可以開始編譯Android庫了。 Cygwin如何知道ndk-build這個命令在哪裏呢?前面我們設置的shell變量就起作用了,以我的配置爲例子,在jni目錄下執行:

$NDK10C/ndk-build

這樣就可以執行編譯了。對於“$NDK10C/ndk-build”中的“$”,它是跟“NDK10C”搭配使用的,自己可以搜搜"shell 變量"。

五、關於Android.mk、Application.mk文件書寫

1、基礎內容:好的社區好的開發文檔很重要,谷歌在這方面那必須好。去Android開發官網去找NDK開發相關內容,很豐富的入門文檔。

2、進階:我看了好多中文博客,參差不齊吧,感覺還是Github開源代碼能學到不少相關的知識,所以也推薦多用用Github。

接下來,以一個簡單例子講解一下如何編寫make文件來讓NDK編譯更自動化,先放一下文件。

Android.mk文件:

#file: Android.mk

LOCAL_PATH := $(call my-dir)
my_LOCAL_PATH:=$(LOCAL_PATH)

#設置編譯項  base_a  base_b  base_c  final
to_COMPILED:=base_a

#編譯base_a庫
ifeq ($(to_COMPILED),base_a)
    include $(my_LOCAL_PATH)/../../base_a/base_a.mk
endif

#編譯base_b庫
ifeq ($(to_COMPILED),base_b)
    include $(my_LOCAL_PATH)/../../base_b/base_b.mk
endif

#編譯base_c庫
ifeq ($(to_COMPILED),base_c)
    include $(my_LOCAL_PATH)/../../base_c/base_c.mk
endif

#編譯final庫
ifeq ($(to_COMPILED),final)
    include $(my_LOCAL_PATH)/../../final/final.mk

    include $(CLEAR_VARS)
    LOCAL_MODULE:=base_a
    LOCAL_SRC_FILES:=$(my_LOCAL_PATH)/../MyLibs/$(TARGET_ARCH_ABI)/libbase_a.a
    include $(PREBUILT_STATIC_LIBRARY)

    LOCAL_PATH := $(my_LOCAL_PATH)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := finalAndroid

    FILE_LIST := $(wildcard $(LOCAL_PATH)/*.cpp)  \
    $(wildcard $(LOCAL_PATH)/../../common/test.cpp)
    LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)
    LOCAL_LDLIBS    := -llog
    LOCAL_STATIC_LIBRARIES := base_a final
    include $(BUILD_SHARED_LIBRARY)
endif

Application.mk文件:

#file: Application.mk
APP_PLATFORM := android-9
APP_STL := stlport_static

#編譯平臺armeabi    arm64-v8a
APP_ABI := armeabi

base_a.mk文件:

#file: base_a.mk
USER_LOCAL_PATH:=$(LOCAL_PATH)
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := base_a
FILE_LIST := $(wildcard $(LOCAL_PATH)/*.cpp) \
             $(wildcard $(LOCAL_PATH)/../common/*.cpp)
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)

LOCAL_LDLIBS    := -llog
LOCAL_PATH := $(USER_LOCAL_PATH)
include $(BUILD_STATIC_LIBRARY)

base_b.mk文件:

#file: base_b.mk
USER_LOCAL_PATH:=$(LOCAL_PATH)
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE	:= base_a
LOCAL_SRC_FILES := $(LOCAL_PATH)/../Android/MyLibs/$(TARGET_ARCH_ABI)/libbase_a.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE    := base_b
FILE_LIST := $(wildcard $(LOCAL_PATH)/*.cpp) \
             $(wildcard $(LOCAL_PATH)/../common/*.cpp) 
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)

LOCAL_LDLIBS    := -llog 
LOCAL_STATIC_LIBRARIES := base_a
LOCAL_PATH := $(USER_LOCAL_PATH)
include $(BUILD_STATIC_LIBRARY)

base_c.mk文件

#file: base_c.mk
USER_LOCAL_PATH:=$(LOCAL_PATH)
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE	:= base_d
LOCAL_SRC_FILES := $(LOCAL_PATH)/../Android/MyLibs/$(TARGET_ARCH_ABI)/libbase_d.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE	:= base_b
LOCAL_SRC_FILES := $(LOCAL_PATH)/../Android/MyLibs/$(TARGET_ARCH_ABI)/libbase_b.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE    := base_c
FILE_LIST := $(wildcard $(LOCAL_PATH)/*.cpp)
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)

LOCAL_LDLIBS    := -llog 
LOCAL_STATIC_LIBRARIES := base_d base_b
LOCAL_PATH := $(USER_LOCAL_PATH)
include $(BUILD_STATIC_LIBRARY)

我在make文件裏面也做了好多註釋。

我現在挑一些關鍵部分解釋下:

1、我在Android.mk文件中設置可以依次生成幾個庫,並且通過一個to_COMPILED變量來保存當前要編譯的庫。然後接下來通過判斷to_COMPILED字段的值,然後執行不同的代碼。  這樣,我每次編譯不同的庫只需要改一處地方。

#設置編譯項  base_a  base_b  base_c  final
to_COMPILED:=base_a
ifeq ($(to_COMPILED),base_b)
    include $(my_LOCAL_PATH)/../../base_b/base_b.mk
endif

2、大家應該注意到了,在每個子make文件我都是類似這樣寫的:

USER_LOCAL_PATH:=$(LOCAL_PATH)
LOCAL_PATH := $(call my-dir)

#中間略去細節

LOCAL_PATH := $(USER_LOCAL_PATH)
include $(BUILD_STATIC_LIBRARY)

這是因爲,每進入一個子make文件,隨着LOCAL_PATH := $(call my-dir)這句話的使用,LOCAL_PATH的值被更新爲當前子make文件的路徑,假如不做處理,當執行完子make文件返回到Android.mk文件中的時候,LOCAL_PATH保存的還是剛剛執行過的子make文件的路徑。  我用的處理方式就是,對於子make文件,用一個變量保存調用者LOCAL_PATH的值,當執行到子make文件的末尾的時候,我再將保存的調用者LOCAL_PATH的值賦給當前LOCAL_PATH,從而程序執行完子make文件回到調用者make文件的時候,LOCAL_PATH的值未發生改變。

3、這裏說一下(哈哈哈),舊版本NDK(反正NDK r8e不可以,NDK10可以)的話Application.mk文件可不敢這麼寫。

4、別的也沒啥了,官方文檔都有。暫時寫這點內容。

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