初识JNI与NDK

JNI

Java JNI的本意是Java Native Interface(Java本地接口),它是为了方便Java调用C/C++等本地代码所封装的一层接口。

NDK

NDK是Android所提供的一个工具集合,通过NDK可以在Android中更加方便地通过JNI来访问本地代码。NDK还提供了交叉编译器,开发人员只需简单修改mk文件就可以生成特定CPU平台的动态库。

windows环境下,通过AndroidStudio实现JNI调用实例

1.首先需要下载NDK,可以手动下载,也可以通过AndroidStudio自动下载。

无论如何下载,需要在AndroidStudio中配置好NDK地址。


NDK.png
2.建立一个带native的类
public class MyJni {
    
    public native String get();
    public native void set(String str);
}
3.通过Javac,创建class文件

指令如下:javac 被编译文件位置
javac E:\QiangPiao\app\src\main\java\com\king\qiangpiao\jni\MyJni.java

4.通过Javah,创建.h头文件

首先在app/src/main的目录下创建jni文件夹,与java同级。
指令如下:javah -d 头文件存放位置 -classpath 被编译.class文件位置
javah -d E:\QiangPiao\app\src\main\jni -classpath E:\QiangPiao\app\src\main\java\ com.king.qiangpiao.jni.MyJni

5.在Jni目录下创建c/c++文件,实现Java文件中native的具体方法
#include"com_king_qiangpiao_jni_MyJni.h"
#include<stdio.h>
JNIEXPORT jstring JNICALL Java_com_king_qiangpiao_jni_MyJni_get(JNIEnv *env, jobject thiz){
    printf("invoke get from C++\n");
    return (env)->NewStringUTF("Hello from JNI !");
}

JNIEXPORT void JNICALL Java_com_king_qiangpiao_jni_MyJni_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);
}
6.在Jni目录下编写Android.mk和Application.mk文件

Android.mk
LOCAL_MODULE :=编译出来名字
LOCAL_SRC_FILES :=被编译的文件

LOCAL_PATH       :=  $(call my-dir)
include              $(CLEAR_VARS)
LOCAL_MODULE     :=  my_jni
LOCAL_SRC_FILES  :=  test.cpp
include              $(BUILD_SHARED_LIBRARY)

Application.mk
编译产生so库的cpu平台,有armeabi , x86 , mips

APP_ABI := armeabi
7.生成so库

进入ndk-build.cmd的目录,就是第一步配置NDK的位置
指令如下:ndk-build -C jni目录地址
ndk-build -C E:\QiangPiao\app\src\main\jni
工程中会多一个app/src/main/libs其中对应平台有对应so库。

8.在Android的使用
public class MainActivity extends AppCompatActivity {

    private Button btn;
    private static MyJni jniTest;
    static{
        try {
//Android.mk文件中的LOCAL_MODULE属性指定的值相同
            System.loadLibrary("my_jni");
            jniTest = new MyJni();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = (Button) findViewById(R.id.jni_btn);
        mTextView = (TextView) findViewById(R.id.textView);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTextView.setText(jniTest.get());
                jniTest.set("hello world from JniTestApp");
            }
        });
    }
}

在app中的build.gradle中添加

  sourceSets {
        main {
            jni.srcDirs = []
            jniLibs.srcDirs = ['src/main/libs']
        }
    }

提示

在gradle.properties中加上下面配置

android.useDeprecatedNdk=true
android.deprecatedNdkCompileLease=1515317190556
按这个流程下来,可以实现Android项目中通过jni调用c/c++文件。深入的理解和应用需要具体深入学习。

注册JNI函数的两种方式

静态注册

就是上面的整个流程

  • 先创建Java类,声明Native方法,编译成.class文件。
  • 使用Javah命令生成C/C++的头文件,使用Javah命令生成一个以.h为后缀的头文件。
  • 创建.h对应的源文件,然后实现对应的native方法
动态注册

我们知道Java Native函数和JNI函数时一一对应的,JNI中就有一个叫JNINativeMethod的结构体来保存这个对应关系,实现动态注册方就需要用到这个结构体。举个例子,你就一下子明白了:

声明native方法还是一样的:

public class JavaHello {
    public static native String hello();
}

创建jni目录,然后在该目录创建hello.c文件,如下:

//
// Created by DevilWwj on 16/8/28.
//
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>

/**
 * 定义native方法
 */
JNIEXPORT jstring JNICALL native_hello(JNIEnv *env, jclass clazz)
{
    printf("hello in c native code./n");
    return (*env)->NewStringUTF(env, "hello world returned.");
}

// 指定要注册的类
#define JNIREG_CLASS "com/devilwwj/library/JavaHello"

// 定义一个JNINativeMethod数组,其中的成员就是Java代码中对应的native方法
static JNINativeMethod gMethods[] = {
    { "hello", "()Ljava/lang/String;", (void*)native_hello},
};


static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

/***
 * 注册native方法
 */
static int registerNatives(JNIEnv* env) {
    if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

/**
 * 如果要实现动态注册,这个方法一定要实现
 * 动态注册工作在这里进行
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;

    if ((*vm)-> GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);

    if (!registerNatives(env)) { //注册
        return -1;
    }
    result = JNI_VERSION_1_4;

    return result;

}

先仔细看一下上面的代码,看起来好像多了一些代码,稍微解释下,如果要实现动态注册就必须实现JNI_OnLoad方法,这个是JNI的一个入口函数,我们在Java层通过System.loadLibrary加载完动态库后,紧接着就会去查找一个叫JNI_OnLoad的方法。如果有,就会调用它,而动态注册的工作就是在这里完成的。在这里我们会去拿到JNI中一个很重要的结构体JNIEnv,env指向的就是这个结构体,通过env指针可以找到指定类名的类,并且调用JNIEnv的RegisterNatives方法来完成注册native方法和JNI函数的对应关系。

我们在上面看到声明了一个JNINativeMethod数组,这个数组就是用来定义我们在Java代码中声明的native方法,我们可以在jni.h文件中查看这个结构体的声明:

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

结构体成员变量分别对应的是Java中的native方法的名字,如本文的hello;Java函数的签名信息、JNI层对应函数的函数指针。

以上就是动态注册JNI函数的方法,上面只是一个简单的例子,如果你还想再实现一个native方法,只需要在JNINativeMethod数组中添加一个元素,然后实现对应的JNI层函数即可,下次我们加载动态库时就会动态的将你声明的方法注册到JNI环境中,而不需要你做其他任何操作。

两种注册方式来自https://blog.csdn.net/wwj_748/article/details/52347341

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

推荐阅读更多精彩内容