android JNI调用机制

JNI的出现使得开发者既可以利用Java语言跨平台、类库丰 富、开发便捷等特点,又可以利用Native语言的高效。

JNI是JVM实现中的一部分,因此Native语言和Java代码都运行在JVM的宿主环境。

JNI是一个双向的接口:开发者不仅可以通过JNI在Java代码中访问Native模块,还可以在 Native代码中嵌入一个JVM,并通过JNI访问运行于其中的Java模块。可见,JNI担任了一个桥梁的角色,它将JVM与Native模块联系起 来,从而实现了Java代码与Native代码的互访。在OPhone上使用Java虚拟机是为嵌入式设备特别优化的Dalvik虚拟机。每启动一个应 用,系统会建立一个新的进程运行一个Dalvik虚拟机,因此各应用实际上是运行在各自的VM中的。Dalvik VM对JNI的规范支持的较全面,对于从JDK 1.2到JDK 1.6补充的增强功能也基本都能支持。

缺点:由于Native模块的使用,Java代码会丧失其原有的跨平台性和类型安全等特性。此外,在JNI应用中,Java代码与Native代 码运行于同一个进程空间内;对于跨进程甚至跨宿主环境的Java与Native间通信的需求,可以考虑采用socket、Web Service等IPC通信机制来实现。

互的类型可以分为在Java代码中调用Native模块和在Native代码中调用Java模块两种。

Java调用Native模块

HelloJni.java

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

这个stringFromJNI()函数就是要在Java代码中调用的Native函数。

hello-jni.c

#include <string.h>    
#include <jni.h>    
jstring    
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,    
                                                 jobject thiz ) {    
 return (*env)->NewStringUTF(env, "Hello from JNI !");    
}    

这个Native函数对应的正是我们在com.example.hellojni.HelloJni这个中声明的Native函数String stringFromJNI()的具体实现。

JNI函数的命名规则: Java代码中的函数声明需要添加native 关键 字;Native的对应函数名要以“Java_”开头,后面依次跟上Java的“package名”、“class名”、“函数名”,中间以下划线“_” 分割,在package名中的“.”也要改为“_”。此外,关于函数的参数和返回值也有相应的规则。对于Java中的基本类型如int 、double 、char 等,在Native端都有相对应的类型来表示,如jint 、jdouble 、jchar 等;其他的对象类型则统统由jobject 来表示(String 是个例外,由于其使用广泛,故在Native代码中有jstring 这个类型来表示,正如在上例中返回值String 对应到Native代码中的返回值jstring )。而对于Java中的数组,在Native中由jarray 对应,具体到基本类型和一般对象类型的数组则有jintArray 等和jobjectArray 分别对应(String 数组在这里没有例外,同样用jobjectArray 表示)。还有一点需要注意的是,在JNI的Native函数中,其前两个参数JNIEnv *和jobject 是必需的——前者是一个JNIEnv 结构体的指针,这个结构体中定义了很多JNI的接口函数指针,使开发者可以使用JNI所定义的接口功能;后者指代的是调用这个JNI函数的Java对象,有点类似于C++中的this 指针。在上述两个参数之后,还需要根据Java端的函数声明依次对应添加参数。在上例中,Java中声明的JNI函数没有参数,则Native的对应函数只有类型为JNIEnv *和jobject 的两个参数。

,要使用JNI函数,还需要先加载Native代码编译出来的动态库文件(在Windows上是.dll,在Linux上则为.so)。这个动作是通过如下语句完成的:

static {  
    System.loadLibrary("hello-jni");  
}  

JNI函数的使用方法和普通Java函数一样。在本例中,调用代码如下:

TextView tv = new TextView(this);    
tv.setText( stringFromJNI() );   
setContentView(tv);    

Native调用Java模块

package com.example.hellojni;    
public class SayHello {    
 public String sayHelloFromJava(String nativeMsg) {    
               String str = nativeMsg + " But shown in Java!";    
 return str;    
        }    
}   

从OPhone的系统架构来看,JVM和Native系统库位于内核之上,构成OPhone Runtime;更多的系统功能则是通过在其上的Application Framework以Java API的形式提供的。因此,如果希望在Native库中调用某些系统功能,就需要通过JNI来访问Application Framework提供的API。

一般来说,要在Native代码中访问Java对象,有如下几个步骤:

1.         得到该Java对象的类定义。JNI定义了jclass 这个类型来表示Java的类的定义,并提供了FindClass接口,根据类的完整的包路径即可得到其jclass 。

2.         根据jclass 创建相应的对象实体,即jobject 。在Java中,创建一个新对象只需要使用new 关键字即可,但在Native代码中创建一个对象则需要两步:首先通过JNI接口GetMethodID得到该类的构造函数,然后利用NewObject接口构造出该类的一个实例对象。

