Android.mk认识:
在上一次【https://www.cnblogs.com/webor2006/p/9946061.html】中学会了用NDK提供的交叉编译工程编译成Android能运行的可执行文件,下面咱们来做个实验来看一下使用静态库与动态库的区别,还是用上一次用的源文件为例:
动态库的具体的生成过程可以参考上一次写的博文,接下来再生成一个静态库,如何生成呢?
所以咱们先来找到NDK提供的ar交叉编译工具:
所以咱们使用它依照生成规则来生成对应的静态库看一下:
接下来咱们新建一个Android工程,来使用编译出来的动静态库,这里还是建立一个纯的非NDK的Android工程,来进一步操练下如何给一个普通Android工程来集成NDK,如下:
然后咱们来集成ndk环境,回顾上一次的步骤,得修改app下的build.gradle配置了:
这个是针对源文件的,而我们想要用Android.mk来进行构建就需要在外层来指定了,如下:
其实上一次已经使用过了,里面Android.mk的编写规则也记不住,直接拷贝一下,重点不是记着,还是你能读懂就成,如下:
然后咱们在对应位置建立一个native-lib.c的源文件:
那咱们编译一个apk,然后可以看到apk中已经有一个.so文件了,如下:
那如果想使用我们刚编译写的libTest.so文件应该怎么使用呢?这里就涉及到多模块的引入问题,先将我们编译好的libTest.so拷入到工程中:
然后在Android.mk中来声明一个新的模块,其写法跟声明.c源文件类似,如下:
然后咱们再来编译一个apk,看一下里面的.so的情况,是不是变为2个.so了?
那是因为咱们新声明的预编译模块还没有使用到,所以需要关联一下,如下:
那此时咱们来在自己的.c文件中来使用一下预编译库定义的函数,如下:
当然在运行之前还得加载一个我们编译的库:
然后编译生成一个apk,看此时它里面包含动态库的情况:
记住动态库的这个现象哈~~之后会用静态库做一个对比!!!!
然后下面分别在Android4.3系统和Android8.1.0的系统上运行,为啥要在两个版本上运行,因为肯定是有坑嘛~~,所以下面先来看在Android4.3上运行,用的手机型号为:
崩溃了,看一下崩溃日志:
11-27 09:12:04.756 6028-6028/com.jni.test E/AndroidRuntime: FATAL EXCEPTION: main java.lang.UnsatisfiedLinkError: dlopen failed: could not load library "/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/libTest.so" needed by "libnative-lib.so"; caused by library "/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/libTest.so" not found at java.lang.Runtime.loadLibrary(Runtime.java:362) at java.lang.System.loadLibrary(System.java:525) at com.jni.test.MainActivity.<clinit>(MainActivity.java:9) at java.lang.Class.newInstanceImpl(Native Method) at java.lang.Class.newInstance(Class.java:1130) at android.app.Instrumentation.newActivity(Instrumentation.java:1078) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2223) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2362) at android.app.ActivityThread.access$700(ActivityThread.java:168) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1329) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:177) at android.app.ActivityThread.main(ActivityThread.java:5493) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:525) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1225) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1041) at dalvik.system.NativeStart.main(Native Method)
找不到libTest.so,这是因为在Android6.0以下版本,System.loadLibrary()不会自动为我们加载依赖的动态库,如果load一个动态库,则需要先将这个动态库的依赖的其他动态库也要load进来,所以:
那此时同样的代码在Android8.1.0的手机上来运行,手机型号是:
又崩溃了,崩溃日志:
--------- beginning of crash 11-27 09:26:34.879 15598-15598/? E/AndroidRuntime: FATAL EXCEPTION: main Process: com.jni.test, PID: 15598 java.lang.UnsatisfiedLinkError: dlopen failed: library "/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/libTest.so" not found at java.lang.Runtime.loadLibrary0(Runtime.java:1016) at java.lang.System.loadLibrary(System.java:1660) at com.jni.test.MainActivity.<clinit>(MainActivity.java:10) at java.lang.Class.newInstance(Native Method) at android.app.Instrumentation.newActivity(Instrumentation.java:1179) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3054) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3278) at android.app.ActivityThread.-wrap12(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1894) at android.os.Handler.dispatchMessage(Handler.java:109) at android.os.Looper.loop(Looper.java:166) at android.app.ActivityThread.main(ActivityThread.java:7377) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:469) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963)
标红处的地址很显示是我的mac电脑上的路径了,在手机上肯定找不到嘛,那为啥在8.1.0的手机上运行会出现从这个路径去加载预编译的动态库呢?其实这就是版本兼容的问题了,在NDK中提供了一个可以看到.so文件的依赖情况,如下:
那咱们来查看一下我们的libnative-lib.so它里面的依赖情况,先定位到这个生成的.so文件的路径处:
然后使用这个ndk的依赖查看命令:
也正好是崩溃日志中所看到的路径,其实是从Android6.0开始,使用Android.mk如果来引入一个预编译动态库是有问题的【这个需要特别的注意!】,其原因也就是“Android6.0以下版本,System.loadLibrary()不会自动为我们加载依赖的动态库,而6.0及以上的版本是会自动为我们加载依赖的动态库”,也就是说在Android6.0以上版本不用我们显示的去写加载的预编译库了,也就是这句话在Android6.0上可以省略掉了:
那使用预编译的动态库在Android6.0以上系统有问题,那如果是改用静态库会不会有问题呢?咱们试试,将之前我们编译好的静态库也放到工程当中:
此时需要修改.mk文件了,如下:
其中关于MK中的这些变量的使用可以参考:
常用内置变量
变量名 | 含义 | 示例 |
---|---|---|
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
编译下编译生成一个apk:
也就是说会将我们程序用到的libTest.a静态库中的那段代码抽离打包到我们自己的so当中,而动态库是整个会打包进apk,跟我们自己的so是独立的,然后在运行时进行依赖调用,也就是说其实使用静态做为预编译库来使用的话打出来的.so会小很多,因为只有我们使用到的才会打包,而动态库是整个都会打包,不管使用了还是木有使用。举个形象的例子来理解静态库与动态库打包的区别:
假设有个jar里面包含a.java、b.java、c.java,然后咱们app有一个app.java,它使用了jar包中的a.java,如果使用了静态库打包,则最终apk中只包含app.java和a.java;而如果使用了动态库打包,则最终APK中包含app.java和整个jar(a.java、b.java、c.java),很显示在开发中一般使用静态库较好,但是有个问题:为啥三方SDK一般都是提供.so文件呢?其实根本原因还是之前讲的:它只能加载动态库,不加载静态库:
只是说如果是我们自己编译的库可以使用静态库,因为我们有NDK的编译环境嘛,如果三方给一个.a静态库,那我们在工程中还是配置NDK编译环境来将它进行一个封装,最终编成我们的.so再来使用,太麻烦了。
此时再运行,在不同版本上都可以兼容了。总之需要记住在mk中引入预编译的动态库是有版本性的差异滴,要解决的话就得用Cmake来代替.mk了,像如今的项目基本都是采用Cmake来进行NDK的编译了,那学习.mk有啥意义呢?因为好多老工程的编译还是基于.mk的,我们需要能看懂它,所以基于这个学习目的,下面来看一个cocos2d的一个hello world级别的工程,它里面的工程编译就是通过mk的形式,我们来试着读一下它的mk,看能否读懂,能读懂的话那目的就达到了:
首先当然先导入这个cocos2d的工程啦,具体工程的代码可以上网上搜搜,这里导它的目的只是为了去了解它的mk文件的编写规则,并非研究怎么用它来开发游戏,如下:
然后工程运行的界面如下:
咱们重点关心的就是它的mk文件的编写内容啦,所在的位置如下:
首先来看一下“Android.mk”完整配置:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) #模块名 如果没有 LOCAL_MODULE_FILENAME 配置 就会生成一个 #libMyGame_shared.so LOCAL_MODULE := MyGame_shared #生成一个 libMyGame.so (可以不写) LOCAL_MODULE_FILENAME := libMyGame #源文件 LOCAL_SRC_FILES := $(LOCAL_PATH)/hellocpp/main.cpp $(LOCAL_PATH)/../../../Classes/AppDelegate.cpp $(LOCAL_PATH)/../../../Classes/HelloWorldScene.cpp # 编译时查找头文件的路径 相当于:-I LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../Classes # _COCOS_HEADER_ANDROID_BEGIN # _COCOS_HEADER_ANDROID_END # 引入一个静态库作为当前编译源文件的依赖(jar) # 定义在其他的mk文件里面 LOCAL_STATIC_LIBRARIES := cocos2dx_static # _COCOS_LIB_ANDROID_BEGIN # _COCOS_LIB_ANDROID_END include $(BUILD_SHARED_LIBRARY) # 单独声明是没有任何意义的 需要结合 import-module 来看 # 设置引入其他mk的查找路径 $(call import-add-path,$(LOCAL_PATH)/../../../cocos2d) $(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/external) $(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/cocos) $(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/cocos/audio/include) #引入其他路径下的Android.mk文件 # 相当于 #include $(call import-module, cocos) # _COCOS_LIB_IMPORT_ANDROID_BEGIN # _COCOS_LIB_IMPORT_ANDROID_END
接下来就是尝试来读懂这里面的规则,第一句不用多说,每个Android.mk文件的开头的固定写法:
可以瞅一下咱们生成的apk中的.so是否是我们指定的这个:
嗯,确实是,继续往下看:
对就是对应于这些源文件:
关于-I参数的含义可以回顾一下:
这个其实咱们在之前的DEMO中已经用到过了,如下:
所以我们查找一下"cocos2dx_static"这个模块的定义,发现木有在当前的Android.mk中定义,而是在其它mk中,这就涉及到如何引入其它mk的module啦,具体使用如下:
比较抽像对吧,来回到cocos2d这块的配置就能理解滴:
对应于工程的这块:
然后咱们打开它,看一下"cocos2dx_static"这个模块是否定义在这个.mk里面:
至此对于第一个Android.mk就分析完了,貌似也都能看懂嘛,接下来还有另外一种mk,也就是下面要学习滴。
Application.mk:
同样是GNU Makefile 片段,在Application.mk中定义一些全局(整个项目)的配置
先来瞅一下工程中的这个mk:
先来看一下它里面都配了啥:
# 指定运行时库 (libc ) APP_STL := c++_static #会交给编译器的参数 APP_CPPFLAGS := -frtti -DCC_ENABLE_CHIPMUNK_INTEGRATION=1 -std=c++11 -fsigned-char -Wno-extern-c-compat # 交给链接器的参数 (so依赖另一个so,这就需要链接 ) APP_LDFLAGS := -latomic # 要生成的cpu架构 APP_ABI := armeabi-v7a #解决windows命令行不支持太长的字符输入的问题 APP_SHORT_COMMANDS := true ifeq ($(NDK_DEBUG),1) APP_CPPFLAGS += -DCOCOS2D_DEBUG=1 APP_OPTIM := debug else APP_CPPFLAGS += -DNDEBUG APP_OPTIM := release endif
接下来一个个了解一下:
也就是我们在写cpp里的一些系统函数,如std::cout之类的,具体这块的配置参数如下:
而它的定义如下:
由于这个工程的源代码是基于cpp的,所以用上面这个标志,如果是c,则需要用下面这个:
【注意】:由于NDK对于mk已经接近放弃的阶段,所以对于CPU的指定建议还是要build.gradle中去指定,如下:
而它的具体配置如下:
需要生成的cpu架构(ndk r17 只支持:armeabi-v7a, arm64-v8a, x86, x86_64)
指令集 | 值 |
---|---|
基于 ARMv7 的设备上的硬件 FPU 指令 | APP_ABI := armeabi-v7a |
ARMv8 AArch64 | APP_ABI := arm64-v8a |
IA-32 | APP_ABI := x86 |
Intel64 | APP_ABI := x86_64 |
MIPS32 | APP_ABI := mips |
MIPS64 (r6) | APP_ABI := mips64 |
所有支持的指令集 | APP_ABI := all |
不同 Android 手机使用不同的 CPU,因此支持不同的指令集。
armeabi-v7a
armeabi-v7a
ABI 使用 -mfloat-abi=softfp
开关强制实施规则,要求编译器在函数调用时必须传递核心寄存器对中的所有双精度值,而不是专用浮点值。 系统可以使用 FP 寄存器执行所有内部计算。 这样可极大地加速计算。
如果要以 armeabi-v7a ABI 为目标,则必须设置下列标志:
CFLAGS= -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16
arm64-v8a
此 ABI 适用于基于 ARMv8、支持 AArch64 的 CPU。它还包含 NEON 和 VFPv4 指令集。
x86
此 ABI 适用于支持通常称为“x86”或“IA-32”的指令集的 CPU。设置的标志如:
-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32
x86_64
-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel
现在手机主要是armeabi-v7a。查看手机cpu:
adb shell cat /proc/cpuinfo
adb shell getprop ro.product.cpu.abi
如查看一下我模拟器的cpu情况:
xiongweideMacBook-Pro:LableCoffee xiongwei$ adb shell cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 69 model name : Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz stepping : 1 cpu MHz : 2600.058 cache size : 3072 KB physical id : 0 siblings : 4 core id : 0 cpu cores : 4 apicid : 0 initial apicid : 0 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand lahf_lm abm bugs : bogomips : 5200.11 clflush size : 64 cache_alignment : 64 address sizes : 39 bits physical, 48 bits virtual power management: processor : 1 vendor_id : GenuineIntel cpu family : 6 model : 69 model name : Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz stepping : 1 cpu MHz : 2600.058 cache size : 3072 KB physical id : 0 siblings : 4 core id : 1 cpu cores : 4 apicid : 1 initial apicid : 1 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand lahf_lm abm bugs : bogomips : 5200.11 clflush size : 64 cache_alignment : 64 address sizes : 39 bits physical, 48 bits virtual power management: processor : 2 vendor_id : GenuineIntel cpu family : 6 model : 69 model name : Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz stepping : 1 cpu MHz : 2600.058 cache size : 3072 KB physical id : 0 siblings : 4 core id : 2 cpu cores : 4 apicid : 2 initial apicid : 2 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand lahf_lm abm bugs : bogomips : 5200.11 clflush size : 64 cache_alignment : 64 address sizes : 39 bits physical, 48 bits virtual power management: processor : 3 vendor_id : GenuineIntel cpu family : 6 model : 69 model name : Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz stepping : 1 cpu MHz : 2600.058 cache size : 3072 KB physical id : 0 siblings : 4 core id : 3 cpu cores : 4 apicid : 3 initial apicid : 3 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand lahf_lm abm bugs : bogomips : 5200.11 clflush size : 64 cache_alignment : 64 address sizes : 39 bits physical, 48 bits virtual power management: xiongweideMacBook-Pro:LableCoffee xiongwei$ adb shell getprop ro.product.cpu.abi x86
apk在安装的时候,如果手机是armeabi-v7a的,则会首先查看apk中是否存在armeabi-v7a目录,如果没有就会查找armeabi。
保证cpu目录下so数量一致。
如果目标是armeabi-v7a,但是拥有一个armeabi的,也可以把它放到armeabi-v7a目录下。但是反过来不行
ABI(横 so)/CPU(竖 手机) | armeabi | armeabi-v7a | arm64-v8a | x86 | x86_64 |
---|---|---|---|---|---|
ARMV5 | 支持 | ||||
ARMV7 | 支持 | 支持 | |||
ARMV8 | 支持 | 支持 | 支持 | ||
X86 | 支持 | ||||
X86_64 | 支持 | 支持 |
继续还是回到工程中的.mk往下瞅:
其中APP_OPTIM的具体含义如下:
以上就是关于mk的东东,配置比较复杂,没必要去记,因为真实项目使用可能都会转到下面要学习的cmake了,重点是要能读取mk,然后可以很容易的将mk转成cmake就可以啦~~
Cmake配置:
在android studio 2.2及以上,构建原生库的默认工具是 CMake。
CMake是一个跨平台的构建工具,可以用简单的语句来描述所有平台的安装(编译过程)。能够输出各种各样的makefile或者project文件。Cmake 并不直接建构出最终的软件,而是产生其他工具的脚本(如Makefile ),然后再依这个工具的构建方式使用。
CMake是一个比make更高级的编译配置工具,它可以根据不同平台、不同的编译器,生成相应的Makefile或者vcproj项目。从而达到跨平台的目的。Android Studio利用CMake生成的是ninja,ninja是一个小型的关注速度的构建系统。我们不需要关心ninja的脚本,知道怎么配置cmake就可以了。从而可以看出cmake其实是一个跨平台的支持产出各种不同的构建脚本的一个工具。
CMake的脚本名默认是CMakeLists.txt
其中标红处提到了一个新名词“ninja”,其实它存在于我们的SDK当中,如下:
当然我们不用关心ninja是如何构建的啦,只需了解CMake如何做就成了,这里做一个了解。
下面咱们来基于之前学习mk的工程来改用cmake,工程回忆一下:
首先得修改gradle的ndk编译脚本的路径,如下:
然后在cmake里面也用之前的native-lib.c这个源文件:
那接下来就是来在CMakeLists.txt中来编写构造脚本啦,那,如何写呢。。下面来学习下,首先得指定一下最低版本,如下:
注意一个小细节:
接下来指令要编译的源文件及编译出来是要静态库或动态库,如下:
此时编译一下:
再次编译:
这里通过建立一个带NDK环境的工程的这块的配置一对比,发现它的ndk配置是这样写的:
那咱们依葫芦画瓢一下:
再編译一下:
Build command failed. Error while executing process /Users/xiongwei/android-sdks/Android/sdk/cmake/3.6.3155560/bin/cmake with arguments {--build /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/.externalNativeBuild/cmake/debug/mips64 --target native-lib} [1/1] Linking C shared library /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/cmake/debug/obj/mips64/libnative-lib.so FAILED: : && /Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=mips64el-none-linux-android --gcc-toolchain=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/mips64el-linux-android-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/platforms/android-21/arch-mips64 -fPIC -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -Wa,--noexecstack -Wformat -Werror=format-security -O0 -fno-limit-debug-info -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libnative-lib.so -o /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/cmake/debug/obj/mips64/libnative-lib.so CMakeFiles/native-lib.dir/native-lib.c.o -lm && : CMakeFiles/native-lib.dir/native-lib.c.o: In function `Java_com_jni_test_MainActivity_nativeTest': /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/src/main/cmake/native-lib.c:7: undefined reference to `test' clang: error: linker command failed with exit code 1 (use -v to see invocation) ninja: build stopped: subcommand failed.
很显然在咱们的源代码中引用了动态库或静态库中的函数,但是我们在CMakeLists.txt中并未指定动态库或静态库的配置,所以接下来咱们先以引入静态库为例:
那从哪里来导入呢,接下来需要设置一下静态库的路径:
由于libTest.a跟我们的CMakeLists.txt不在同一个目录,所以路径需要注意一下,最后一步需要进行链接,也就是将这两个模块需要关联起来:
如何写呢?如下:
好,一切设置完毕,咱们试着来编译一下看是否正常了:
Build command failed. Error while executing process /Users/xiongwei/android-sdks/Android/sdk/cmake/3.6.3155560/bin/cmake with arguments {--build /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/.externalNativeBuild/cmake/debug/mips64 --target native-lib} ninja: error: '../../cpp/libTest.a', needed by '/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/build/intermediates/cmake/debug/obj/mips64/libnative-lib.so', missing and no known rule to make it
貌似是静态库的路径这样设置不行,怎么办呢?其实可以尝试将CMakeLists.txt文件挪一下位置,咱们先来看一下目录结构:
挪了位置之后,当然build.gradle的脚本配置那块也得变下了,如下:
然后在CMakeLists.txt路径那可以修改为:
然后再编译一下:
Build command failed. Error while executing process /Users/xiongwei/android-sdks/Android/sdk/cmake/3.6.3155560/bin/cmake with arguments {--build /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/.externalNativeBuild/cmake/debug/mips64 --target native-lib} [1/1] Linking C shared library ../../../../build/intermediates/cmake/debug/obj/mips64/libnative-lib.so FAILED: : && /Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=mips64el-none-linux-android --gcc-toolchain=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/mips64el-linux-android-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/platforms/android-21/arch-mips64 -fPIC -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -Wa,--noexecstack -Wformat -Werror=format-security -O0 -fno-limit-debug-info -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libnative-lib.so -o ../../../../build/intermediates/cmake/debug/obj/mips64/libnative-lib.so CMakeFiles/native-lib.dir/src/main/cmake/native-lib.c.o ../../../../src/main/cpp/libTest.a -lm && : /Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/mips64el-linux-android-4.9/prebuilt/darwin-x86_64/lib/gcc/mips64el-linux-android/4.9.x/../../../../mips64el-linux-android/bin/ld: unknown architecture of input file `../../../../src/main/cpp/libTest.a(test.o)' is incompatible with mips:isa64r6 output clang: error: linker command failed with exit code 1 (use -v to see invocation) ninja: build stopped: subcommand failed.
又失败了,有点崩溃。。貌似从标红处看是不支持相应的架构,此时想起来在build.gradle中打包APK时需要指定CPU类型,所以可以尝试加一下:
再编译,终于成功了:
其中需要注意这个:
其实还有一个变量用得比较多,如下:
编译看一下它的输出:
那这个值是从哪获取的呢?其实就是我们在build.gradle中配置的,如下:
如果咱们再加一个CPU架构,再看一下输出效果:
上面是链接的静态库的配置,下面来使用动态库来链接,那又如何配呢?这里需要注意:需要将我们的动态.so库放到jniLibs里面,否则不会打包进apk,具体如下:
然后修改CMakeLists.txt配置:
此时,咱们在Android4.3机器上运行一下:
11-30 09:42:22.583 5731-5731/? E/AndroidRuntime: FATAL EXCEPTION: main java.lang.UnsatisfiedLinkError: dlopen failed: could not load library "../../../../src/main/jniLibs/armeabi-v7a/libTest.so" needed by "libnative-lib.so"; caused by library "../../../../src/main/jniLibs/armeabi-v7a/libTest.so" not found at java.lang.Runtime.loadLibrary(Runtime.java:362) at java.lang.System.loadLibrary(System.java:525) at com.jni.test.MainActivity.<clinit>(MainActivity.java:9) at java.lang.Class.newInstanceImpl(Native Method) at java.lang.Class.newInstance(Class.java:1130) at android.app.Instrumentation.newActivity(Instrumentation.java:1078) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2223) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2362) at android.app.ActivityThread.access$700(ActivityThread.java:168) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1329) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:177) at android.app.ActivityThread.main(ActivityThread.java:5493) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:525) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1225) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1041) at dalvik.system.NativeStart.main(Native Method)
其实跟用.mk引入预编译动态库一样,6.0之前的系统是不会自动加入依赖库的,所以咱们需要手动加载一下依赖库,如下:
此时再运行就正常了,然后apk中就有了两个so文件,如下:
那如果改运行到Android6.0以上的机型,运行:
但是和mk不同的是,CMake有其它办法引入动态库让它运行在Android6.0以上也没问题,如何整:
试一下:
再运行,在所有版本都可运行了。
关于.mk和cmake引入预编译动态库在Android5.0及以下与6.0及以上的注意事项这里总结一下,以勉踩坑:
比如存在两个动态库libhello-jni.so
与 libTest.so
。libhello-jni.so
依赖于libTest.so
(使用NDK下的ndk-depends
可查看依赖关系),则:
其中对于Android.mk来说: 使用Android.mk在 >=6.0 设备上不能再使用预编译动态库(静态库没问题):
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := Test #libTest.so放在当前文件同目录 LOCAL_SRC_FILES := libTest.so #预编译库 include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) #引入上面的Test模块 LOCAL_SHARED_LIBRARIES := Test LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c include $(BUILD_SHARED_LIBRARY)
上面这段配置生成的libhllo-jni
在>=6.0设备中无法执行。
而对于CMake来说:使用CMakeList.txt在 >=6.0 设备上引入预编译动态库配置如下:
cmake_minimum_required(VERSION 3.4.1) file(GLOB SOURCE *.c ) add_library( hello-jni SHARED ${SOURCE} ) #这段配置在6.0依然没问题 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -L[SO所在目录]") #这段配置只能在6.0以下使用 原因和android.mk一样 #add_library(Test SHARED IMPORTED) #set_target_properties(Test PROPERTIES IMPORTED_LOCATION [SO绝对地址]) target_link_libraries( hello-jni Test )
好,接下来看另外一个CMake的东东:如果说我们的代码中要使用Android的Log库,之前咱们也说过,它的实现是处于NDK的这块位置:
咱们来看一下在CMake中来如何指定这个动态库,先在源代码中增加一个LOG的代码:
目前没有配置的话编译肯定会抛异常,如下:
Build command failed. Error while executing process /Users/xiongwei/android-sdks/Android/sdk/cmake/3.6.3155560/bin/cmake with arguments {--build /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/.externalNativeBuild/cmake/debug/armeabi-v7a --target native-lib} [1/2] Building C object CMakeFiles/native-lib.dir/src/main/cmake/native-lib.c.o clang: warning: argument unused during compilation: '-L/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/src/main/jniLibs/armeabi-v7a' [2/2] Linking C shared library ../../../../build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so FAILED: : && /Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=armv7-none-linux-androideabi --gcc-toolchain=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/xiongwei/android-sdks/Android/sdk/ndk-bundle/platforms/android-14/arch-arm -fPIC -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fno-integrated-as -mthumb -Wa,--noexecstack -Wformat -Werror=format-security -L/Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/src/main/jniLibs/armeabi-v7a -O0 -fno-limit-debug-info -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libnative-lib.so -o ../../../../build/intermediates/cmake/debug/obj/armeabi-v7a/libnative-lib.so CMakeFiles/native-lib.dir/src/main/cmake/native-lib.c.o -lTest -lm && : /Users/xiongwei/Documents/workspace/studio/jnistudys/jnistudy1/app/src/main/cmake/native-lib.c:11: error: undefined reference to '__android_log_print' clang: error: linker command failed with exit code 1 (use -v to see invocation) ninja: build stopped: subcommand failed.
下面来配置一下,这里就会学习到用查找的方式来将这个系统日志的so的路径给查出来,而不是手动去写死,如下:
咱们来看一下打印:
运行看一下是否打印出来日志了:
但是!!其实上面在CMake引入log库的方式还可以简化,如下:
另外还有一点就是咱们目前的源文件只有一个,这样配置没啥问题:
那如果有很多源文件一个个这样添加是不是很麻烦,这时可以引用某个目录下的所有源文件,如下:
另外还有一种指定目录的方式,了解下:
如果在cmake中需要使用其他目录的cmakelist,可以这样,具体就不演示了:
另外对于头文件的包含还有个小细节需要注意下,先新建一个头文件:
如果源文件中想要引用这个头文件,可以这样写:
但是还可以在CMake中加入这样一个配置来支持这样的写法,如下:
具体配置如下:
其实这句话配置就相当于:
此时再编译源文件就没报红了:
至此关于.mk和cmake相关配置的东东就学到这了,比上次学习还杂,但是收获还是颇多滴~~