文档章节

android app中使用JNI

WolfCS
 WolfCS
发布于 2013/03/02 16:45
字数 1997
阅读 4139
收藏 12

JNI提供了一种机制,使得在Java 代码中可以使用 C/C++ 的本地层代码,这种使用主要是指在 Java 代码中调用 C/C++ 代码。这种机制为我们开启了一扇门,一扇将Java 代码与广阔的 C/C++ 本地层连接起来的门。

基于 android-ndk-r8d 提供的sample 程序——android-ndk-r8d/samples/hello-jni 的代码,我们来看一下,如何在 android 应用开发中使用 JNI。

首先来看在 Java 代码中需要做些什么事情:

package com.example.hellojni;

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;

public class HelloJni extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        /* Create a TextView and set its content.
         * the text is retrieved by calling a native
         * function.
         */
        TextView  tv = new TextView(this);
        tv.setText( stringFromJNI() );
        setContentView(tv);
    }

    /* A native method that is implemented by the
     * 'hello-jni' native library, which is packaged
     * with this application.
     */
    public native String  stringFromJNI();

    /* This is another native method declaration that is *not*
     * implemented by 'hello-jni'. This is simply to show that
     * you can declare as many native methods in your Java code
     * as you want, their implementation is searched in the
     * currently loaded native libraries only the first time
     * you call them.
     *
     * Trying to call this function will result in a
     * java.lang.UnsatisfiedLinkError exception !
     */
    public native String  unimplementedStringFromJNI();

    /* this is used to load the 'hello-jni' library on application
     * startup. The library has already been unpacked into
     * /data/data/com.example.hellojni/lib/libhello-jni.so at
     * installation time by the package manager.
     */
    static {
        System.loadLibrary("hello-jni");
    }
}

上面那些带 native 修饰符的方法,就是在 Java 代码中调用,实现却在本地层的方法。native 修饰符告诉Java 编译器,暂时不需要去链接这些方法。

那Java的机制到底要如何去调用到那个在本地层实现的Java method的呢?可以看到最后的那个static的block,里面有调用到System.loadLibrary(),这个动作完成的事情,其实就是把实现了java方法的shared library加载进来。java的机制会在这个shared library中找到那个java方法的实现。

可是,JVM是怎么样知道java方法和本底层函数的对应关系的呢?或者说,在Java Code中,调用了那个native修饰的家伙,要如何去找到它的实现呢?C/C++中可没有java的string那种东西。建立Java方法到native方法的映射,也就是告诉Java层,它所调用的那些native方法在本底层对应于哪个实际的C/C++方法,有两种方法。一种是通过给C/C++的函数按照某个特定的规则来命名实现的。比如前面的那个public native String  stringFromJNI()方法,Java的机制是会知道它的实现应该是在C/C++中的Java_com_example_hellojni_HelloJni_stringFromJNI() 这个函数的。看看这个函数名,是有多么的冗长啊,这个函数名中要包含对应的Java 方法的package name、class name及method name的信息嘛。可以看到这个方法名的构成,Java开头,然后是下划线,紧接着是下划线分割的package name,然后跟着的是下划线和class name,最后是下划线和方法名。

建立Java方法到native方法的映射还有另外的一种让人感觉更为舒服的方法,那就是JNI_OnLoad机制。一个shared library,如果他里面有一个JNI_OnLoad(JavaVM* vm, void*)函数,那么当这个shared library被load起来的时候,它首先就会被调用一次。然后在这个JNI_OnLoad()函数中,就可以通过JNI提供的一些函数,来显式地注册Java 方法到native方法的映射关系了。我们修改ndk的sample code,来使用JNI_OnLoad这种机制(同时我们想要加一个限制,那就是只能基于"jni.h"提供的方法来实现,尽管在libandroid_android.so中已经存在一些非常好用的设施了,以使得我们的JNI code可以用ndk提供的设施轻松的build过)。如下是前面那个 stringFromJNI()的实现代码:

 

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

#include <android/log.h>

#include "JniDebug.h"
#include "JniHelper.h"


/* This is a trivial JNI example where we use a native method
 * to return a new VM String. See the corresponding Java source
 * file located at:
 *
 *   apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java
 */

//extern "C" {
//    JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv * env, jobject thiz );
//};
//
//JNIEXPORT jstring
//JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
//                                                  jobject thiz )
//{
//    const char *str = "Hello from JNI !";
//    JniDebug(str);
//    return env->NewStringUTF(str);
//}

