JNI 简单入门

概述

本文主要介绍JNI使用在Android开发中的简单入门

环境搭建

NDK的开发是基于JNI的,下面是在Android Studio中配置NDK,按照图中的顺序依次下去,选择NDK开始下载

实例演示

native方法声明

创建一个Android项目,在MainActivity.java里声明native方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static {
System.loadLibrary("jni-test");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.textView);
textView.setText(get());
set("Hello world from JniTestApp!");
TextView textView2 = (TextView) findViewById(R.id.textView2);
textView2.setText(get2());
}
public static void methodCalledByJni(String msgFromJni) {
Log.d("test", "MainActivity methodCalledByJni msg: " + msgFromJni);
}
public native String get();
public native String get2();
public native void set(String str);

实现native方法

在应用模块目录下创建jni目录,创建4个文件:test.cpptest2.cppAndroid.mkApplication.mk

  1. test.cpp实现MainActivity.java的getset方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // test.cpp
    #include <stdio.h>
    #include <jni.h>
    #ifdef __cplusplus
    extern "C" {
    #endif
    jstring Java_com_jeff_customjnitest_MainActivity_get(JNIEnv *env, jobject thiz) {
    printf("invoke get in c++\n");
    return env->NewStringUTF("Hello from JNI in libjni-test.so!");
    }
    void Java_com_jeff_customjnitest_MainActivity_set(JNIEnv *env, jobject thiz, jstring string) {
    printf("invoke set from c++\n");
    char *str = (char*) env->GetStringUTFChars(string, NULL);
    printf("%s\n", str);
    env->ReleaseStringUTFChars(string, str);
    }
    #ifdef __cplusplus
    }
    #endif
  2. test2.cpp实现MainActivity.java的get2()方法,此方法是用于在JNI中调用Java方法,即先通过类名找到类,再根据方法名找到方法的ID,最后调用此方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // test2.cpp
    #include <stdio.h>
    #include <jni.h>
    #ifdef __cplusplus
    extern "C" {
    #endif
    void callJavaMethod(JNIEnv *env, jobject thiz) {
    jclass clazz = env->FindClass("com/jeff/customjnitest/MainActivity");
    if (clazz == NULL) {
    printf("find class MainActivity error!");
    return;
    }
    jmethodID id = env->GetStaticMethodID(clazz, "methodCalledByJni", "(Ljava/lang/String;)V");
    if (NULL == id)
    printf("find method methodCalledByJni error!");
    jstring msg = env->NewStringUTF("msg send by calledJavaMethod in test2.cpp");
    env->CallStaticVoidMethod(clazz, id, msg);
    }
    jstring Java_com_jeff_customjnitest_MainActivity_get2(JNIEnv *env, jobject thiz) {
    printf("invoke get in c++\n");
    callJavaMethod(env, thiz);
    return env->NewStringUTF("Hello from JNI in libjni-test.so!=====");
    }
    #ifdef __cplusplus
    }
    #endif
  3. Android.mk中主要描述编译时的相关配置项,描述如下

    • LOCAL_MODULE:模块的名称
    • LOCAL_SRC_FILES:参与编译的源文件,有多个时使用\换行符隔开
      1
      2
      3
      4
      5
      6
      7
      8
      9
      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      LOCAL_MODULE := jni-test
      LOCAL_SRC_FILES := test.cpp \
      test2.cpp
      include $(BUILD_SHARED_LIBRARY)
  4. Applicaiton.mk表示CPU的架构平台类型,这里使用armeabix86架构,多个架构平台使用,隔开

    1
    APP_ABI := armeabi,x86

编译生成so库

进入jni目录,使用ndk-build命令编译生成so库,如下图所示,so库会存放在jni目录的对应不同架构的目录下

运行Android程序

直接运行Android应用,会发现程序无法正常运行,提示java.lang.UnstatisfiedLinkError错误,即找不到so库文件,需要制定so库所在的目录(在build.gradle中添加指定jniLibs的目录)

1
2
3
4
5
6
7
8
9
android {
...
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}

Note:AndroidStudio中最好指定需要打包的so库到APK中,即需要在build.gradle中添加指定abiFilters内容

1
2
3
4
5
6
7
8
9
android {
defaultConfig {
...
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
}
}
}

参考方案

  1. Android开发艺术探索第十四章–JNI和NDK编程
  2. java.lang.UnsatisfiedLinkError 解决方法
  3. DEMO参考 (下载地址)