前言
安卓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'
}