上一篇博客博客地址关于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
这三个选项一定要选择啊,这是造船的基础,工欲善其事,必先利其器,哈,完事后我们新建一个工程,如下图
第一步:
在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中执行
全部代码:
#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文件移植到别的工程引用就不在此说明了,可以观看我的另一篇文章,文章开头已放链接,写的较为简陋,请多多包涵,有问题随时指出或者在下方评论