前言
在我们开发当中,总有那么一些奇葩的需求要用奇葩的骚操作来实现,好了,话不多说,直接进入主题
打开方式
简单来说,当我们需要使用多个架构的SO库编译出多个SO的时候,怎么去配置NDK的编译脚本呢?
我个人比较喜欢使用原始的Android.mk编译,本文也以Android.mk为例,好像CMake更简单一些,不过,我不喜欢
Gradle中配置NDK自动编译
我们使用的是Android.mk文件,那么怎么让Gradle识别并自动编译呢?其实很简单,只需要在项目的gradle.build文件的 android 节点下添加这么一段
externalNativeBuild {
ndkBuild {
path 'src/main/cpp/Android.mk'
}
}
其中的 path 表示你的Android.mk所在的绝对路径,这样一来,就实现了NDK自动编译了
在Android.mk中配置引用多个SO
一般情况,我们在进行NDK开发的时候,目录结构大概是这样的
cpp 目录下包含C/C++的源文件
jniLibs 目录下是引用的对应架构的库文件
当你的项目需要编译引用多个SO来的时候,怎么去配置呢?其实也很简单的,这里以Android中使用FFmpeg为例
-
打开你的Android.mk文件,设置工作目录
正常情况一个合格的Android.mk文件应该是这样子的
## 选择需要编译的目标架构,如果全部架构都需要则把 APP_ABI 的值设置为 all 即可
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := native-lib
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_SRC_FILES := native-lib.c native-lib.h
include $(BUILD_SHARED_LIBRARY)
-
声名SO库的名称及位置
我们在 LOCAL_PATH := $(call my-dir) 后边添加声名,添加完成过后大概是这个样子的
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
LOCAL_PATH := $(call my-dir)
## libavcodec.so
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libavcodec.so
include $(PREBUILT_SHARED_LIBRARY)
## libavdevice.so
include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libavdevice.so
include $(PREBUILT_SHARED_LIBRARY)
## libavfilter.so
include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libavfilter.so
include $(PREBUILT_SHARED_LIBRARY)
## libavformat.so
include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libavformat.so
include $(PREBUILT_SHARED_LIBRARY)
## libavutil.so
include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libavutil.so
include $(PREBUILT_SHARED_LIBRARY)
## libswresample.so
include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libswresample.so
include $(PREBUILT_SHARED_LIBRARY)
## libswscale.so
include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libswscale.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := native-lib
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_SRC_FILES := native-lib.c native-lib.h
include $(BUILD_SHARED_LIBRARY)
这里需要注意的是,声明的SO库后边的 include 应该为 $(PREBUILT_SHARED_LIBRARY)
而你编译的SO库后边的 include 应该为 $(BUILD_SHARED_LIBRARY)
-
引用SO库
最后,在你的编译脚本中加入 LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil swresample swscale 这后边的值需要是你之前声名的 LOCAL_MODULE 的名称,多个引用中间用空格隔开,添加引用过后整个Android.mk大概就是这个样子的
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
LOCAL_PATH := $(call my-dir)
## libavcodec.so
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libavcodec.so
include $(PREBUILT_SHARED_LIBRARY)
## libavdevice.so
include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libavdevice.so
include $(PREBUILT_SHARED_LIBRARY)
## libavfilter.so
include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libavfilter.so
include $(PREBUILT_SHARED_LIBRARY)
## libavformat.so
include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libavformat.so
include $(PREBUILT_SHARED_LIBRARY)
## libavutil.so
include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libavutil.so
include $(PREBUILT_SHARED_LIBRARY)
## libswresample.so
include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libswresample.so
include $(PREBUILT_SHARED_LIBRARY)
## libswscale.so
include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/armeabi-v7a/libswscale.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := native-lib
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil swresample swscale
LOCAL_SRC_FILES := native-lib.c native-lib.h
include $(BUILD_SHARED_LIBRARY)
这样,你就实现了多个SO的引用
多架构SO引用编译
相信你也发现了,上边引用的SO全部是 armeabi-v7a 的,那我们需要引用 arm64-v8a 或者 x86 ,x86_64 的架构时,怎么操作呢?把 armeabi-v7a 换成 x86 或者其他的架构在编译一次?当然不可能,因为这太繁琐了,其实,Android.mk有一些全局变量,而大多数是编译的时候使用的,我们并不知道,那我怎么知道Android.mk当前编译的是那个架构的呢?答案只有一个,那就是 TARGET_ARCH_ABI ,当编译的架构为 armeabi-v7a 的时候,TARGET_ARCH_ABI 的值为 armeabi-v7a ,那么这个时候,我们只需要引用 TARGET_ARCH_ABI 的值替换目录名称就行了,修改后的整个Android.mk是这样的
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
LOCAL_PATH := $(call my-dir)
## libavcodec.so
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/$(TARGET_ARCH_ABI)/libavcodec.so
include $(PREBUILT_SHARED_LIBRARY)
## libavdevice.so
include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/$(TARGET_ARCH_ABI)/libavdevice.so
include $(PREBUILT_SHARED_LIBRARY)
## libavfilter.so
include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/$(TARGET_ARCH_ABI)/libavfilter.so
include $(PREBUILT_SHARED_LIBRARY)
## libavformat.so
include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/$(TARGET_ARCH_ABI)/libavformat.so
include $(PREBUILT_SHARED_LIBRARY)
## libavutil.so
include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/$(TARGET_ARCH_ABI)/libavutil.so
include $(PREBUILT_SHARED_LIBRARY)
## libswresample.so
include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/$(TARGET_ARCH_ABI)/libswresample.so
include $(PREBUILT_SHARED_LIBRARY)
## libswscale.so
include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := $(LOCAL_PATH)/../jniLibs/$(TARGET_ARCH_ABI)/libswscale.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := native-lib
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil swresample swscale
LOCAL_SRC_FILES := native-lib.c native-lib.h
include $(BUILD_SHARED_LIBRARY)
当然,这还不够,Android.mk算是完事了,但是呢?你会发现在编译的时候会有冲突,因为编译器不知道你的SO文件应该放在那里,所以呢,需要我们告诉他,那怎么告诉他呢?其实也很简单,就是在 build.gradle 文件的 android 节点中加入这一段
packagingOptions {
pickFirst 'lib/armeabi-v7a/libavcodec.so'
pickFirst 'lib/armeabi-v7a/libavdevice.so'
pickFirst 'lib/armeabi-v7a/libavfilter.so'
pickFirst 'lib/armeabi-v7a/libavformat.so'
pickFirst 'lib/armeabi-v7a/libavutil.so'
pickFirst 'lib/armeabi-v7a/libswresample.so'
pickFirst 'lib/armeabi-v7a/libswscale.so'
pickFirst 'lib/x86/libavcodec.so'
pickFirst 'lib/x86/libavdevice.so'
pickFirst 'lib/x86/libavfilter.so'
pickFirst 'lib/x86/libavformat.so'
pickFirst 'lib/x86/libavutil.so'
pickFirst 'lib/x86/libswresample.so'
pickFirst 'lib/x86/libswscale.so'
pickFirst 'lib/arm64-v8a/libavcodec.so'
pickFirst 'lib/arm64-v8a/libavdevice.so'
pickFirst 'lib/arm64-v8a/libavfilter.so'
pickFirst 'lib/arm64-v8a/libavformat.so'
pickFirst 'lib/arm64-v8a/libavutil.so'
pickFirst 'lib/arm64-v8a/libswresample.so'
pickFirst 'lib/arm64-v8a/libswscale.so'
pickFirst 'lib/x86_64/libavcodec.so'
pickFirst 'lib/x86_64/libavdevice.so'
pickFirst 'lib/x86_64/libavfilter.so'
pickFirst 'lib/x86_64/libavformat.so'
pickFirst 'lib/x86_64/libavutil.so'
pickFirst 'lib/x86_64/libswresample.so'
pickFirst 'lib/x86_64/libswscale.so'
}
这表示,打包后的APK文件中的lib目录下边有几种架构的SO库,并且每个架构的文件夹里边有多少SO,名称分别为什么
架构打包过滤
如果在打包的时候,不想把 x86,x86_64 这类打包到APK中,这时应该怎么操作呢?当然你可能想的是,不编译就行了,当然这是最简单粗暴的解决办法,那么如何优雅的处理这类问题呢?当然是使用Gradle来进行打包过滤啦,只需要在 build.gradle 文件中的 defaultConfig 节点下添加这么一段话就行了
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
}
abiFilters表示打包时需要打包哪些架构的SO到APK中,如果不需要的架构,直接注释掉或者删掉,如果有其他的架构,自己添加上去即可,方便快捷,优雅不单调,简洁而大方