JNI的使用与So库的生成

一、前言

JNI是Java Native Interface的缩写,它的主要作用是提供了若干API来实现Java和其他语言的通信(主要是C和C++)。

NDK是一系列工具的集合,它可以帮助开发者快速开发C(或者C++)的动态库(也称So库),并So库和Java应用一起打包。
JNI的使用就是需要将C(或者C++)代码编译成动态库供Java方法调用。

本文的内容是基于Android Studio的,其他使用命令行或者其他工具生成So库的方法请另行查找。

二、生成So库的步骤(方法一)

1、下载并配置NDK

首先在Android Studio中的Preferences找到Android SDK,并在SDK Tools这一栏中找到NDK,然后下载。如下图所示:


下载NDK

下载完NDK后,去local.properties文件中查看是否定义了ndk的存储路径,如下图所示:


定义ndk的存储路径

然后到gradle.properties文件中添加android.useDeprecateNdk=true,表示我们的应用需要使用NDK,如下图所示:

gradle.properties文件的配置

最后在app/build.gradle中配置ndk指定So库的名字为jni-test(如果不指定则生成的So库的默认名字为app),如下图所示:


build.gradle中指定So库的名字

2、编写Java调用类

如下图所示,我们定义了一个Java调用类。如下图所示:


Java调用类

Java调用类的说明:
1、静态代码块表明了该Java类需要加载哪一个So库,例子中的jni-test是So库的名称。(注意:实际上NDK生成的So库的名称为libjni-test.so,但是So库的名称就是jni-test)
2、native关键字声明了get()和set()两个方法,这两个方法需要在C(或者C++)中实现。

3、生成.h头文件,编写C(或者C++)代码

首先我们执行一下Android项目的build命令,在app目录的build文件夹下,路径为intermediates/classes/debug(release)/xx/xx/目标.class。


生成class文件

然后我们通过命令行进入到app/build/intermediates/classes/debug目录下,并输入命令javah -jni com.example.runningh.mydemo.TestJNI生成.h头文件
如下图所示:

生成头文件的命令

生成了头文件

这里需要注意的是,我们不能直接进入到TestJNI.class所在的文件夹,然后使用javah -jni TestJNI命令生成头文件,这样会报找不到类文件的错误。我们需要进入的是包名文件夹的上一层,上面的例子就是debug文件,然后通过javah -jni com.example.runningh.mydemo.TestJNI这样的形式来生成头文件。

接着我们在app/src/main目录下建立名为jni的文件夹(注意:文件夹的名字只能为jni,其他名字ndk是识别不了的),并将上面生成的头文件拷贝到jni文件夹中,然后我们在jni文件夹中新建一个C++文件,名字随便命名,C++文件内容如下所示:

#include <jni.h>
#include "com_example_runningh_mydemo_TestJNI.h"
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL Java_com_example_runningh_mydemo_TestJNI_get(JNIEnv *env, jobject thiz) {
    printf("invoke get in c++\n");
    return env->NewStringUTF("Hello from JNI int libjni-test.so!");
}

JNIEXPORT void JNICALL Java_com_example_runningh_mydemo_TestJNI_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

该C++文件包含了上面的头文件,并对Java文件定义的get和set方法进行了编写。
头文件和C++文件的目录如下图所示:


jni目录

4、生成So库

再次使用Android项目的build命令,此时会发生错误,原因是在gradle3.0以上,android.useDeprecatedNdk=true这种方法不再支持了。但是它还是给出了两种解决方法:
1、在gradle.properties中使用android.deprecatedNdkCompileLease=1526577754228替换android.useDeprecatedNdk=true,这样可以继续使用60天。
2、使用CMake工具或者ndk-build工具。

我们先讲第一种方法,在gradle.properties中使用android.deprecatedNdkCompileLease=1526577754228,然后重新build,可以看到在build/intermediates/ndk/debug/lib下生成了不同的CPU版本So库。

然后我们将需要用到的CPU版本对应的So库复制到Android项目中,即在app/src/main文件夹下新建jniLibs文件夹,将我们的So库复制到jniLibs文件夹中(注意:Android Studio只能识别jniLibs文件夹中So库,如果需要改变So库的识别路径,可以在app/build.gradle中指定jniLibs.srcDir,如下所示:

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.runningh.mydemo"
        minSdkVersion 16
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk {
            moduleName "jni-test"
        }
    }

    sourceSets.main {
        jniLibs.srcDir 'src/main/jni_libs'
    }
}

上面就指定了So库的识别路径为src/main/jni_libs目录。

5、运行Android项目,调用本地方法

我们在MainActivity中调用了TestJNI对象的test()方法,而上面提到的TestJNI对象又调用了本地方法get()和set(string),最终我们成功的将本地方法调起。

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new TestJNI().test();
    }
}

三、生成So库的步骤(方法二)

1、下载NDK、CMake和LLDB并配置相关信息

首先在Android Studio中的Preferences找到Android SDK,并在SDK Tools这一栏中找到NDK、CMake和LLDB,然后下载。如下图所示:


下载NDK、CMake和LLDB

下载完NDK后,去local.properties文件中查看是否定义了ndk的存储路径,该步骤和方法一是一致的。

然后在app/build.gradle中配置使用CMake工具以及CMake配置文件的路径(注意:这里是相对路径,图中的CMakeLists.txt文件在Android项目的app目录下)

最后在我们看一下CMakeLists.txt文件的内容:

#cmake最小版本
cmake_minimum_required(VERSION 3.4.1)

add_library( # 设置so文件名称.
             jni-test
             # 设置这个so文件为共享.
             SHARED
             # 设置 c文件源码位置.
             src/main/jni/test.cpp )

我们只需要设置一下要生成的So库的名称,C(或者C++)文件的位置,So库名称、设置So库为共享、设置C文件源码位置之间需要用空格隔开)

2、编写Java调用类

和方法一的步骤一致。

3、生成.h头文件,编写C(或者C++)代码

和方法一的步骤一致。

4、生成So库

调用build命令编译Android项目,可以在build/intermediates/cmake/debug(release)/obj生成不同的CPU版本So库。如下图所示:


然后我们将需要用到的CPU版本对应的So库复制到Android项目中,步骤和方法一的一致。

5、运行Android项目,调用本地方法

和方法一的步骤一致。

四、总结

我们可以看到JNI的使用最主要是要生成所需的So库,而So库的生成有两种不同的方法,第一是使用NDK工具,不过这方法已经即将废弃;第二是使用CMake工具,官方建议我们使用第二种方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容