android studio 已经成为目前目前开发Android的主流ide了,所以但是其目前对ndk开发做的不是很好,所以特地写一篇博客来记录下楼主目前用Android studio 开发ndk项目的方法
首先新建一个项目,创建一个Activity,添加如下代码:
/**
* cd app/src/main/java
* javah -d ../jni com.example.ndksample.myapplication.MainActivity
*
* @return string
*/
private static native String stringFromJni();
static {
System.loadLibrary("jni");
}
android studio 自带window命令行终端,这点特别方便。打开studio下面的Terminal,如下图所示,终端会自动定位到当前项目:
如果没有请到如下位置自己打开:
然后进入 项目的java目录,利用javah命令生成.h文件,cmd命令如下:
cd app/src/main/java
javah -d ../jni com.example.ndksample.myapplication.MainActivity
然后就会在 ‘app/src/main/java/jni“ 目录看到生成的com_example_ndksample_myapplication_MainActivity.h文件,内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndksample_myapplication_MainActivity */
#ifndef _Included_com_example_ndksample_myapplication_MainActivity
#define _Included_com_example_ndksample_myapplication_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_ndksample_myapplication_MainActivity
* Method: stringFromJni
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_ndksample_myapplication_MainActivity_stringFromJni
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
其实不需要这个文件,只需要看看jni对本地方法的命名的参数,因为楼主所有的是利用一个 JNINativeMethod 的结构体构建本地方法表,来注册本地方法。
然后再到jni目录新建一个main.cpp(PS:其实c也行,不过楼主更习惯用C++文件),main.cpp文件就是写jni本地方法的具体实现,具体内容如下,楼主会一一解释怎么做的:
#include <jni.h>
#include <android/log.h>
#include <stdlib.h>
#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif
#define LOG_TAG "android_native"
#ifndef LOGI
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#endif
#ifndef LOGE
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#endif
const char *className = "com/example/ndksample/myapplication/MainActivity"; // the jni class
/*
* Class: com_example_ndksample_myapplication_MainActivity
* Method: stringFromJni
* Signature: ()Ljava/lang/String;
*/
jstring stringFromJni(JNIEnv *env, jclass clazz) {
return env->NewStringUTF("this message is from jni");
}
/**
* name: stringFromJni
* signature: ()Ljava/lang/String;
* fnPtr: stringFromJni
*
* jni.h struct JNINativeMethod
*/
static const JNINativeMethod sMethods[] = { // jni methods table
{"stringFromJni", "()Ljava/lang/String;", (void *) stringFromJni},
};
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("GetEnv failed!");
return result;
}
LOGI("Retrieve the env success !");
jclass clazz = env->FindClass(className); // find the jni class
if (clazz == NULL) {
LOGE("FindClass Failed !");
return JNI_ERR;
}
if (env->RegisterNatives(clazz, sMethods, NELEM(sMethods)) < 0) { //register jni methods
LOGE("RegisterNatives Failed !");
return JNI_ERR;
}
return JNI_VERSION_1_4; // use jni version 1.4
}
说明:
前面是一些宏定义就不用多说了。
className 是你调用jni本地方法的那个类名,这个是给注册方法的时候查找类用。
stringFromJni 这个函数就是本地方法的实现,其实名字可以随意,只要你对应好,这就是楼主推荐的注册本地方法来实现jni的一些好处,不用谢那个长的函数名。
sMethods 这是一个结构体数组,在jni.h里面有定义JNINativeMethod的结构,我这边把那个结构体直接贴上来
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一个参数是函数名,也就是你java代码中写的那个本地方法的函数名字;
第二个参数是能唯一表明函数的一些特征(参数和返回值类型),这个可以在刚刚生成的那个.h文件中查看;
第三个就是函数指针地址,指向你本地具体实现的那个C/C++函数;
JNI_OnLoad 最后一个就是jni so库首次加载的时候会去调用的一个方法,这个方法是在jni.h中定义的,我们需要在这里面注册jni methods,这样每次加载时就能根据那个结构体数组的映射关系,正确的找到函数的执行地址,返回的时候需要返回JNI_VERSION_1_4,因为默认会返回低版本的,所以需要强行改一下。
好了,完成了.cpp文件接下来就是添加Android,mk文件,内容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog -lm
LOCAL_MODULE := jni
LOCAL_SRC_FILES := main.cpp
include $(BUILD_SHARED_LIBRARY)
这个也没什么好解释的,具体相关自行百度。
然后再添加一个Application.mk,内容如下:
APP_ABI := all
APP_STL := gnustl_static
APP_CFLAGS += -Wno-error=format-security
第一个标记是为所有平台编译so库,例如armeabi、armeabi-v7a、x86、x86_64等等,如果你不想所有都编译可以指定几个编译。
第二个和第三个是防止编译so库时出现一些错误而添加的,不加也可以。
好了,说了这么多,以上这些基本上在eclipse上就可以了,但是Android studio 是利用gradle构建的,如果设置其他的是不会编译so库的,在app目录的build.gradle进行如下修改:
import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "com.example.ndksample.myapplication"
minSdkVersion 10
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
jniLibs.srcDirs 'src/main/libs'
jni.srcDirs = [] // disable automatic ndk-build call
}
}
// call regular ndk-build(.cmd) script
task ndkBuild(type: Exec) {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
def command = properties.getProperty('ndk.dir', null) + "/ndk-build.cmd"
commandLine command, '-C', file('src/main/jni').absolutePath
} else {
def command = properties.getProperty('ndk.dir', null) + "/ndk-build"
commandLine command, '-C', file('src/main/jni').absolutePath
}
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.0'
compile 'com.android.support:design:23.1.0'
}
说明:
首先,你得在sourceSets中设置下 jni.srcDirs = [] 禁用gradle自动jni build,因为目前gradle plugin还不完善,支持的还不好,所以我们还是跟eclipse那样利用Android.mk去进行ndk开发,然后指定so库的目录为 src/main/libs,这样打包时studio才会把libs下的所有so库打进apk中,因为我们的jni目录是在src/main/jni ,当编译so库时会自动把生成的so库导入src/main/libs目录,所以这个地方路径不要写错。
其次,我们新建一个ndkbuild task来执行cmd命令,windows下利用ndk-build.cmd来执行,其他系统直接用ndk-build,在这里我们把ndk的根目录保存在了local.properties文件中,所以千万别忘了在这个文件中添加ndk根目录。
android studio其实可以图形化设置ndk的目录,具体在项目-右键-Open Modules Settings中去设置,如图:
如果你是用旧版的 studio,你可以自己手动添加ndk目录到 local.properties文件,例如 :
ndk.dir=F\:\\android-ndk-r10e
到此,如果你所有的都设置好了之后,直接运行项目,studio便会自动编译so库,然后打包时加进apk中,设置好之后,之后就基本不需要改动了,这样就完美支持jni开发 。
楼主是基于目前最新的开发环境给的例子,下面说下楼主所有的环境:
Android Studio 141.2343393
Gradle 2.8
com.android.tools.build:gradle:1.3.1
android-ndk-r10e
整个项目的git地址:点击打开链接
当然,你也可以使用体验版的gradle plugin来做ndk开发:
com.android.tools.build:gradle-experimental:0.2.1