3.         访问jobject 中的成员变量或方法。访问对象的方法是先得到方法的Method ID,然后使用Call<Type>Method 接口调用,这里Type对应相应方法的返回值——返回值为基本类型的都有相对应的接口,如CallIntMethod;其他的返回值(包括String) 则为CallObjectMethod。可以看出,创建对象实质上是调用对象的一个特殊方法,即构造函数。访问成员变量的步骤一样:首先 GetFieldID得到成员变量的ID,然后Get/Set<Type>Field读/写变量值。

jstring helloFromJava( JNIEnv* env ) {    
       jstring str = NULL;    
       jclass clz = (*env)->FindClass(env, "com/example/hellojni/SayHello");    
       jmethodID ctor = (*env)->GetMethodID(env, clz, "<init>", "()V");    
       jobject obj = (*env)->NewObject(env, clz, ctor);    
       jmethodID mid = (*env)->GetMethodID(env, clz, "sayHelloFromJava", "(Ljava/lang/String;)Ljava/lang/String;");    
 if (mid) {    
              jstring jmsg = (*env)->NewStringUTF(env, "I'm born in native.");    
              str = (*env)->CallObjectMethod(env, obj, mid, jmsg);    
       }    
 return str;    
}    

提一下编程时要注意的要点:1、FindClass要写明Java类的完整包路径,并将 “.”以“/”替换;2、GetMethodID的第三个参数是方法名(对于构造函数一律用“<init>”表示),第四个参数是方法的“签 名”,需要用一个字符串序列表示方法的参数(依声明顺序)和返回值信息。

如上这种使用NewObject创建的对象实例被称为“Local Reference”,它仅在创建它的Native代码作用域内有效,因此应避免在作用域外使用该实例及任何指向它的指针。如果希望创建的对象实例在作用 域外也能使用,则需要使用NewGlobalRef接口将其提升为“Global Reference”——需要注意的是,当Global Reference不再使用后,需要显式的释放,以便通知JVM进行垃圾收集。

Native模块的编译与发布

NDK的全称是Native Development Toolkit,即原生应用开发包。,为广大开发者提供了编译用于Android应用的Native模块的能力,以及将Native模块随Java应用打包为APK文件。

引用地址:http://mysuperbaby.iteye.com/blog/915425

二、

<span style="font-family: Arial, Helvetica, sans-serif;">原文地址:http://www.open-open.com/lib/view/open1324909652374.html</span> 

android JNI是连接android Java部分和C/C++部分的纽带,完整使用JNI需要Java代码和C/C++代码。其中C/C++代码用于生成库文件,Java代码用于引用C /C++库文件以及调用C/C++方法。

jnitest.java  
 
package com.hello.jnitest;  
import android.app.Activity;  
import android.os.Bundle;  
 
public class jnitest extends Activity {  
 
 /** Called when the activity is first created. */ 
 @Override 
 public void onCreate(Bundle savedInstanceState) {  
 super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        Nadd test = new Nadd();  
        setTitle("The Native Add Result is "+String.valueOf(test.nadd(10, 20)));  
    }   
}  
 
Nadd.java  
 
package com.hello.jnitest;  
public class Nadd {  
 static {  
        System.loadLibrary("hello_jni");  
    }  
 
public native int nadd(int a, int b);  
 
}  

Java代码说明:

1)jnitest.java是一个activity的类对象,在该类对象中生成调用JNI函数的类对象,同时调用JNI方法,最后将JNI方法的结果显示到标题栏上;

2)Nadd.java是一个引用和声明JNI库和函数的类,其中System.loadLibrary();函数用来引用JNI库,默认JNI库放在 android系统的/system/lib/目录下;public nadd int nadd(int a, int b);为声明需要在java程序中使用的JNI库中的函数;

JNI中java部分的代码到此就结束了,总结一下在java代码中需要做两件事:

1)使用System.loadLibrary()函数来引用JNI库;

2)声明调用JNI库的函数且前面添加native关键字;

android C/C++部分代码:

#define LOG_TAG "hello-JNI" 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <assert.h> 
#include "jni.h" 
#include "JNIHelp.h" 
#include "android_runtime/AndroidRuntime.h" 
static jint com_hello_jnitest_jnitest_nadd(JNIEnv *env, jobject obj, jint a, jint b)  
{  
 return (a * b);  
}  
static JNINativeMethod gMethods[] = {  
{"nadd", "(II)I", (void *)com_hello_jnitest_jnitest_nadd},};  
static int register_android_test_hello(JNIEnv *env)  
{  
 return android::AndroidRuntime::registerNativeMethods(env, "com/hello/jnitest/Nadd", gMethods, NELEM(gMethods));  
}  
jint JNI_OnLoad(JavaVM *vm, void *reserved)  
{  
    JNIEnv *env = NULL;  
 if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {  
        printf("Error GetEnv\n");  
 return -1;  
    }  
    assert(env != NULL);  
 if (register_android_test_hello(env) < 0) {  
        printf("register_android_test_hello error.\n");  
 return -1;  
    }  
 return JNI_VERSION_1_4;  
}  