static jstring HelloJni_stringFromJNI( JNIEnv* env, jobject thiz )
{
    JniDebug("from HelloJni_stringFromJNI");
    const char *str = "Hello from JNI !";
    JniDebug(str);
    return env->NewStringUTF(str);
}

static JNINativeMethod gMethods[] = {
        NATIVE_METHOD(HelloJni, stringFromJNI, "()Ljava/lang/String;"),
};

void register_com_example_hellojni(JNIEnv* env) {
    jniRegisterNativeMethods(env, "com/example/hellojni/HelloJni", gMethods, NELEM(gMethods));
}

int JNI_OnLoad(JavaVM* vm, void*) {
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        abort();
    }
    register_com_example_hellojni(env);
    JniDebug("JNI_OnLoad in hello-jni");
    return JNI_VERSION_1_6;
}

来看这段code,就建立Java方法到native方法的映射部分而言,主要做了两件事情:

  1. 建立了一个表,这个表描述了Java方法和native方法的映射关系。这个表中会包含Java层中这个方法的名称,native层中相同方法的名称,参数的类型及返回值的类型等信息。对应于上面那段code中定义的gMethods这个数组。怎么没有包含这个方法所在的java class的名称信息呢?Java class name的信息当然是不可或缺的。且看这个部分完成第二件事情。
  2. 调用JNI的方法把第一步中建立的那个表注册进系统,对应于上面那个对于jniRegisterNativeMethods()函数的调用。这个地方实际上是需要提供Java class name信息的啦。

实际上在此处,不管是上面的第一步,还是第二步,我们都没有直接使用"jni.h"中提供的那些函数或结构。而是参照android系统中libcore/luni/src/main/native/下面的那些实现,定义了一个宏来更加方便的建立Java 方法到native方法的映射表。同时参照android系统中libnativehelper的实现,对"jni.h"中直接提供的那些方法进行了一点点的增强。接下来来看这个部分的Code。首先是头文件JniHelper.h:

 

#include "jni.h"

#ifdef __cplusplus
extern "C" {
#endif


/*
 * Register one or more native methods with a particular class.
 * "className" looks like "java/lang/String". Aborts on failure.
 * TODO: fix all callers and change the return type to void.
 */
int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);

#ifdef __cplusplus
}
#endif

/*
 * For C++ code, we provide inlines that map to the C functions.  g++ always
 * inlines these, even on non-optimized builds.
 */
#if defined(__cplusplus)
inline int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) {
    return jniRegisterNativeMethods(&env->functions, className, gMethods, numMethods);
}
#endif

#define NATIVE_METHOD(className, functionName, signature) \
    { #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) }


# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))

接下来是JniHelper.cpp 文件:

 

#include "JniDebug.h"
#include "JniHelper.h"

#include <stdlib.h>

/**
 * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.)
 */
template<typename T>
class scoped_local_ref {
public:
    scoped_local_ref(C_JNIEnv* env, T localRef = NULL)
    : mEnv(env), mLocalRef(localRef)
    {
    }

    ~scoped_local_ref() {
        reset();
    }

    void reset(T localRef = NULL) {
        if (mLocalRef != NULL) {
            (*mEnv)->DeleteLocalRef(reinterpret_cast<JNIEnv*>(mEnv), mLocalRef);
            mLocalRef = localRef;
        }
    }

    T get() const {
        return mLocalRef;
    }

private:
    C_JNIEnv* mEnv;
    T mLocalRef;

    // Disallow copy and assignment.
    scoped_local_ref(const scoped_local_ref&);
    void operator=(const scoped_local_ref&);
};

static jclass findClass(C_JNIEnv* env, const char* className) {
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
    return (*env)->FindClass(e, className);
}

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    JniDebug("Registering %s natives", className);

    scoped_local_ref<jclass> c(env, findClass(env, className));
    if (c.get() == NULL) {
        JniDebug("Native registration unable to find class '%s', aborting", className);
        abort();
    }

    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
        JniDebug("RegisterNatives failed for '%s', aborting", className);
        abort();
    }

    return 0;
}

在Java 方法的native实现的那个文件中,我们看到有一些打log的函数。这个地方也顺便看一下在做NDK开发时打log的方法,首先是头文件:

 

void JniDebug(const char format[], ...);

这个头文件,没什么可说的。接着来看这个打log的函数的实现:

 

#include "stdio.h"

static const size_t kBufferSize = 256;

#define LOG_TAG "hello-jni"
#include <android/log.h>

#include "JniDebug.h"

