美文网首页
NDK JNI开发 之 Eclipse中静态注册(二)

NDK JNI开发 之 Eclipse中静态注册(二)

作者: Sharkchilli | 来源:发表于2020-06-07 23:26 被阅读0次

JNI技术概述

JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。它允许Java代码和其他语言写的代码进行交互。
其实就是java调用本地语言的技术,对于Linux和Windows而言本地语言自然是C和C++。换句话说就是java如何调用C或C++代码,而你要把C或C++代码给他人调用自然要编写成动态链接库了,在Windows是dll,在Linux是so。(这里我们讨论Android上的so库编写和调用)

这个技术当初在用友工作时就接触到了,由于当时技术的局限性,还被卡了一个星期。导致同事在外地待了一个星期。

开发环境

Eclipse+adt
android-ndk-r20(ndk的开发基础在上一章介绍:https://www.jianshu.com/p/c546783ad284

静态注册

要对java中native关键字定义的方法进行注册, 注册方式有两种: 静态注册和动态注册, 这里我们先介绍静态注册

Android代码

我们在Android中先写一个简单的点击事件,并在static中加载so库,在按钮点击时调用so库中的函数

package com.example.jnitest;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {
    
    static {
        //加载libhello_jni.so(默认去项目目录下的libs下查找)
        System.loadLibrary("hello_jni");
    }

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


    // 测试JNI
    public void testJni(View view) {
        String jniString = HelloNdk();
        Toast.makeText(MainActivity.this, jniString, Toast.LENGTH_LONG).show();
    }
    
    //定义一个native方法 native关键字说明方法的实现在so里面
    public native String  HelloNdk();
}

界面如下:


image.png

SO库编写

静态注册 在c代码中我们的函数名要遵守一定的约定,不然jni无法识别函数,约定如下:

  • (1)函数名:JNIEXPORT + 返回类型 + JNICALL Java_+包名 +类型 + 函数名(java 中声明的),以下划线连接
  • (2)返回值类型,是 jni 中的数据类型,若没有返回类型,则使用void
  • (3)默认传入两个参数 JNIEnv* env(jvm运行环境), jobject obj(调用这个函数的Java对象)

例如
java中函数声明:public native String HelloNdk();
c中函数的实现:JNIEXPORT jstring JNICALL Java_com_example_jnitest_MainActivity_HelloNdk (JNIEnv * env, jobject obj)

这里你可以手动编写,但是java提供了javah来自动从java代码生成对应的头文件。

  • 使用cmd进入项目的根目录,在项目的根目录创建jni目录
javah -classpath src -d jni com.example.jnitest.MainActivity
  • classpath 表示类文件路径
  • src表示java代码的文件夹
  • d表示生成头文件所在路径
  • jni 表示 jni 文件夹下
  • com.example.jnitest.MainActivity,表示需要编译的类文件,也就是包名+类名(也就是包含 native 方法的类文件)

生成文件内容:com_example_jnitest_MainActivity.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnitest_MainActivity */

#ifndef _Included_com_example_jnitest_MainActivity
#define _Included_com_example_jnitest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jnitest_MainActivity
 * Method:    HelloNdk
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jnitest_MainActivity_HelloNdk
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

这里我们只做c的编写所以去除c++的一些东西,在jni目录下创建hello_jni.c,并拷贝代码修改为以下:

#include <jni.h>
#include <string.h>

/* Header for class com_example_jnitest_MainActivity */

/*
 * Class:     com_example_jnitest_MainActivity
 * Method:    HelloNdk
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jnitest_MainActivity_HelloNdk
  (JNIEnv * env, jobject obj){
    return (*env)->NewStringUTF(env, "Hello JNI!");
}

在jni.h中我们看到如下定义(在%java_home%\include\jni.h):

#include "jni_md.h"

typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;

再进入jni_md.h中看看(在%java_home%\include\win32\jni_md.h):

#define JNIEXPORT __declspec(dllexport)
#define JNIIMPORT __declspec(dllimport)
#define JNICALL __stdcall

所以我们直接就不用解释JNIEXPORT 和 JNICALL 了
现在来讨论一下jstring 返回值。

java和c是如何互通的?

其实不能互通的原因主要是数据类型的问题,jni解决了这个问题,例如那个c文件中的jstring数据类型就是java传入的String对象,经过jni函数的转化就能成为c的char
对应数据类型关系如下表:

image.png
image.png
这里我们使用(
env)->NewStringUTF(env, "Hello JNI!");生成了一个jstring,并返回它。

使用ndk编译so库

  • 在jni目录编写实现函数对应的Android.mk(该文件的描述在上一章中)
LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)

LOCAL_MODULE    := hello_jni
LOCAL_SRC_FILES := hello_jni.c
     
include $(BUILD_SHARED_LIBRARY)

返回到项目根目录运行ndk

ndk-build

在项目根目录下会生成libs文件夹里面有所有平台的so库(这里你也可以指定平台)


image.png

由于我的虚拟机是X86的所有它使用的是x86文件夹下的so

运行

现在所有准备工作做完我们部署项目到手机上,点击按钮后如下:


image.png

尾言

这里我们只对JNI的类型对应做了一些基本的说明,由于此文的专注点是如何静态注册jni函数所以这里不做过多的描述,将来我将用一文详细说明jni的一些技术细节。下一章我们将对动态注册进行说明。

相关文章

网友评论

      本文标题:NDK JNI开发 之 Eclipse中静态注册(二)

      本文链接:https://www.haomeiwen.com/subject/ojwhtktx.html