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开发的理想选择。

  1. 从Cygwin官方网站下载安装程序。
  2. 在安装过程中,选择合适的下载源。
  3. 安装必要的开发包:
    • 方法一:选择安装整个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位库的编译。

  1. 从Android开发者网站下载NDK r10c。
  2. 解压NDK到指定目录,无需额外配置。

2.3 环境变量配置

为了便于使用NDK命令,需要配置Cygwin的环境变量:

  1. 启动Cygwin,确保生成必要的配置文件。
  2. 编辑~/.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 技巧解析

  1. 模块化编译
    Android.mk 文件使用 BUILD_TARGET 变量来控制当前编译的模块,允许通过修改单一变量来切换编译目标。

    BUILD_TARGET := module_core
    ifeq ($(BUILD_TARGET),module_core)
        include $(ORIGINAL_LOCAL_PATH)/../../module_core/module_core.mk
    endif
    
  2. LOCAL_PATH 管理
    每个子 Makefile 都保存并恢复 LOCAL_PATH,确保不同模块间的路径正确性。

    PARENT_LOCAL_PATH := $(LOCAL_PATH)
    LOCAL_PATH := $(call my-dir)
    # ... 模块特定代码 ...
    LOCAL_PATH := $(PARENT_LOCAL_PATH)
    
  3. 动态文件包含
    使用 wildcard 函数动态包含源文件,提高了 Makefile 的灵活性。

    CPP_SOURCE_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
    LOCAL_SRC_FILES := $(CPP_SOURCE_FILES:$(LOCAL_PATH)/%=%)
    
  4. 预编译库的使用
    示例中展示了如何包含预编译的静态库。

    include $(CLEAR_VARS)
    LOCAL_MODULE    := module_core
    LOCAL_SRC_FILES := $(LOCAL_PATH)/../Android/MyLibs/$(TARGET_ARCH_ABI)/libmodule_core.a
    include $(PREBUILT_STATIC_LIBRARY)
    
  5. 条件编译
    使用 ifeq 语句实现条件编译,根据不同的编译目标包含不同的模块。

参考资源

  1. Android NDK官方文档
  2. GitHub上的开源NDK项目示例
posted @ 2018-05-08 20:23  purehol  阅读(794)  评论(0编辑  收藏  举报