void JniDebug(const char format[], ...) {
    va_list args;
    va_start(args, format);
    __android_log_vprint(ANDROID_LOG_DEBUG, LOG_TAG, format, args);
    va_end(args);
}

看上去与我们平常用的那些printf函数也真心没有太大的区别。要打log,自然不能忘了要在jni的Android.mk文件中,建立对于liblog的依赖。像下面这样:

 

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

ANDROID_SOURCE_TOP=$(HOME)/android/Data/android_src/JellyBean/

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.cpp \
                   JniHelper.cpp\
                   JniDebug.cpp

LOCAL_LDLIBS    := -llog

include $(BUILD_SHARED_LIBRARY)

接下来再来看一看我们上面的那个hello-jni程序运行的结果。它打出来的log:

 

03-02 08:44:23.362: D/hello-jni(922): Registering com/example/hellojni/HelloJni natives
03-02 08:44:23.362: D/hello-jni(922): JNI_OnLoad in hello-jni
03-02 08:44:23.512: D/hello-jni(922): from HelloJni_stringFromJNI
03-02 08:44:23.512: D/hello-jni(922): Hello from JNI !

结束。

 

© 著作权归作者所有

上一篇: ICU Layout Engine
下一篇: JNI Tips
WolfCS
粉丝 81
博文 147
码字总数 505184
作品 4
杭州
高级程序员
私信 提问
加载中

评论(1)

穿山乙
穿山乙
唉,看不懂
Android—JNI调用简单实例解析

转自:http://www.cnblogs.com/sevenyuan/p/4202759.html 感谢原作者的细心整理! 1. 在Eclipse中创建项目:TestJNI 2. 新创建一个class:TestJNI.java package com.wwj.jni; public class ......

80后小子
2015/11/12
0
0
解决JNI在Windows环境下因长路径导致编译失败问题

之前听一个朋友反馈LuaScriptoCore在Windows下编译会报错,今天特意跑到Windows环境下测试了一番,果然是存在问题。得到了下面的编译报错信息: Build command failed. Error while executin...

杰嗒嗒的阿杰
2018/05/04
0
0
初识 Android Native Development Kit (NDK)

Android开发者社区对JNI的呼声一直很高,这次Google发布NDK可以说是顺应民意。NDK和SDK一样提供了Linux、Windows、Mac三大开发平台的版本。 在Linux上,尤其是Debian/Ubuntu上进行安装,非常...

红薯
2009/07/03
15.1K
4
Android JNI MAC OS环境配置

Android JNI MAC OS环境配置 http://whbzju.github.io/blog/2013/06/01/android-jni-config/ JUN 1ST, 2013 | COMMENTS 前言—JNI技术简介 JNI是Java Native Interface的缩写,即“Java本地调......

whb_zju
2013/06/02
0
0
Android JNI知识简介

Java Native Interface (JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 Java 代码能够与用其它编程语...

长平狐
2013/12/25
73
0

没有更多内容

加载失败,请刷新页面

加载更多

八、RabbitMQ的集群原理

集群架构 写在前面 RabbitMQ集群是按照低延迟环境设计的,千万不要跨越WAN或者互联网来搭建RabbitMQ集群。如果一定要在高延迟环境下使用RabbitMQ集群,可以参考使用Shovel和Federation工具。...

XuePeng77
今天
1
0
mac系统下,brew 安装mysql,用终端可以连接,navicat却连接不上?

问题: 1.报错? 2059 - Authentication plugin 'caching_sha2_password' cannot be loaded: dlopen(../Frameworks/caching_sha2_password.so, 2): image not found 2.自己通过设置,已经把密......

写bug的攻城狮
昨天
2
0
老生常谈,HashMap的死循环

问题 最近的几次面试中,我都问了是否了解HashMap在并发使用时可能发生死循环,导致cpu100%,结果让我很意外,都表示不知道有这样的问题,让我意外的是面试者的工作年限都不短。 由于HashMap...

群星纪元
昨天
5
0
拉普拉斯算子

拉普拉斯算子是二阶微分算子。 我们知道,一维离散信号一阶微分公式如下: 相应的,一维离散信号二阶微分公式如下: 由于图像有x和y两个方向,因此图像信号属于二维离散信号。其在x,y两个...

yepanl
昨天
3
0
记录"正则表达式"

详细请查看我的博客:https://blog.enjoytoshare.club/article/RegularExpression.html 1 写在前面 正则表达式(Regular Expression)在代码中常常简写为regex。正则表达式通常被用来检索、替...

wugenqiang
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部