android CMake制作.so文件与跨平台引用

上一篇博客博客地址关于so文件的制作的是用ndkbuild命令执行,配置略显繁琐,且如果项目build升级到3.0,google便不在推荐使用此方式进行编译,而是使用最新的CMake工具进行编译,CMake对于上一种方式虽说简单了一些,但要熟练掌握还是有不小的难度的

1 NDK 简介
在介绍 NDK 之前还是首推 Android 官方 NDK 文档。官方地址
官方文档分别从以下几个方面介绍了 NDK

NDK 的基础概念
如何编译 NDK 项目
ABI 是什么以及不同 CPU 指令集支持哪些 ABI

1.1 NDK 基础概念
首先先用简单的话分别解释下 JNI、NDK, 以及分别和 Android 开发、c/c++ 开发的配合。在解释过程中会对 Android.mk、Application.mk、ndk-build、CMake、CMakeList 这些常见名词进行扫盲。
JNI(Java Native Interface):Java本地接口。是为了方便Java调用c、c++等本地代码所封装的一层接口(也是一个标准)。大家都知道,Java的优点是跨平台,但是作为优点的同时,其在本地交互的时候就编程了缺点。Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,于是Java提供了jni专门用于和本地代码交互,这样就增强了Java语言的本地交互能力。上述部分文字摘自任玉刚的 Java JNI 介绍

NDK(Native Development Kit) : 原生开发工具包,即帮助开发原生代码的一系列工具,包括但不限于编译工具、一些公共库、开发IDE等。
NDK 工具包中提供了完整的一套将 c/c++ 代码编译成静态/动态库的工具,而 Android.mk 和 Application.mk 你可以认为是描述编译参数和一些配置的文件。比如指定使用c++11还是c++14编译,会引用哪些共享库,并描述关系等,还会指定编译的 abi。只有有了这些 NDK 中的编译工具才能准确的编译 c/c++ 代码。
ndk-build 文件是 Android NDK r4 中引入的一个 shell 脚本。其用途是调用正确的 NDK 构建脚本。其实最终还是会去调用 NDK 自己的编译工具。

CMake 又是什么呢。脱离 Android 开发来看,c/c++ 的编译文件在不同平台是不一样的。Unix 下会使用 makefile 文件编译,Windows 下会使用 project 文件编译。而 CMake 则是一个跨平台的编译工具,它并不会直接编译出对象,而是根据自定义的语言规则(CMakeLists.txt)生成 对应 makefile 或 project 文件,然后再调用底层的编译。
在Android Studio 2.2 之后,工具中增加了 CMake 的支持,你可以这么认为,在 Android Studio 2.2 之后你有2种选择来编译你写的 c/c++ 代码。一个是 ndk-build + Android.mk + Application.mk 组合,另一个是 CMake + CMakeLists.txt 组合。这2个组合与Android代码和c/c++代码无关,只是不同的构建脚本和构建命令。本篇文章主要会描述后者的组合。(也是Android现在主推的)
1.2 ABI 是什么
ABI(Application binary interface)应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,所以同样的程序代码为了兼容多个不同的CPU,需要为不同的 ABI 构建不同的库文件。当然对于CPU来说,不同的架构并不意味着一定互不兼容。

armeabi设备只兼容armeabi;
armeabi-v7a设备兼容armeabi-v7a、armeabi;
arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;
X86设备兼容X86、armeabi;
X86_64设备兼容X86_64、X86、armeabi;
mips64设备兼容mips64、mips;
mips只兼容mips;

具体的兼容问题可以参见这篇文章。Android SO文件的兼容和适配
当我们开发 Android 应用的时候,由于 Java 代码运行在虚拟机上,所以我们从来没有关心过这方面的问题。但是当我们开发或者使用原生代码时就需要了解不同 ABI 以及为自己的程序选择接入不同 ABI 的库。(库越多,包越大,所以要有选择)

CMake使用
一般是在项目创建的时候勾选上对C++ 的支持,系统会自动创建相应的CMake程序,不过有些时候需要在已经创建完毕且没有勾选C++支持的库的工程中使用CMake

SDK选项

这三个选项一定要选择啊,这是造船的基础,工欲善其事,必先利其器,哈,完事后我们新建一个工程,如下图
这里写图片描述

第一步:
在main文件夹下创建cpp文件夹,用来存放生成的.cpp文件,接着在cpp文件夹下创建一个空的cpp文件
创建方法new Directory=》new C++ Source File,如下图

这里写图片描述

第二步:
创建工具类CmakeUtils文件来加载so文件,内容如下:

public class CmakeUtils {
  //cmaketestone为cpp文件夹下的cpp文件夹名称
    static {
        System.loadLibrary("cmaketestone");
    }

    public static native String getString(String as);
}

第三步:
在app目录下创建CMakeLists.txt文件,如下图:
这里写图片描述

cmake_minimum_required(VERSION 3.4.1)

# 编译出一个动态库 cmaketestone,源文件只有 src/main/cpp/cmaketestone.cpp
add_library(cmaketestone SHARED src/main/cpp/cmaketestone.cpp)

# 找到预编译库 log_lib 并link到我们的动态库 native-lib中
find_library( # Sets the name of the path variable.
              log-lib
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

target_link_libraries( # Specifies the target library.
                       cmaketestone
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

具体的CMakeLists.txt属性详解请参考官方翻译中文版本,这其实是一个最基本的 CMakeLists.txt ,其实 CMakeLists.txt 里面可以非常强大,比如自定义命令、查找文件、头文件包含、设置变量等等

第四步:

根据创建好的CMakeLists.txt文件编译生成对应的build.gradle代码
在CMakeLists.txt文件上右键选择Link C++ Project with Gradle,注意(选择自己工程的CMakeLists.txt文件)
等构建完毕后会在build.gradle中生成对应的代码,如下图:
这里写图片描述

第五步:
回到刚才创建的CmakeUtils中选择getString方法生成对应的头文件
这里写图片描述

不构建完毕是没有这个选项的,生成完毕后复制相应的代码到cmaketestone.cpp中,之后删除就行了,主要逻辑代码cmaketestone.cpp中执行
12

这里写图片描述

全部代码:

#include <jni.h>
#include <string.h>
#include <stdlib.h>
/**
 * jstring转char函数...
 * @param env
 * @param jstr
 * @return
 */
char *Jstring2CStr(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("UTF-8");   //这里填写是工作空间的编码,若是默认中文则是GB2312
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char *) malloc(alen + 1);  //字符串拼接函数...
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}

extern "C"
/**
 * 字符串拼接的第一种方法
 * @param env
 * @param instancek
 * @param str_
 * @return
 */
JNIEXPORT jstring JNICALL
Java_com_lgd_cmaketest_1one_CmakeUtils_getString(JNIEnv *env, jobject instance,
                                          jstring str_) {
    char *cstr = Jstring2CStr(env, str_);
    char *hellostr = "-World";
    strcat(cstr, hellostr); //拼接两个字符串
    return env->NewStringUTF(cstr);
}

第六步:
使用,可以看到项目编译完毕后生成了arm64-v8a的so文件,默认生成,如果需要其他版本需要在 声明ABI头文件
这里写图片描述

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i("TAG",""+new CmakeUtils().getString(" 二哈 "));
    }
}

这里写图片描述

到此完毕,将生成的so文件移植到别的工程引用就不在此说明了,可以观看我的另一篇文章,文章开头已放链接,写的较为简陋,请多多包涵,有问题随时指出或者在下方评论

猜你喜欢

转载自blog.csdn.net/liu3364575/article/details/80091506