C/C++语言基础 –> JNI编程 –> NDK编程 –> 音视频处理 –> 图像处理等等 ,从零到一 , 零基础学习NDK开发 。学习本专题 , 你需要具备一定的Android开发知识 C/C++基础, 知道如何创建一个Android工程 , 有一定的开发经验更佳 。

前言

Android.mk 的文件配置详解,能够读懂Android.mk;为何Google推荐使用cmake,而不在使用Android.mk?Android.mk存在哪些缺陷?

如何生成静态库与动态库(在上几篇文章中讲过)?

Android.mk 如何配置动态库(.so文件)、配置静态库(.a文件)?

静态库与动态库的区别?

带着这些问题,思考,往下看。

环境:

AS 3.1.0 版本

NDK 16 版本

命令行: Mac 自带的命令行

通过命令来生成动态库和静态库

这里用mac的终端来演示,通过vim 命令来生成一个.c文件,这个就不必细说了吧。在NDK 开发必知必会1⃣️CC++编译器配置,中有详细的讲解

输入: vim a.c

image.png

然后配置一个临时的变量:

export CC="/Users/prim/Library/Android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-gcc --sysroot=/Users/prim/Library/Android/sdk/ndk-bundle/platforms/android-21/arch-arm -isystem /Users/prim/Library/Android/sdk/ndk-bundle/sysroot/usr/include -isystem /Users/prim/Library/Android/sdk/ndk-bundle/sysroot/usr/include/arm-linux-androideabi"

输入 :echo $CC 验证变量是否配置成功

输入 :$CC -fPIC -shared a.c -o libA.so 生成libA.so文件,这里便生成了一个动态库

输入:$CC -fPIC -c a.c -o a.o 生成a.o 文件,用于生成静态库

输入 : /Users/prim/Library/Android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-ar r libA.a a.o

会生成libA.a 静态库文件。

将得到的libA.so libA.a copy到项目的cpp 文件夹下(cpp可参照如何配置NDK开发环境)。

配置依赖动态库

NDK 环境的配置,这里就不再细说了,配置完成如下图:
image.png

gradle 配置

image.png

下面是重点,Android.mk文件如何配置动态库

LOCAL_PATH := $(call my-dir)

# 预编译库引入(提前编译好的库)
include $(CLEAR_VARS)
LOCAL_MODULE := A
LOCAL_SRC_FILES := libA.so
# 构建动态库
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)
LOCAL_MODULE := holle-jni
LOCAL_SRC_FILES := holle-jni.c
# 编译holle-jni模块 需要连接A模块
# A模块是一个预编译库模块 动态库
LOCAL_SHARED_LIBRARIES := A
# 动态库配置
include $(BUILD_SHARED_LIBRARY)

LOCAL_PATH := $(call my-dir) 表示:源文件在的位置。宏函数 my-dir 返回当前目录(包含 Android.mk 文件本身的目录)的路径。

include $(CLEAR_VARS):引入其他makefile文件。CLEAR_VARS 变量指向特殊 GNU Makefile,可为您清除许多 LOCAL_XXX 变量
不会清理 LOCAL_PATH 变量

LOCAL_MODULE := hello-jni:存储您要构建的模块的名称 每个模块名称必须唯一,且不含任何空格

#如果模块名称的开头已是 lib,则构建系统不会附加额外的前缀 lib;而是按原样采用模块名称,并添加 .so 扩展名。

LOCAL_SRC_FILES := hello-jni.c:包含要构建到模块中的 C 和/或 C++ 源文件列表 以空格分开

构建动态库 include $(BUILD_SHARED_LIBRARY)

这样便配置好了动态库,build一下看cpp文件有没有变亮,如果变亮则动态库配置成功。

会生成两个so包libholle-jni.so libA.so

image.png

如何使用libA.so中的函数呢?

编辑 holle-jni.c

#include <jni.h>
//libA.so 中的方法
extern int test1();



JNIEXPORT void JNICALL
Java_ndk_config_com_configndk_MainActivity_useLibAMoudle(JNIEnv *env, jobject instance) {

    // TODO
    test1();
}

Activity 中代码

static {
        System.loadLibrary("holle-jni");
    }

这样就可以调用动态库中的函数了。

Android.mk 配置动态库的缺陷

在4.4上 如果load一个动态库 ,需要先将这个动态库的依赖的其他动态库load进来
比如:在Android 4.4 先要load holle-jni 链接的动态库,要在load holle-jni 之前load进来

System.loadLibrary("A");
 System.loadLibrary("holle-jni");

从6.0开始 使用Android.mk 如果来引入一个预编译动态库 有问题
在6.0以下 System.loadLibrary 不会自动为我们加载依赖的动态库
6.0以上 System.loadLibrary 会自动为我们加载依赖的动态库

那么在6.0 以上就不能使用,如下方法loadlibary,否则项目会报错:

