在AndroidStudio 中进行NDK 开发比起以往的Eclipse 要方便的多,下面来介绍下如何使用AndroidStudio 这个 IDE 工具实现NDK 相关开发工作。
1. 准备工作
在实际写代码之前,首先我们还是需要做一些准备工作:
配置系统环境变量
下载好NDK 开发包之后,直接解压到任意目录,然后需要配置一下系统环境变量,之所以要配置环境变量,是为了方便使用命令ndk-build 脚本进行NDK 编译。配置参考如下:
# 在.bash_profile中配置如下代码export ANDROID_NDK=/Users/you/android-ndkexport
PATH=$ANDROID_NDK:$PATH
# 然后执行如下代码,更新配置文件
source .bash_profile
其实编译C/C++代码不一定在AndroidStudio 中,如果配置好环境变量,直接使用进入项目中的jni 目录执行ndk-build 命令即可在当前目录下生成一个libs 的目录,里面存放了不同 平台的.so 包,当然运行这个命令的前提是,这个目录下至少得有一个Android.mk 文件,如果需要指定具体的编译平台,那么还需要添加一个Application.mk 文件,当然,如果命令行让你头疼,那么你可以采用gradle 的方式来解决这些问题,接下来我们将分别介绍这些使用方式。
2. 项目配置
使用AndroidStudio 开发前我们也要做点额外工作,我们需要在项目根目录下local.properties 中添加编译NDK 的路径:
ndk.dir=/Users/you/android-ndk
如果这个文件不存在,你可以手动生成一个,然后再添加上述内容即可。完成这个步骤之后,我们就可以正式开始着手NDK 相关的开发工作了。之所以要配置这个目录,目的是让我们开发的项目在使用 gradle 编译时能够找到NDK 相关编译路径
那么,接下来的工作也分为两种情况:
没有(C/C++)源码,别人已经提供好相应的.so 文件,不需要编译代码
拥有(C/C++)源码,需要自己编译.so 文件
2.1 已有.SO文件,不需要编译源码
这类情况是最简单的,.so 文件以及被其他人员编译好,或者是第三方库来提供的,那么我们只需要把相应.so 文件放到AndroidStudio 目录src/main/jniLibs/ 下即可,当然,肯定需要按 CPU 架构分不同的子目录。
jniLibs 是AndroidStudio 默认提供的ndk 目录,用来存放已经编译好的.so 文件,当然你也可以放在任意自定义目录下,例如src/main/libs,然后在build.gradle 中指定相应的资源目录位置即可:
android {
sourceSets.main {
// 你的.so库的实际路径
jniLibs.srcDir 'src/main/libs'
}
}
在导入.so 文件完成之后,那么你可以在相应的java 类文件中,加载这个静态库,一般来说,.so 文件如果由第三方提供,他在提供.so 文件的同时也会提供相应的java 调用类文件,或者按之前双方定好的规则自己创建相应类文件,并生成相应的方法,之所以要约定好只因为,NDK 下的C/C++
函数和Java 桥接的函数命名是有约束的,规则如下:
Java_PackageName_ClassName_MethodName
双方必须按这个规则来实现或者调用此函数,否则不会成功,例如,我们现在有一个函数:String stringFromJNI()的java函数,它在com.example.hellojni.HelloJni 这个文件下,这个函数用来返回一个字符串,功能由底层C 来实现,那么相应的C 语言jni 开发文件中就必须按上述规则命名一个Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz) 的函数,并返回一个字符串结果:
#include
// 函数名格式必须按规矩来
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
{
return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI ".");
}
同样对应的java 文件也必须:
文件必须在com.example.hellojni 包名下
类文件名必须是HelloJni
方法名必须是stringFromJNI
package com.example.hellojni;
class HelloJni {
public static native String stringFromJNI();
static {
// 加载 hellojni.so静态块
System.loadLibrary("hellojni");
}
}
2.2 有源码,需要编译.so
文件
如果有 C/C++ 源码,没有.so 文件,那么我们就得手动把源码文件编译成.so 文件,编译的方式也分为两种:
手工执行命令经行编译
使用gradle 脚本自动实现编译
AndroidStudio 默认的源码存放目录是:
src/main/jni
如果你没发现此目录,那么你可以手动创建一个,把所有的 C/C++ 源码放在此文件下,当然并非必须要放在此目录下,你可以自定义目录,然后在build.gradle 中做一个资源路径指定即可:
// build.gradle
android {
sourceSets.main {
// 你的源码目录
jni.srcDir 'src/main/otherDir'
}
}
2.2.1 手工执行命令经行编译
在使用手工编译(C/C++)文件之前,我们要回到文章开头部分,我们需要配置好系统环境变量,这样我们才能在系统环境下执行ndk相关编译命令,如果您的环境变量还没有配置,那么可以参考下文章开头部分,如果已经做好这部分工作,那么咱们继续。
接下来,我们还要创建如下两个文件:
Android.mk
Applicatoin.mk (非必要)
2.2.1.1 创建 Android.mk
Android.mk 文件用来指定源码编译的配置信息,例如工作目录,编译模块的名称,参与编译的文件等,大致内容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello_jni
LOCAL_SRC_FILES := hello_jni.c
include $(BUILD_SHARED_LIBRARY)
1. LOCAL_PATH:设置工作目录,而 my-dir 则会返回 Android.mk 文件所在的目录。
2. CLEAR——VARS:清除几乎所有以 LOCAL——PATH 开头的变量(不包括 LOCAL_PATH)。
3. LOCAL_MODULE:用来设置模块的名称。
4. LOCAL_SRC_FILES:用来指定参与模块编译的 C/C++ 源文件名。
5. BUILD_SHARED_LIBRARY:作用是指定生成的静态库或者共享库在运行时依赖的共享库模块列表。
2.2.1.2 创建 Application.mk
这个文件用来配置编译平台相关内容,我们最常用的估计只是APP_ABI
字段,它用来指定我们需要基于哪些 CPU 架构的.so 文件,当然你可以配置多个平台:
APP_ABI := armeabi armeabi-v7a x86 mips
如果不创建Application.mk 文件,那么手动编译的.so 文件只有armeabi 平台一个版本,其他平台的不会被编译。
假设我们配置好了Android.mk 文件,那么接下来我们就可以执行如下命令来生成.so 文件了,我们假设开发NDK
的目录为默认目录:
cd src/main/jni/
ndk-build
如果顺利,那么你将会看到,在src/main/ 目录下会多了一个libs 目录,这是NDK 使用命令编译.so 文件的生成的默认目录,而AndroidSutdio 默认加载NDK 的目录是jniLibs,那么你有两种解决方式:
配置build.gradle 资源目录,参见文章 2.1 小节
使用 ndk-build NDK_LIBS_OUT=../jniLibs指定具体的输出目录
生成.so后,为了避免C/C++文件被Java编译,可能需要使用如下gradle 配置:
sourceSets.main {
// default .so path
jniLibs.srcDir 'src/main/libs'
// disable automatic ndk-build
jni.srcDirs = []
}
当你得到了.so 文件,那么接下来就是在java 文件中调用执行即可,如果想了解更多ndk-build 命令内容,可参见:Android ndk-build 使用文档
2.2.2 使用 gradle 脚本
当然该机器做的事我们还是尽量让机器来做,因此,接下来我打算使用build.gradle 来添加一些配置,让Gradle 自动帮我完成编译工作,这简直就是爽歪歪啦!
使用gradle, 你再也不用手动添加Android.mk 和Application.mk 文件,一切在build.gradle 文件中就都能搞定,在这里我们直接贴出build.gradle 中ndk 相关的配置:
android.ndk {
// 模块名称
moduleName = "hello-jni"
// 指定编译平台,更多平台信息 参见https://developer.android.com/ndk/guides/abis.html#sa
abiFilters "armeabi", "armeabi-v7a"
/* * Other ndk flags configurable here are
* cppFlags.add("-fno-rtti")
* cppFlags.add("-fno-exceptions")
* ldLibs.addAll(["android", "log"])
* stl = "system"
*/
}
使用gradle 的好处是,自动编译生成apk 文件,并且把相关的.so 文件打包到apk 安装包中,一劳永逸。