最近在项目中要使用Ndk,这里对Ndk的一些配置使用做记录如下。我们之前使用Ndk都是通过ndk-build来进行配置实现的,现在AndroidStudio出了另外一种使用Cmake来进行更加简单的实现,我们了解使用这两种方式。
通过ndk-build构建Ndk项目
ndk-build的形式来构建ndk代码也就是我们之前常看到的jni下有android.mk,application.mk的那一些配置来构建ndk项目,这个东西要总结好其实也是很固定的一些配置写法。
配置Ndk常用命令
我们之前一直要生成头文件,那串东西生成很麻烦,以及使用ndk-build来生成so文件,现在Androidstudio支持配置命令来完成这些操作。
Javah命令配置:
具体命令如下:
Program:
$JDKPath$/bin/javah
Parameters:
-encoding UTF-8 -d ../jni -jni $FileClass$
Working directory:
$SourcepathEntry$
ndk-build配置:
Program: 你的NDK目录/build/ndk-build
注意:windows用ndk-build.cmd,MAC/Linux用ndk-build
Parameters: 什么都不用填
Working directory:$ModuleFileDir$/src/main
ndk-build clean配置:
Program: 你的NDK目录/build/ndk-build
注意:windows用ndk-build.cmd,MAC/Linux用ndk-build
Parameters: clean
Working directory:$ModuleFileDir$/src/main
配置完成以后,我们点击文件右键看到
这样就可以happy的使用这些命令了。
配置工程
在local.properties文件中配置ndk目录(这个如果你下了ndk会自动配置上)
ndk.dir=yours/Android/sdk/ndk-bundle
在gradle.properties文件中配置
android.useDeprecatedNdk = true
ok从上面开始我们就完成了基本的配置工作。下面来记录几种生成so文件的方式。
生成单个so文件
当我们自己的c代码要生成一个so文件库的时候我们可以通过在gradle里面进行简单配置就可以了。
- 在当前module应用的build.gradle中配置信息:
android {
//...
defaultConfig {
//...
ndk {
moduleName "OldUseNdk"//定义NDKlibrary的名字
abiFilters "armeabi-v7a"//输出不同平台so
}
}
//...
}
- 在Activity中声明要调用的so库里面的函数
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("OldUseNdk");
}
TextView tv;
// public native String getName();
public native String getName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.text);
tv.setText(getName());
}
}
注意这里的loadLibrary的名字为我们在上面的gradle里面配置的名字
- 实现cpp函数
上面的.h文件通过配置工具来生成的,里面的实现细节看最后的源代码。
这时的so文件默认放在这里:
再来看看我们的apk的文件so的位置在这里:
到这里我们就可以通过gradle来编译so库到我们的apk里面来使用了,感觉非常的简单
我们这里是生成一个so文件,但是当我们有多个cpp文件的时候,这种方式在gradle里面配置modulename的时候只有一个,要怎么生成单独的多个so勒?
生成多个so文件
我们在上面配置了一个命令ndk-build.我想不通过gradle命令来配置so的编译库,而是通过ndk-build来生成so文件,最后把生成好的文体放到项目的默认so 库的位置jinLibs目录下面。来干吧
通过ndk-build来生成多个so库
jni里面多添加一个cpp文件,并且添加ndk-build编译的标准的Android.mk和Application.mk文件
目录结构如下:
其中Android.mk和Application.mk的配置几乎相同。
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := OldUseNdk
LOCAL_SRC_FILES := oldndkuse.cpp
include $(BUILD_SHARED_LIBRARY)
TARGET_ARCH_ABI := armeabi-v7a
Application.mk
#APP_MODULES := OldUseNdk
APP_ABI := armeabi-v7a arm64-v8a
这两个文件里面的配置命令含义参考官网:点击
到这里我们先不执行ndk-build命令,我们之前在gradle里面简单的配置了so输出的信息,我们这时直接运行项目看看是什么结果
运行后发现可以直接运行并且so的位置和在输出的apk中和上面单个cpp的名称也一样;并且我们在MainActivity中调用oldndkuse2的实现也可以成功的调用。
在此,我觉得gradle把我的两个cpp文件的实现打到了一个so文件里面去。
回到我们的使用ndk-build来生成so文件的方式上面来。
在jni目录上右键执行ndk-build命令。
在main目录下面生成如下文件:
ok这不就是我们要的so库文件嘛,把它移到jniLibs下面就好啦
但是有一个问题就是我可以使用ndk-build生成两个不同的so文件而不是通过gradle的默认的方式打到一个so库下面去,可是我没有发现该如何移除grad le默认打包的so库文件,在输出的apk中总会有一个合并的so文件?下面要试图通过修改gradle默认打包输出一个so文件而是输出不同的so文件。
通过gradle输出不同的so库文件
又绕到了gradle的配置上面来了,这里上面的cpp文件写好就好了,通过gradle来直接搞定,配置信息如下:
android {
//...
ndk {
// moduleName "OldUseNdk"//定义NDKlibrary的名字
abiFilters "armeabi-v7a"//输出不同平台so
}
sourceSets.main.jniLibs.srcDirs = ['src/main/libs','libs']
sourceSets.main.jni.srcDirs = []
}
//...
}
task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
def ndkDir = android.ndkDirectory
commandLine "$ndkDir/ndk-build",
'-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
'-j', Runtime.runtime.availableProcessors(),
'all',
'NDK_DEBUG=1',
'V=1'
}
task cleanNative(type: Exec, description: 'Clean JNI object files') {
def ndkDir = android.ndkDirectory
// if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine "$ndkDir/ndk-build",
'-C', file('src/main/jni').absolutePath,
'clean'
}
//
//clean.dependsOn 'cleanNative'
task nativeLibsToJar(type: Zip, description: 'create a jar archive of the native libs') {
destinationDir file("$projectDir/src/main/java/libs")
// baseName 'native-libs'
extension 'jar'
from fileTree(dir: "$projectDir/src/main/java/libs", include: '**/*.so')
into 'lib/'
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn(buildNative)
}
我们在gradle里面把jniLibs目录直接链到libs下面了,不用把so移到jniLibs下面去了。
看看生成的so的效果:
完美的生成两个不同的so库。并且最后输出的so库的平台是Android.mk下面的配置平台,应为gradle里面的命令也是执行ndk-build来生成so文件。
通过Cmake来开发Ndk项目
AndroidStudio2.2以后支持开发Ndk使用Cmake来编译C或者C++部分的代码,这种方式可比原来的mk文件的形式简单了,下载Cmake支持我们在这里就不多说了,AndroidStudio支持构建一个包含C++的工程,在我们新建工程的时候勾选下面的支持即可。
项目结构分析
我们新建的项目目录结构如下:
和我们以前的项目不同点就是红色箭头指示的地方了,以前使用Android.mk的时候要到build.gradle里面去配置很多,Cmake的配置也很简单:
android {
defaultConfig {
externalNativeBuild {
cmake {
//// Sets a flag to enable format macro //constants for the C++ compiler.
cppFlags "-frtti -fexceptions"
// 指定只用clang编译器
// Clang是一个C语言、Objective-C、C++语言的轻量级编译器。
arguments "-DANDROID_TOOLCHAIN=clang"
// 生成.so库的目标平台
}
}
ndk {
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
'arm64-v8a'
}
}
externalNativeBuild {
cmake {
//指定CMakeLists.txt的位置
path "CMakeLists.txt"
}
}
}
Cmake文件:
cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp)
add_library( # Sets the name of the library.
my-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/my-lib.cpp)
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.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
add_library():
配置so库信息- native-lib:so库的名称。
- SHARED:so库文件共享,会在Run项目或者build项目时会在目录intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下生成so库文。此外,so库文件都会在打包到.apk里面,可以通过选择菜单栏的Build->Analyze Apk…*查看apk中是否存在so库文件,一般它会存放在lib目录下。
- src/main/cpp/my-lib.cpp:构建so库的源文件
find_library()
这个方法与我们要创建的so库无关而是使用NDK的Apis或者库,默认情况下Android平台集成了很多NDK库文件,所以这些文件是没有必要打包到apk里面去的。直接声明想要使用的库名称即可(猜测:貌似是在Sytem/libs目录下)。在这里不需要指定库的路径,因为这个路径已经是CMake路径搜索的一部分。如示例中使用的是log相关的so库。- log-lib
这个指定的是在NDK库中每个类型的库会存放一个特定的位置,而log库存放在log-lib中 - log
指定使用log库
- log-lib
target_link_libraries()
如果你本地的库(native-lib)想要调用log库的方法,那么就需要配置这个属性,意思是把NDK库关联到本地库。- native-lib
要被关联的库名称 ${log-lib}
要关联的库名称,要用大括号包裹,前面还要有$符号去引用。
- native-lib
Question one:如何生成多个so文件库?
Answer::在CmakeLists.cpp中的再次写一个add_library()做相应的配置即可
Question two:如何使用写好的so?
Answers:
- 告诉脚步导入so库:
add_library( imported-lib
SHARED
IMPORTED )//IMPORT表示只需要导入,不需要构建so
- 设置so库路径:
set_target_properties(
imported-lib // so库的名称
PROPERTIES IMPORTED_LOCATION // import so库
libs/libimported-lib.so // so库路径
)
Question three:如何知道so的输出路径?
Answer:默认的情况Cmake会把so放到这里
我们要到CmakeLists.txt中进行配置如下:
#设置生成的so动态库最后输出的路径
set(jnilibs "${CMAKE_SOURCE_DIR}/src/main/jniLibs")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${jnilibs}/${ANDROID_ABI})
这样就可以打到我们以前熟悉使用的so的目录下面了
感觉通过Cmake来配置要灵活一些,以后开发也会首选Cmake来进行配置。
Cmake配置引用第三方so实例
刚才我们通过CMake提到了引入第三方so的配置代码,但是这个里面在我实际操作了以后感觉还不是那么简单的配置的事情。
在之前我们引入第三方so放到jniLibs就好了,然后在Java层调用它实现的方法就好了,但是有一个问题就是我们自己的C++代码如何引用第三方so里面的实现方法我们下面就做这个事情
生成简单的第三方so库
在之前的使用Cmake的例子上面多添加一个cpp文件并且实现生成so
这里作为第三方库供别人使用,我多添加了一个.h文件,它是提供给使用者引用的。
两个文件的实现很简单做一个简单加法:
forthird-lib.h:
#ifndef MYNDKDEMO_FORTHIRD_LIB_H
#define MYNDKDEMO_FORTHIRD_LIB_H
#ifdef __cplusplus
extern "C" {
#endif
int add(int a, int b);
#ifdef __cplusplus
}
#endif
#endif //MYNDKDEMO_FORTHIRD_LIB_H
forthird-lib.cpp:
#include <jni.h>
#include <string>
#include "forthird-lib.h"
int add(int a,int b){
return a+b;
}
本地调用第三方so库代码
引入第三方so库相关文件到项目
首先看一下应用的项目结构:
我们把引入的so放到了jniLibs目录下面,把forthird-lib.h文件放到了cpp目录下面。
配置应用CmakeLists.txt
这里我只贴出和第三方so相关的配置代码:
#加载第三方库作为动态库引用
add_library(
#libxxx.so的名称xxx
forthird-lib
#SHARED 为引入动态库so,STATIC 为引入静态库.a
SHARED
IMPORTED
)
SET(
third_path
${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libforthird-lib.so
)
MESSAGE(STATUS “src third so path= ${third_path}”)
#设置要连接的so的相对路径,${ANDROID_ABI}表示so文件的ABI类型的路径,这一步引入了动态加入编译的so
set_target_properties(
forthird-lib
PROPERTIES IMPORTED_LOCATION
${third_path}
)
include_directories( ${CMAKE_SOURCE_DIR}/src/main/cpp/include)
target_link_libraries( # Specifies the target library.
native-lib forthird-lib
# Links the target library to the log library
# included in the NDK.
${log-lib}
)
本地应用C++引用so方法
直接看到native-lib.cpp文件:
#include <jni.h>
#include <string>
#include "include/forthird-lib.h"
extern "C"
JNIEXPORT int JNICALL
Java_com_lyman_usethirdso_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */,jint a,jint b) {
const char *hello = "Hello from C++ Me";
int result = add(a,b);
//return env->NewStringUTF((const char *) add(a, b));
return result;
}
本应用Java层调用实现代码
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI(77,11)+"");
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native int stringFromJNI(int a,int b);
}
小结:
这里的配置要注意几个点:
- 关于第三方so文件的存放位置一定是能让AS找到的位置,我认为放在jniLibs默认的so的位置比较简单
- 第三方so的.h文件要引入进来,引入了以后要在CmakeLists.txt里面进行配置
参考demo代码:查看