System.loadLibrary("A");
 System.loadLibrary("holle-jni");

改成,因为6.0以上会自动加载依赖的动态库。

 System.loadLibrary("holle-jni");

这里并没有太好的解决方法,或许是因为这个原因,Google才推荐使用cmake的方式,现在的ndk对mk的支持已经接近放弃阶段了。

配置依赖静态库

LOCAL_PATH := $(call my-dir)

# 预编译库引入(提前编译好的库)
include $(CLEAR_VARS)
LOCAL_MODULE := A
LOCAL_SRC_FILES := libA.a
# 构建静态库
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := holle-jni
LOCAL_SRC_FILES := holle-jni.c
# 构建静态库
LOCAL_STATIC_LIBRARIES := A

# 动态库配置
include $(BUILD_SHARED_LIBRARY)

调用静态库的函数和调用动态库的函数方式一致。

同时Activity只需要,便可以,静态库不需要动态的加载依赖,在打包时已经将静态库打进去了。

 System.loadLibrary("holle-jni");

使用静态库会生成一个so文件,很显然apk会小很多

image.png

静态库与动态库的区别

静态库节省时间:不需要再进行动态链接,需要调用的代码直接就在代码内部

动态库节省空间:如果一个动态库被两个程序调用,那么这个动态库只需要在内存中

Java中在不经过封装的情况下只能直接使用动态库。

我们可以这样比喻两个库的区别:
jar包 =》 a.java b.java c.java

app: app.java(a.java),app.java 调用jar包的 a.java
假设jar包是静态库,那么打包的apk 只包含 app.java + a.java 而没有将 b.java c. java 打进apk包中。
app.apk –> app.java+a.java

假设jar包是动态库,直接将整个jar包打入到apk包中。
app.apk: app.java+ jar包(a.java b.java c.java)

显然,如果我们使用静态库(.a)打的apk包要比动态库(.so)打的apk包小很多。

MakeFile 配置详解

变量和宏

定义自己的任意变量。在定义变量时请注意,NDK 构建系统会预留以下变量名称:

以 LOCAL_ 开头的名称,例如 LOCAL_MODULE。
以 PRIVATE_、NDK_ 或 APP 开头的名称。构建系统在内部使用这些变量。
小写名称,例如 my-dir。构建系统也是在内部使用这些变量。
如果为了方便而需要在 Android.mk 文件中定义自己的变量,建议在名称前附加 MY_。

常用内置变量

变量名 含义 示例
BUILD_STATIC_LIBRARY 构建静态库的Makefile脚本 include $(BUILD_STATIC_LIBRARY)
PREBUILT_SHARED_LIBRARY 预编译共享库的Makeifle脚本 include $(PREBUILT_SHARED_LIBRARY)
PREBUILT_STATIC_LIBRARY 预编译静态库的Makeifle脚本 include $(PREBUILT_STATIC_LIBRARY)
TARGET_PLATFORM Android API 级别号 TARGET_PLATFORM := android-22
TARGET_ARCH CUP架构 arm arm64 x86 x86_64
TARGET_ARCH_ABI CPU架构 armeabi armeabi-v7a arm64-v8a

模块描述变量

变量名 描述
LOCAL_MODULE_FILENAME 覆盖构建系统默认用于其生成的文件的名称 LOCAL_MODULE := foo LOCAL_MODULE_FILENAME := libnewfoo
LOCAL_CPP_FEATURES 特定 C++ 功能 支持异常:LOCAL_CPP_FEATURES := exceptions
LOCAL_C_INCLUDES 头文件目录查找路径 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_CFLAGS 构建 C 和 C++ 的编译参数 LOCAL_CPPFLAGS c++
LOCAL_STATIC_LIBRARIES 当前模块依赖的静态库模块列表 LOCAL_SHARED_LIBRARIES
LOCAL_WHOLE_STATIC_LIBRARIES –whole-archive 将未使用的函数符号也加入编译进入这个模块
LOCAL_LDLIBS 依赖 系统库 LOCAL_LDLIBS := -lz

导出给引入模块的模块使用:

LOCAL_EXPORT_CFLAGS

LOCAL_EXPORT_CPPFLAGS

LOCAL_EXPORT_C_INCLUDES

LOCAL_EXPORT_LDLIBS

引入其他模块

#将一个新的路径加入NDK_MODULE_PATH变量
#NDK_MODULE_PATH 变量是系统环境变量
$(call import-add-path,$(LOCAL_PATH)/platform/third_party/android/prebuilt)
#包含CocosDenshion/android目录下的mk文件
$(call import-module,CocosDenshion/android)

#这里即为 我需要引入 CocosDenshion/android 下面的Android.mk
#CocosDenshion/android 的路径会从 $(LOCAL_PATH)/platform/third_party/android/prebuilt 去查找