一、如何安装配置NDK
1、对于Android Studio,点击Setting 在Android SDK -> SDK Tools,找到Android NDK选项,勾选并下载;
2、下载成功后,一般在你Android Studio配置的SDK的 sdk 目录下能找到 ndk-bundle目录;
3、设置环境变量,新建系统变量, NDK_HOME 值为 D:\adt-bundle\sdk\ndk-bundle,然后在 编辑用户变量 path,在后面添加 ;%NDK_HOME%;
4、Win +R 命令打开 cmd命令行,输入命令 ndk-build 后, 如果环境变量没有配置成功或者项目所在目录存有空格,就会提示 “ *** Android NDK: Aborting. . Stop. ”;
二、JNI编程之Java调用C方法
1、在NdkUtil类中,添加native 方法 和静态代码块加载 .so 库,代码如下:
public class NdkUtil {
static {
System.loadLibrary("myJniTest");
}
// Java调用C方法
public static native String getCLanguageString();
// C调用Java方法,提供入口
public static native void callJavaMethodFromJni();
}
在JniTestActivity中调用C方法,并显示在TextView;
public class JniTestActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jni_test);
textView = (TextView) findViewById(R.id.textView);
textView.setText(NdkUtil.getCLanguageString());
// C调用Java方法入口
NdkUtil.callJavaMethodFromJni();
}
}
2、选择 Build -> Make Project ,Make之后生成 .class文件,生成的.class文件 位于 build/intermediates/classes/debug/ 包名;
3、打开cmd,进入 项目/app/src/main/java 目录,进入到java目录下,使用javah命令在main目录下生成 jni目录,其中有生成的 .h 头文件;
命令如下:
cd D:\AndroidProject\CommonToolsApp\app\src\main\java
javah -d ../jni com.xss.mobile.activity.jnitest.NdkUtil
生成的.h头文件如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xss_mobile_activity_jnitest_NdkUtil */
#ifndef _Included_com_xss_mobile_activity_jnitest_NdkUtil
#define _Included_com_xss_mobile_activity_jnitest_NdkUtil
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_xss_mobile_activity_jnitest_NdkUtil
* Method: getCLanguageString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_xss_mobile_activity_jnitest_NdkUtil_getCLanguageString
(JNIEnv *, jclass);
/*
* Class: com_xss_mobile_activity_jnitest_NdkUtil
* Method: callJavaMethodFromJni
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_xss_mobile_activity_jnitest_NdkUtil_callJavaMethodFromJni
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
4、编辑.cpp文件,在jni目录下 新建 com_xss_mobile_activity_jnitest_NdkUtil.cpp 文件,实现上一步在头文件生成的方法,代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <Android/log.h>
#include "com_xss_mobile_activity_jnitest_NdkUtil.h"
#define TAG "tag_myJniTest"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) // 定义DEBUG类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) // 定义INFO类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) // 定义WARN类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) // 定义ERROR类型
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_xss_mobile_activity_jnitest_NdkUtil
* Method: getCLanguageString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_xss_mobile_activity_jnitest_NdkUtil_getCLanguageString
(JNIEnv *env, jclass clazz) {
return env->NewStringUTF("Hello, world!");
}
JNIEXPORT void JNICALL Java_com_xss_mobile_activity_jnitest_NdkUtil_callJavaMethodFromJni
(JNIEnv *env, jclass clazz) {
LOGI("%s", "-----begin C-----");
}
#ifdef __cplusplus
}
#endif
5、配置NDK目录,在 local.properties 文件中指明 sdk 和 ndk 的路径:
6、设置ndk参数、名字、发布平台等,如下所示:ndk.dir=F\:\\adt-bundle-windows-x86_64-20140917\\sdk\\ndk-bundle sdk.dir=F\:\\adt-bundle-windows-x86_64-20140917\\sdk
7、生成.so文件,再次选择 Build -> Make Project 就可以生成 .so 文件。会出现不支持NDK的错误提示,可根据提示修改,在项目 gradle.properties中添加:defaultConfig { applicationId "com.xss.myjni" minSdkVersion 16 targetSdkVersion 23 versionCode 1 versionName "1.0" ndk { moduleName "myjni" ldLibs "log", "z", "m" abiFilters "armeabi", "armeabi-v7a", "x86" } }
#使用遗弃的NDK; android.useDeprecatedNdk=true
8、针对三种不同平台的 CPU 生成三种不同的 .so 文件,生成的 .so文件位于 build/intermediates/debug/lib;
9、安装运行。
三、JNI编程之C调用Java方法
JNI调用Java方法,提供一个native方法,在生成的.cpp文件中,完成该方法的逻辑调用,通过JNIEnv指针提供的FindClass 和 GetMethodID 找到对应的类和方法。
1、新建供C调用的类和方法,代码如下:
public class JniHandle {
/**
* C调用Java的方法,可理解为C需要获取UI线程中的某个参数,以此传参于C
* @return
*/
public static String getStringFromStatic() {
Log.d("JniHandle", "c invoke Java");
return "String from static method in Java";
}
}
2、通过javap命令获取 Java类签名,命令如下;
cd D:\AndroidProject\CommonToolsApp\app\build\intermediates\classes\debug
javap -s com.xss.mobile.activity.jnitest.JniHandle
生成方法签名如下:
Compiled from "JniHandle.java"
public class com.xss.mobile.activity.jnitest.JniHandle {
public com.xss.mobile.activity.jnitest.JniHandle();
descriptor: ()V
public static java.lang.String getStringFromStatic();
descriptor: ()Ljava/lang/String;
}
3、在com_xss_mobile_activity_jnitest_NdkUtil.cpp 文件中补全Java_com_xss_mobile_activity_jnitest_NdkUtil_callJavaMethodFromJni 方法体,代码如下:
JNIEXPORT void JNICALL Java_com_xss_mobile_activity_jnitest_NdkUtil_callJavaMethodFromJni(JNIEnv *env, jclass clazz) {
LOGI("%s", "-----begin C-----");
LOGI("%s", "This is at C");
// 得到JniHandler java类
jclass jniHandle = env->FindClass("com/xss/mobile/activity/jnitest/JniHandle");
if (NULL == jniHandle) {
LOGI("%s", "Cant find JniHandle");
}
// 通过GetStaticMethodID获取静态方法
jmethodID method = env->GetStaticMethodID(jniHandle, "getStringFromStatic", "()Ljava/lang/String;");
if (NULL == method) {
env->DeleteLocalRef(jniHandle);
LOGI("%s", "Cant find method getStringFromStatic() from JniHandle");
return;
}
jstring result = (jstring)env->CallStaticObjectMethod(jniHandle, method);
const char *resultChar = env->GetStringUTFChars(result, NULL);
env->DeleteLocalRef(jniHandle);
env->DeleteLocalRef(result);
LOGI("%s", resultChar);
LOGI("%s", "-----end C-----");
}
Tips:
1)DeleteLocalRef方法表示释放局部引用,VM释放局部引用有两种方式:一是本地方法执行完毕后VM自动释放;二是通过DeleteLocalRef手动释放。
2)VM可以自动释放,为什么还要手动释放?
A:因局部变量会阻止它所引用的对象被GC回收,它所引用的对象无法被GC回收,自己本身也就无法自动释放,因此需要使用DeleteLocalRef手动释放。
4、在JniTestActivity中打开入口供C调用Java方法:
// C调用Java方法入口
NdkUtil.callJavaMethodFromJni();
四、如何打印JNI C代码日志
1、导入log头文件: #include <Android/log.h>
2、在Android.mk 文件中添加:LOCAL_LDLIBS :=-llog (一般会自动添加上)
3、定义LOG 函数,先定义一个全局变量,再定义一些输出的LOG函数,代码如下:
#define TAG "tag_myJniTest"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) // 定义DEBUG类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) // 定义INFO类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) // 定义WARN类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) // 定义ERROR类型
4、调用: LOGI("%s", "-----end C-----");
五、上述整个工程运行结果如下:
六、JNI编程之C和C++的区别
1、hello.c文件。在C中没有引用,传递的env是两级指针,用(*env)方法调用并且要在方法中传入 env参数。
hello.cpp文件。C++中env为一级指针,用env->调用方法,无需传入env;C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern "C"进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名;extern "C"{jni代码}
c调用:
return (*env)->NewStringUTF(env, "Hello, world!");
c++调用:
return env->NewStringUTF("Hello, world!");
2、Android.mk文件中,需要更改为对应的文件名:
LOCAL_SRC_FILES := (is C)? hello.c : hello.cpp
相关链接:
Android NDK开发流程:http://jingyan.baidu.com/article/a501d80c1a9aa4ec630f5e8b.html
Android NDK开发——Java调用:https://segmentfault.com/a/1190000005760699