前一陣子攢了點工程小經驗,忙的有點亂,梳理一下。
隨着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.mk、Application.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、別的也沒啥了,官方文檔都有。暫時寫這點內容。