JNI C/C++代码说明:

1)JNI_OnLoad()函数。该函数在Java程序调用System.loadLibrary()时,被调用执行,用于向JavaVM注册JNI函数等。在本例中首先通过参数JavaVM(Java虚拟机指针)获取当前应用程序所在的线程,即:JNIEnv。再通过调用 android::AndroidRuntime::registerNativeMethods()注册native实现的函数指针。

2)JNI函数和Java调用函数的映射关系。使用JNINativeMethod将java调用的函数名与JNI实现的函数名联系在一起;

3)JNI函数实现;

Android.mk代码:

LOCAL_PATH := $(call my-dir)  
include $(CLEAR_VARS)  
LOCAL_PRELINK_MODULE := false 
LOCAL_SRC_FILES := \  
com_hello_jnitest.cpp  
LOCAL_SHARED_LIBRARIES := \  
libandroid_runtime  
LOCAL_MODULE := libhello_jni 
include $(BUILD_SHARED_LIBRARY)  

需要注意的是:

1)JNI C/C++部分的代码需要在android源代码树上进行编译,编译完成后我的做法是直接将生成的.so通过adb push方法上传到android虚拟机的/system/lib/目录下;

2)java代码可以在eclipse下直接编译且在虚拟机上执行;

编译JNI C/C++部分代码(在android内核源代码根目录下):

#make libhello_jni

之后在out/target/product/generic/system/lib/目录下生成libhello_jni.so

上传libhello_jni.so到android虚拟机:

#adb push out/target/product/generic/system/lib/libhello_jni.so /system/lib

注意:此时有可能出现Out of Memory的错误提示。当出现如上错误提示时,需要使用#adb remount重新加载一下就可以了。

另外,也有可能直接使用eclipse启动android虚拟机时出现上述错误且使用#adb remount也出现的情况,此时需要手动启动android虚拟机,如:#emulator -avd xxx -partition-size 128,之后在使用#adb push就可以了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏嵌入式程序猿

freeRTOS任务创建

我们曾经在公众号里给大家推送过关于freeRTOS在NXP kinetis KV4x上的移植,得到了猿友大量的反馈,很多猿友还是感觉对基础的一些东西不懂,今天我...

41970
来自专栏zhisheng

Java研发方向如何准备BAT技术面试答案(上)

1. 面向对象和面向过程的区别 面向过程 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Un...

46840
来自专栏前端杂货铺

AngularJS源码分析之依赖注入$injector

开篇 随着javaEE的spring框架的兴起,依赖注入(IoC)的概念彻底深入人心,它彻底改变了我们的编码模式和思维。在IoC之前,我们在程序中需要创建一个...

35950
来自专栏小灰灰

Java 回调函数的使用

回调函数 回调函数是什么鬼, 回调函数干嘛用,回调函数可以怎么用 如果有过android开发经验,经常可以看到一些类似下面的代码 Button Btn1 = ...

44180
来自专栏技术之路

[原创翻译]Protocol Buffer Basics: C#

Protocol Buffer 基础知识:c#    原文地址:https://developers.google.com/protocol-buffers/d...

29490
来自专栏JavaQ

Java研发方向如何准备BAT技术面试答案(上)

最近因为忙于工作,没时间整理,本篇是下班后晚上抽空整理的,文中部分答案本来是想自己好好整理一份的,但是时间真的很紧,所以就整理了一下网络上的文章链接,挑了写的不...

38750
来自专栏FreeBuf

Python黑客学习笔记:从HelloWorld到编写PoC(上)

本系列文章适合CS在读学生和万年工具党,本文会在英文原文的基础上做些修改,并适当增加些解释说明。 ? 本篇包含原文的前几部分: 0x0 – Getting St...

335100
来自专栏爱撒谎的男孩

Spring初体验

43460
来自专栏公众号_薛勤的博客

Java多线程编程核心技术(三)多线程通信

通过本节可以学习到,线程与线程之间不是独立的个体,它们彼此之间可以互相通信和协作。

14180
来自专栏余林丰

JVM入门——运行时数据区

jdk1.7.0_79  ?   这张图我相信基本上对JVM有点接触的都应该很熟悉,可以说这是JVM入门的第一课。其中的“堆”和“虚拟机栈(栈)”更是耳熟能详。...

21750

扫码关注云+社区

领取腾讯云代金券