NDK开发:使用命令行编译Android C/C++动态库
1. 引言
随着Android开发生态系统的演变,从Eclipse到Android Studio的迁移标志着开发工具的重大转变。然而,对于需要编译原生库的开发者来说,使用Android Native Development Kit (NDK)通过命令行进行编译提供了更大的灵活性和控制力。本文将详细介绍如何使用NDK和Cygwin环境在Windows平台上编译Android C/C++动态库。
2. 开发环境搭建
2.1 Cygwin安装与配置
Cygwin提供了类Unix环境,是在Windows上进行NDK开发的理想选择。
- 从Cygwin官方网站下载安装程序。
- 在安装过程中,选择合适的下载源。
- 安装必要的开发包:
- 方法一:选择安装整个Devel类别。
- 方法二:手动选择以下包:
autoconf2.1, automake1.10, binutils, gcc-core, gcc-g++, gcc4-core, gcc4-g++, gdb, pcre, pcre-devel, gawk, make
2.2 NDK下载与安装
NDK版本选择对于编译过程至关重要。本文推荐使用NDK r10c,它支持32位和64位库的编译。
- 从Android开发者网站下载NDK r10c。
- 解压NDK到指定目录,无需额外配置。
2.3 环境变量配置
为了便于使用NDK命令,需要配置Cygwin的环境变量:
- 启动Cygwin,确保生成必要的配置文件。
- 编辑
~/.bash_profile
文件,添加以下行:export NDK10C=/cygdrive/path/to/your/android-ndk-r10c
3. NDK编译流程
3.1 项目结构设置
创建一个包含以下文件的项目目录:
- jni/
- Android.mk
- Application.mk
- 源文件(.cpp)
- 头文件(.h)
3.2 编译命令
在Cygwin中,导航到jni目录并执行:
$NDK10C/ndk-build
4. Makefile编写
4.1 Android.mk
Android.mk 是 NDK 编译系统的核心文件,定义了模块和编译规则。以下是一个优化后的 Android.mk 示例,展示了如何管理多个模块的编译:
# file: Android.mk
LOCAL_PATH := $(call my-dir)
ORIGINAL_LOCAL_PATH := $(LOCAL_PATH)
# 设置编译项 module_core module_a module_b final
BUILD_TARGET := module_core
# 编译 module_core 库
ifeq ($(BUILD_TARGET),module_core)
include $(ORIGINAL_LOCAL_PATH)/../../module_core/module_core.mk
endif
# 编译 module_a 库
ifeq ($(BUILD_TARGET),module_a)
include $(ORIGINAL_LOCAL_PATH)/../../module_a/module_a.mk
endif
# 编译 module_b 库
ifeq ($(BUILD_TARGET),module_b)
include $(ORIGINAL_LOCAL_PATH)/../../module_b/module_b.mk
endif
# 编译 final 库
ifeq ($(BUILD_TARGET),module_main)
include $(ORIGINAL_LOCAL_PATH)/../../module_main/module_main.mk
include $(CLEAR_VARS)
LOCAL_MODULE := module_core
LOCAL_SRC_FILES := $(ORIGINAL_LOCAL_PATH)/../MyLibs/$(TARGET_ARCH_ABI)/libmodule_core.a
include $(PREBUILT_STATIC_LIBRARY)
LOCAL_PATH := $(ORIGINAL_LOCAL_PATH)
include $(CLEAR_VARS)
LOCAL_MODULE := MainAndroid
CPP_SOURCE_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) \
$(wildcard $(LOCAL_PATH)/../../common/test.cpp)
LOCAL_SRC_FILES := $(CPP_SOURCE_FILES:$(LOCAL_PATH)/%=%)
LOCAL_LDLIBS := -llog
LOCAL_STATIC_LIBRARIES := module_core module_main
include $(BUILD_SHARED_LIBRARY)
endif
4.2 Application.mk
Application.mk 定义了应用级的编译设置:
# file: Application.mk
APP_PLATFORM := android-9
APP_STL := stlport_static
# 编译平台 armeabi arm64-v8a
APP_ABI := armeabi
4.3 模块特定的 Makefile
module_core.mk
# file: module_core.mk
PARENT_LOCAL_PATH := $(LOCAL_PATH)
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := module_core
CPP_SOURCE_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) \
$(wildcard $(LOCAL_PATH)/../common/*.cpp)
LOCAL_SRC_FILES := $(CPP_SOURCE_FILES:$(LOCAL_PATH)/%=%)
LOCAL_LDLIBS := -llog
LOCAL_PATH := $(PARENT_LOCAL_PATH)
include $(BUILD_STATIC_LIBRARY)
module_a.mk
# file: module_a.mk
PARENT_LOCAL_PATH := $(LOCAL_PATH)
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := module_core
LOCAL_SRC_FILES := $(LOCAL_PATH)/../Android/MyLibs/$(TARGET_ARCH_ABI)/libmodule_core.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := module_a
CPP_SOURCE_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) \
$(wildcard $(LOCAL_PATH)/../common/*.cpp)
LOCAL_SRC_FILES := $(CPP_SOURCE_FILES:$(LOCAL_PATH)/%=%)
LOCAL_LDLIBS := -llog
LOCAL_STATIC_LIBRARIES := module_core
LOCAL_PATH := $(PARENT_LOCAL_PATH)
include $(BUILD_STATIC_LIBRARY)
module_b.mk
# file: module_b.mk
PARENT_LOCAL_PATH := $(LOCAL_PATH)
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := module_core
LOCAL_SRC_FILES := $(LOCAL_PATH)/../Android/MyLibs/$(TARGET_ARCH_ABI)/libmodule_core.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := module_a
LOCAL_SRC_FILES := $(LOCAL_PATH)/../Android/MyLibs/$(TARGET_ARCH_ABI)/libmodule_a.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := module_b
CPP_SOURCE_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
LOCAL_SRC_FILES := $(CPP_SOURCE_FILES:$(LOCAL_PATH)/%=%)
LOCAL_LDLIBS := -llog
LOCAL_STATIC_LIBRARIES := module_core module_a
LOCAL_PATH := $(PARENT_LOCAL_PATH)
include $(BUILD_STATIC_LIBRARY)
4.4 Makefile 技巧解析
-
模块化编译:
Android.mk 文件使用BUILD_TARGET
变量来控制当前编译的模块,允许通过修改单一变量来切换编译目标。BUILD_TARGET := module_core ifeq ($(BUILD_TARGET),module_core) include $(ORIGINAL_LOCAL_PATH)/../../module_core/module_core.mk endif
-
LOCAL_PATH 管理:
每个子 Makefile 都保存并恢复LOCAL_PATH
,确保不同模块间的路径正确性。PARENT_LOCAL_PATH := $(LOCAL_PATH) LOCAL_PATH := $(call my-dir) # ... 模块特定代码 ... LOCAL_PATH := $(PARENT_LOCAL_PATH)
-
动态文件包含:
使用wildcard
函数动态包含源文件,提高了 Makefile 的灵活性。CPP_SOURCE_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) LOCAL_SRC_FILES := $(CPP_SOURCE_FILES:$(LOCAL_PATH)/%=%)
-
预编译库的使用:
示例中展示了如何包含预编译的静态库。include $(CLEAR_VARS) LOCAL_MODULE := module_core LOCAL_SRC_FILES := $(LOCAL_PATH)/../Android/MyLibs/$(TARGET_ARCH_ABI)/libmodule_core.a include $(PREBUILT_STATIC_LIBRARY)
-
条件编译:
使用ifeq
语句实现条件编译,根据不同的编译目标包含不同的模块。
参考资源
- Android NDK官方文档
- GitHub上的开源NDK项目示例