NDK编译静态/动态库

大城市里の小女人 提交于 2020-08-13 13:43:23

 前一阵子攒了点工程小经验,忙的有点乱,梳理一下。

随着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、别的也没啥了,官方文档都有。暂时写这点内容。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!