最近学习JNI为了记录一下开发过程,通过实现一个简单的jni例子来记录下开发的流程和环境配置,使用的操作系统是windows。
使用Android Studio开发当然开发环境时必须的,此外需要配置NDK的开发环境,NDK环境很简单,首先需要下载NDK包,可以自行google下载,也可以在这里下载。下载后解压,注意ndk包所放的目录路径中不能有空格,如:Program Files (x86),否则在命令行中使用ndk-build时会报错如下:
C:\Users\49801>ndk-build
'D:\Program' 不是内部或外部命令,也不是可运行的程序
或批处理文件。
配置好环境后执行ndk-build结果如下:
C:\Users\49801>ndk-build
Android NDK: Could not find application project directory !
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
D:\android-ndk-r17\build\\..\build\core\build-local.mk:151: *** Android NDK: Aborting . Stop.
至此可以开始第一个实例开发了。
第一步:
新建Android项目取名MyFirstJni,并在项目中新建java文件取名JNIMethod,并在加载JNI库和定义调用native的方法。内容如下:
public class JNIMethod
{
static {
System.loadLibrary("JNIMethod");
}
public static native String getNativeString();
}
并在MainActivity中调用getNativeString方法如下:
public class MainActivity
extends AppCompatActivity
{
private TextView txtContent;
private Button btnStart;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtContent = (TextView) findViewById(R.id.txt_content);
btnStart = (Button) findViewById(R.id.btn_start);
btnStart.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
txtContent.setText(JNIMethod.getNativeString());
}
});
}
}
第二步
进入JNIMethod文件所在目录执行javac JNIMethod.java生成对应的class文件,
D:\code\MyFirstJni\app\src\main\java\com\demon\myfirstjni>javac JNIMethod.java
然后使用.class文件生成.h头文件,退回到项目根目录即/java目录下执行javah JNIMethod,
D:\code\MyFirstJni\app\src\main\java\com\demon\myfirstjni>cd ../../..
D:\code\MyFirstJni\app\src\main\java>javah com.demon.myfirstjni.JNIMethod
执行完成后会在该目录下生成.h文件com_demon_myfirstjni_JNIMethod.h,该文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_demon_myfirstjni_JNIMethod */
#ifndef _Included_com_demon_myfirstjni_JNIMethod
#define _Included_com_demon_myfirstjni_JNIMethod
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_demon_myfirstjni_JNIMethod
* Method: getNativeString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_demon_myfirstjni_JNIMethod_getNativeString
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
第三步
在项目根目录下新建一个文件夹jni用于存放JNI相关的东西,将上一步生成的.h文件移到jni目录下,并新建JNIMethod.c文件用于实现头文件中的方法,JNIMethod.c如下:
#include <string.h>
#include <jni.h>
jstring Java_com_demon_myfirstjni_JNIMethod_getNativeString(JNIEnv *env, jobject thiz)
{
callJavaMethod(env, thiz);
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
第四步
以上已经准备好头文件和源文件,此时就需要准备编译用到的mk文件,在jni目录下分别新建Android.mk和Application.mk两个文件。
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JNIMethod
LOCAL_SRC_FILES := JNIMethod.c
include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH := $(call my-dir)设置Android.mk路径,include $(CLEAR_VARS)用于清理清理很多LOCAL_xxx.例如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES等等。但不清理LOCAL_PATH。LOCALL_MODULE用于设置模块名称,LOCAL_SRC_FILES用于设置源文件。include $(BUILD_SHARED_LIBRARY) 表示编译成动态库。
Application.mk
APP_ABI := all
表示编译出所有的平台so文件,如:armeabi,x86,x86_64,armeabi-v7a等。
第四步
编译所需文件都准备好后,切换到jni目录执行ndk-build命令。
D:\code\MyFirstJni\app\src\main\java\jni>ndk-build
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-14.
[arm64-v8a] Compile : JNIMethod <= JNIMethod.c
[arm64-v8a] SharedLibrary : libJNIMethod.so
[arm64-v8a] Install : libJNIMethod.so => libs/arm64-v8a/libJNIMethod.so
[x86_64] Compile : JNIMethod <= JNIMethod.c
[x86_64] SharedLibrary : libJNIMethod.so
[x86_64] Install : libJNIMethod.so => libs/x86_64/libJNIMethod.so
[armeabi-v7a] Compile thumb : JNIMethod <= JNIMethod.c
[armeabi-v7a] SharedLibrary : libJNIMethod.so
[armeabi-v7a] Install : libJNIMethod.so => libs/armeabi-v7a/libJNIMethod.so
[x86] Compile : JNIMethod <= JNIMethod.c
[x86] SharedLibrary : libJNIMethod.so
[x86] Install : libJNIMethod.so => libs/x86/libJNIMethod.so
正确执行后会在jni同级目录下生成libs和obj两个目录,libs下面就是生成的各个平台的so库文件,将libs下面的内容复制到Android工程的libs目录下并在项目的build.gradle的android下添加
sourceSets {
main() {
jniLibs.srcDirs = ["libs"]
}
}
让项目依赖上so库,完成后运行项目查看运行结果。
从结果中看获取到了JNI方法中返回的字符串。
第五步
上面介绍了java调用JNI的方法,下面接着讲讲JNI调用java的流程,这里介绍调用静态方法,调用非静态方法的不同点就在于多一步构造对象的过程。首先在前面的JNIMethod类中添加一个静态方法,
public class JNIMethod
{
static {
System.loadLibrary("JNIMethod");
}
public static native String getNativeString();
public static void getStringFromJava(String str)
{
Log.d("demon", "msg:"+str);
}
}
然后在JNIMethod.c文件中实现调用静态方法,
#include <string.h>
#include <jni.h>
#include <stdio.h>
void callJavaMethod(JNIEnv *env, jobject thiz)
{
jclass clazz = (*env)->FindClass(env, "com/demon/myfirstjni/JNIMethod");
if(clazz == NULL)
{
printf("find class MainActivity error!");
return;
}
jmethodID id = (*env)->GetStaticMethodID(env, clazz, "getStringFromJava", "(Ljava/lang/String;)V");
if(id == NULL)
{
printf("find method getStringFromJava error!");
return;
}
jstring msg = (*env)->NewStringUTF(env, "msg send by callJavaMethod in JNIMethod.c.");
(*env)->CallStaticVoidMethod(env, clazz, id, msg);
}
jstring Java_com_demon_myfirstjni_JNIMethod_getNativeString(JNIEnv *env, jobject thiz)
{
callJavaMethod(env, thiz);
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
从callJavaMethod可以看出,流程是通过com/demon/myfirstjni/JNIMethod找到类,然后通过getStringFromJava找到方法,(Ljava/lang/String;)V是该方法的签名,最后通过JNIEnv对象的CallStaticVoidMethod来完成调用。
java通过调用Java_com_demon_myfirstjni_JNIMethod_getNativeString方法时,JNI先调用java的静态方法然后返回JNI方法的调用结果。最后执行ndk-build编译出新的so库,替换掉之前的so库运行项目。点击按钮查看JNI调用java静态方法的情况。
08-06 16:48:59.553 4760-4760/com.demon.myfirstjni D/demon: msg:msg send by callJavaMethod in JNIMethod.c.
从结果中看成功调用了getStringFromJava方法打印出了JNI中设置文字的log。
参考文章: