Android.mk走读与Cmake配置

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(竖 手机)armeabiarmeabi-v7aarm64-v8ax86x86_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.solibhello-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相关配置的东东就学到这了,比上次学习还杂,但是收获还是颇多滴~~

posted on 2018-11-22 09:14  cexo  阅读(...)  评论(...编辑  收藏

导航

统计