前言
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 (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 去查找
网友评论