前言

安卓NDK编译会涉及到:源码编译、制作静态链接库/静态库依赖编译、制作动态链接库/动态链接库依赖编译、嵌套编译,这几个组合类别。

本文将对这些编译搭配类别进行编译演示以快速入门掌握编译方法。Android.mk、Application.mk等全面语法请查阅其他文章。


0x01 NDK工程目录和纯源码可执行文件编译

纯源码:材料仅有源码.c/.cpp源文件和.h头文件。

test
└─jni
    │  Android.mk
    │  Application.mk
    │  test.c
    │
    └─include
            func.h

ndk-build会到jni子目录中寻找Androkd.mk和Application.mk等配置文件,所以源码、头文件之类的为了简便,也以jni目录为相对目录放置。

# Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE        := test
LOCAL_SRC_FILES     := test.c
LOCAL_CONLYFLAGS    := -std=c11
include $(BUILD_EXECUTABLE)

#如你的理解,指定了c11标准进行编译,编译后的模块名为test,直接编译文件为test.c。
# Application.mk
APP_ABI      := armeabi armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM := android-14

# 如你的理解,需要编译 ... 等平台,最低兼容android-14平台
E:\Workspace\NDK\test> ndk-build
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-14.
[arm64-v8a] Compile        : test <= test.c
[arm64-v8a] Executable     : test
[arm64-v8a] Install        : test => libs/arm64-v8a/test
[armeabi-v7a] Compile thumb  : test <= test.c
[armeabi-v7a] Executable     : test
[armeabi-v7a] Install        : test => libs/armeabi-v7a/test
[x86] Compile        : test <= test.c
[x86] Executable     : test
[x86] Install        : test => libs/x86/test
[x86_64] Compile        : test <= test.c
[x86_64] Executable     : test
[x86_64] Install        : test => libs/x86_64/test

看来没啥问题,让我们看看编译后的目录结构。

test
├─jni
│  │  Android.mk
│  │  Application.mk
│  │  test.c
│  │
│  └─include
│          func.h
│
├─libs
│  ├─arm64-v8a
│  │      test
│  │
│  ├─armeabi-v7a
│  │      test
│  │
│  ├─x86
│  │      test
│  │
│  └─x86_64
│          test
│
└─obj
    └─...

将libs目录中对应架构文件推送到手机上即可运行。

0x02 动态链接库编译与依赖动态链接库编译

即当前最火热的so文件。

# Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE            := adloog
LOCAL_SRC_FILES         := adloog.cpp
LOCAL_CFLAGS            := -Wall -Wextra -Werror -g
LOCAL_CONLYFLAGS        := -std=c++11
LOCAL_LDLIBS            := -llog
include $(BUILD_SHARED_LIBRARY)

与0x01编译的可执行文件没有太大区别,执行ndk-build便得到各架构的adloog.so文件。

现在有了新的组合方式,假设0x01的可执行文件依赖于这个动态链接库,如何编译?

# Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE            := adloog
LOCAL_SRC_FILES         := $(LOCAL_PATH)/../libs/$(TARGET_ARCH_ABI)/adloog.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE            := test
LOCAL_SRC_FILES         := test.c
LOCAL_SHARED_LIBRARIES  := adloog
LOCAL_CFLAGS            := -Wall -Wextra -Werror -g
LOCAL_CONLYFLAGS        := -std=c11
LOCAL_LDLIBS            := -llog
include $(BUILD_SHARED_LIBRARY)

使用include $(PREBUILT_SHARED_LIBRARY)对依赖库进行预处理,随后在编译的主模块(即test)上使用LOCAL_SHARED_LIBRARIES选项指定需要链接的动态链接库标识。

0x03 静态链接库编译与依赖静态链接库编译

静态编译将会输出.a文件,用于后续的静态链接。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := adloop
LOCAL_SRC_FILES := adloop1.c adloop2.c
include $(BUILD_STATIC_LIBRARY)

嗯,都是重复的,于是越写越少了。

现在有了新的组合方式,假设0x01的可执行文件依赖于这个静态链接库,如何编译?

LOCAL_PATH := $(call my-dir)
NDK_MODULE_PATH = E:\Workspace\NDK

include $(CLEAR_VARS)
LOCAL_MODULE    := adloop
LOCAL_SRC_FILES := $(LOCAL_PATH)/../libs/$(TARGET_ARCH_ABI)/adloop.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE            := test
LOCAL_SRC_FILES         := test.c
LOCAL_STATIC_LIBRARIES  := adloop
LOCAL_CFLAGS            := -Wall -Wextra -Werror -g
LOCAL_CONLYFLAGS        := -std=c11
LOCAL_LDLIBS            := -llog
include $(BUILD_SHARED_LIBRARY)

0x04 那么问题来了,现在我需要将0x01作为主模块,同时依赖于0x02的动态链接库和0x03的静态链接库,该怎么编译?

Well,这个问题留给聪明的你。

0x05 嵌套编译

别人写好了一个NDK库,具体需要的编译选项他写在自己的Android.mk。如何在我的Android.mk中包含这个库呢?

# 自己的Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE            := adloog
LOCAL_SRC_FILES         := adloog.cpp 
LOCAL_STATIC_LIBRARIES  := adloop
LOCAL_CFLAGS            := -Wall -Wextra -Werror -g
LOCAL_CONLYFLAGS        := -std=c++11
LOCAL_LDLIBS            := -llog
include $(BUILD_SHARED_LIBRARY)

$(call import-add-path, $(LOCAL_PATH)\..\..\adloop
$(call import-module, libhook)

值得一说的是call系列调用要放在Android.mk最后。

# 别人的Android.mk
include $(CLEAR_VARS)
LOCAL_MODULE    := adloop
LOCAL_SRC_FILES := static/$(TARGET_ARCH_ABI)/adloop.a
include $(PREBUILT_STATIC_LIBRARY)

$(call import-module)用于搜寻第三库的Android.mk编译规则。

这里要注意,如果不存在这个规则NDK不会给明显的错误,而是下面的形式:

ndk-build
D:/RTEws/Android/android-ndk-r16b/build//../build/core/setup-app.mk:81: Android NDK: Application targets deprecated ABI(s): armeabi
D:/RTEws/Android/android-ndk-r16b/build//../build/core/setup-app.mk:82: Android NDK: Support for these ABIs will be removed in a future NDK release.
Android NDK: E:/Workspace/NDK/test/jni/Android.mk: Cannot find module with tag 'adloop' in import path
Android NDK: Are you sure your NDK_MODULE_PATH variable is properly defined ?
Android NDK: The following directories were searched:
Android NDK:
E:/Workspace/NDK/test/jni/Android.mk:20: *** Android NDK: Aborting.    .  Stop.

嗯。。无提示错误,最为致命。那么怎么修改?用0x02或0x03不就完事了。

0x06 预编译动态库产生固定依赖的hack方式处理

如下,本意为test模块需要调用libmodaa中的函数,也是官方推荐写法。

include $(CLEAR_VARS)
LOCAL_MODULE            := test
LOCAL_SRC_FILES         := test.cpp
LOCAL_SHARED_LIBRARIES  := libmodaa
LOCAL_CFLAGS            := -Wall -Wextra -Werror -Wunused-variable -Wc++11-extensions -g
LOCAL_CONLYFLAGS        := -std=c++11
LOCAL_LDLIBS            := -llog -L$(LOCAL_PATH) 
LOCAL_LDFLAGS 			:= 
include $(BUILD_SHARED_LIBRARY)

实际编译后的libtest.so会产生固定路径依赖,导致随后System.loadLibrary("test");时出现以下错误

Caused by: java.lang.UnsatisfiedLinkError: dlopen failed: library "./obj/local/armeabi-v7a/libmodaa.so" not found

# 把依赖的so丢到ndk的so依赖搜索目录,随后把LOCAL_SHARED_LIBRARIES直接去掉,在LOCAL_LDLIBS中直接加入模块名
\Android\SDK\ndk\21.3.6528147\toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\lib\arm-linux-androideabi\22

include $(CLEAR_VARS)
LOCAL_MODULE            := test
LOCAL_SRC_FILES         := test.cpp
LOCAL_CFLAGS            := -Wall -Wextra -Werror -Wunused-variable -Wc++11-extensions -g
LOCAL_CONLYFLAGS        := -std=c++11
LOCAL_LDLIBS            := -llog -L$(LOCAL_PATH) -lmodaa
LOCAL_LDFLAGS 			:= 
include $(BUILD_SHARED_LIBRARY)

附赠一个build.gradle写法,ndk指定欲编译架构,jniLibs.srcDirs在打包apk后集成so文件,ndkBuild.path指定Android.mk路径。

apply plugin: 'com.android.application'

android {

    defaultConfig {
        ndk{
            abiFilters "armeabi", "armeabi-v7a"
        }

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

    externalNativeBuild{
        ndkBuild{
            path   "src/main/jni/Android.mk"
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}