`
hunankeda110
  • 浏览: 746158 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Android平台Native开发与JNI机制详解

阅读更多
个人认为下面这篇转载的文章写的很清晰很不错. 注意Android平台上的JNI机制使用包括Java代码中调用Native模块以及Native代码中调用Java模块.

http://www.ophonesdn.com/article/show/263

众所周知,OPhone平台上的应用开发主要基于Java语言,但平台完全支持且提供了一定的Native开发能力(主要是C/C++),使得开发者可以借助JNI更深入的实现创意。本文主要介绍OPhone平台的JNI机制和Native模块开发与发布的方法。
JNI简介
Java Native Interface(JNI)是Java提供的一个很重要的特性。它使得用诸如C/C++等语言编写的代码可以与运行于Java虚拟机(JVM)中的 Java代码集成。有些时候,Java并不能满足你的全部开发需求,比如你希望提高某些关键模块的效率,或者你必须使用某个以C/C++等Native语 言编写的程序库;此时,JNI就能满足你在Java代码中访问这些Native模块的需求。JNI的出现使得开发者既可以利用Java语言跨平台、类库丰 富、开发便捷等特点,又可以利用Native语言的高效。



图1 JNI与JVM的关系


实际上,JNI是JVM实现中的一部分,因此Native语言和Java代码都运行在JVM的宿主环境(Host Environment),正如图1所示。此外,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补充的增强功能也基本都能支持。
开发者在使用JNI之前需要充分了解其优缺点,以便合理选择技术方案实现目标。JNI的优点前面已经讲过,这里不再重复,其缺点也 是显而易见的:由于Native模块的使用,Java代码会丧失其原有的跨平台性和类型安全等特性。此外,在JNI应用中,Java代码与Native代 码运行于同一个进程空间内;对于跨进程甚至跨宿主环境的Java与Native间通信的需求,可以考虑采用socket、Web Service等IPC通信机制来实现。

在OPhone开发中使用JNI
正如我们在上一节所述,JNI是一个双向的接口,所以交互的类型可以分为在Java代码中调用Native模块和在Native代码中调用Java模块两种。下面,我们就使用一个Hello-JNI的示例来分别对这两种交互方式的开发要点加以说明。

Java调用Native模块
Hello-JNI这个示例的结构很简单:首先我们使用Eclipse新建一个OPhone应用的Java工程,并添加一个 com.example.hellojni.HelloJni的类。这个类实际上是一个Activity,稍后我们会创建一个TextView,并显示一 些文字在上面。
要在Java代码中使用Native模块,必须先对Native函数进行声明。在我们的例子中,打开HelloJni.java文件,可以看到如下的声明:

view plain copy to clipboard print ?
/* A native method that is implemented by the 
   * 'hello-jni' native library, which is packaged 
   * with this application. 
   */  
  public   native  String  stringFromJNI(); 
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的C文件,内容很简单,只有如下一个函数:


view plain copy to clipboard print ?
#include <string.h> 
#include <jni.h> 
jstring 
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, 
                                                 jobject thiz ) { 
        return  (*env)->NewStringUTF(env,  "Hello from JNI !" ); 

Java代码 
#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()的具体实现。

从上面Native函数的命名上我们可以了解到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)。这个动作是通过如下语句完成的:

view plain copy to clipboard print ?
static  { 
    System.loadLibrary("hello-jni" ); 

Java代码 
static { 
    System.loadLibrary("hello-jni"); 


注意这里调用的共享库名遵循Linux对库文件的命名惯例,因为OPhone的核心实际上是Linux系统——上例中,实际加载的库文件应为 “libhello-jni.so”,在引用时遵循命名惯例,不带“lib”前缀和“.so”的扩展名。对于没有按照上述惯例命名的Native库,在加 载时仍需要写成完整的文件名。

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

view plain copy to clipboard print ?
TextView tv =  new  TextView( this ); 
tv.setText( stringFromJNI() ); 
setContentView(tv); 
Java代码 
TextView tv = new TextView(this); 
tv.setText( stringFromJNI() ); 
setContentView(tv); 

就可以在TextView中显示出来自于Native函数的字符串。怎么样,是不是很简单呢?

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

JNI规范定义了一系列在Native代码中访问Java对象及其成员与方法的API。下面我们还是通过示例来具体讲解。首先,新建一个SayHello 的类,代码如下:

view plain copy to clipboard print ?
package  com.example.hellojni; 
public   class  SayHello { 
        public  String sayHelloFromJava(String nativeMsg) { 
               String str = nativeMsg + " But shown in Java!" ; 
               return  str; 
        } 

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


接下来要实现的就是在Native代码中调用这个SayHello 类中的sayHelloFromJava方法。

一般来说,要在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读/写变量值。

上面概要介绍了从Native代码中访问Java对象的过程,下面我们结合示例来具体看一下。如下是调用sayHelloFromJava方法的Native代码:

view plain copy to clipboard print ?
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; 

Java代码 
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>”表示),第四个参数是方法的“签 名”,需要用一个字符串序列表示方法的参数(依声明顺序)和返回值信息。由于篇幅所限,这里不再具体说明如何根据方法的声明构造相应的“签名”,请参考 JNI的相关文档。

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

Native模块的编译与发布

通过前面的介绍,我们已经大致了解了在OPhone的应用开发中使用JNI的方法。那么,开发者如何编译出能在OPhone上使用的Native模块呢?编译出的Native模块又如何像APK文件那样分发、安装呢?

Google于2009年6月底发布了Android NDK的第一个版本,为广大开发者提供了编译用于Android应用的Native模块的能力,以及将Native模块随Java应用打包为APK文件, 以便分发和安装的整套解决方案。NDK的全称是Native Development Toolkit,即原生应用开发包。由于OPhone平台也基于Android,因此使用Android NDK编译的原生应用或组件完全可以用于OPhone。需要注意的是,Google声称此次发布的NDK仅兼容于Android 1.5及以后的版本,由于OPhone 1.0平台基于Android 1.5之前的版本,虽然不排除使用该NDK开发的原生应用或组件在OPhone 1.0平台上正常运行的可能性,但建议开发者仅在OPhone 1.5及以上的平台使用。

最新版本的NDK可以在http://developer.android.com/sdk/ndk/index.html 下载。NDK提供了适用于Windows、Linux和MAC OS X的版本,开发者可以根据自己的操作系统下载相应的版本。本文仅使用基于Linux的NDK版本做介绍和演示。

NDK的安装很简单:解压到某个路径下即可,之后可以看到若干目录。其中docs目录中包含了比较详细的文档,可供开发者参考,在NDK根目录 下的README.TXT也对个别重要文档进行了介绍;build目录则包含了用于Android设备的交叉编译器和相关工具,以及一组系统头文件和系统 库,其中包括libc、libm、libz、liblog(用于Android设备log输出)、JNI接口及一个C++标准库的子集(所谓“子集”是指 Android对C++支持有限,如不支持Exception及STL等);apps目录是用于应用开发的目录,out目录则用于编译中间结果的存储。接 下来,我们就用前面的例子简单讲解一下NDK的使用。

进入<ndk>/apps目录,我们可以看到一些示例应用,以hello-jni为例:在hello-jni目录中有一个 Application.mk文件和一个project文件夹,project文件夹中则是一个OPhone Java应用所有的工程文件,其中jni目录就是Native代码放置的位置。这里Application.mk主要用于告诉编译器应用所需要用到的 Native模块有什么,对于一般开发在示例提供的文件的基础上进行修改即可;如果需要了解更多,可参考<ndk>/docs /APPLICATION-MK.txt。接下来,我们将示例文件与代码如图2放置到相应的位置:




图2 Hello-JNI示例的代码结构

可以看到,和Java应用一样,Native模块也需要使用Android.mk文件设置编译选项和参数,但内容有较大不同。对于Native模块而言,一般需要了解如下几类标签:

1.         LOCAL_MODULE:定义了在整个编译环境中的各个模块, 其名字应当是唯一的。此外,这里设置的模块名称还将作为编译出来的文件名:对于原生可执行文件,文件名即为模块名称;对于静态/动态库文件,文件名为 lib+模块名称。例如hello-jni的模块名称为“hello-jni”,则编译出来的动态库就是libhello-jni.so。
2.         LOCAL_SRC_FILES:这里要列出所有需要编译的C/C++源文件,以空格或制表符分隔;如需换行,可放置“\”符号在行尾,这和GNU Makefile的规则是一致的。
3.         LOCAL_CFLAGS:定义gcc编译时的CFLAGS参数,与GNU Makefile的规则一致。比如,用-I参数可指定编译所需引用的某个路径下的头文件。
4.         LOCAL_C_INCLUDES:指定自定义的头文件路径。
5.         LOCAL_SHARED_LIBRARIES:定义链接时所需要的共享库文件。这里要链接的共享库并不限于NDK编译环境中定义的所有模块。如果需要引用其他的库文件,也可在此处指定。
6.         LOCAL_STATIC_LIBRARIES:和上个标签类似,指定需要链接的静态库文件。需要注意的是这个选项只有在编译动态库的时候才有意义。
7.         LOCAL_LDLIBS:定义链接时需要引入的系统库。使用时需要加-l前缀,例如-lz指的是在加载时链接libz这个系统库。libc、libm和libstdc++是编译系统默认会链接的,无需在此标签中指定。

欲了解更多关于标签类型及各类标签的信息,可参考<ndk>/docs/ANDROID-MK.txt文件,其中详细描述了Android.mk中各个标签的含义与用法。如下给出的就是我们的示例所用的Android.mk:


view plain copy to clipboard print ?
LOCAL_PATH := $(call my-dir) 
include $(CLEAR_VARS) 
LOCAL_MODULE    :=  hello-jni 
LOCAL_C_INCLUDES :=  $(LOCAL_PATH)/include 
LOCAL_SRC_FILES   :=  src/call_java.c \ 
                                          src/hello-jni.c  
include $(BUILD_SHARED_LIBRARY) 
Java代码 
LOCAL_PATH := $(call my-dir) 
include $(CLEAR_VARS) 
LOCAL_MODULE    :=  hello-jni 
LOCAL_C_INCLUDES :=  $(LOCAL_PATH)/include 
LOCAL_SRC_FILES   :=  src/call_java.c \ 
                                          src/hello-jni.c  
include $(BUILD_SHARED_LIBRARY) 


写好了代码和Makefile,接下来就是编译了。使用NDK进行编译也很简单:首先从命令行进入<ndk>目录,执 行./build/host-setup.sh,当打印出“Host setup complete.”的文字时,编译环境的设置就完成了。这里开发者需要注意的是,如果使用的Linux发行版是Debian或者Ubuntu,需要通过 在<ndk>目录下执行bash build/host-setup.sh,因为上述两个发行版使用的dash shell与脚本有兼容问题。接下来,输入make APP=hello-jni,稍等片刻即完成编译,如图3所示。从图中可以看到,在编译完成后,NDK会自动将编译出来的共享库拷贝到Java工程的 libs/armeabi目录下。当编译Java工程的时候,相应的共享库会被一同打包到apk文件中。在应用安装时,被打包在libs/armeabi 目录中的共享库会被自动拷贝到/data/data/com.example.HelloJni/lib/目录;当System.loadLibrary 被调用时,系统就可以在上述目录寻找到所需的库文件libhello-jni.so。如果实际的Java工程不在这里,也可以手动在Java工程下创建 libs/armeabi目录,并将编译出来的so库文件拷贝过去。



图3 使用NDK编译Hello-JNI


最后,将Java工程连带库文件一同编译并在OPhone模拟器中运行,结果如图4所示。

通过上面的介绍,你应该已经对OPhone上的Native开发有了初步了解,或许也已经跃跃欲试了。事实上,尽管Native开发在 OPhone上不具有Java语言的类型安全、兼容性好、易于调试等特性,也无法直接享受平台提供的丰富的API,但JNI还是为我们提供了更多的选择, 使我们可以利用原生应用的优势来做对性能要求高的操作,也可以利用或移植C/C++领域现有的众多功能强大的类库或应用,为开发者提供了充分的施展空间。 这就是OPhone的魅力!


分享到:
评论

相关推荐

    OPhone平台Native开发与JNI机制详解

    标题中的“OPhone平台Native开发与JNI机制详解”揭示了本文将深入探讨在OPhone操作系统上进行原生(Native)开发以及Java Native Interface (JNI)的相关技术。OPhone是基于Linux内核和中国移动OMS(Open Mobile ...

    android studio 3.4配置Android -jni 开发基础的教程详解

    Android Studio 3.4 配置 Android-JNI 开发基础教程详解 Android Studio 3.4 配置 Android-JNI 开发基础是 Android 应用程序开发中的一项重要技术,能够帮助开发者更好地编写 Android 应用程序。本文将详细介绍如何...

    Android 深入研究JNI详解

    ### Android深入研究JNI详解 #### 一、JNI与Android中的.so库载入 在深入了解JNI(Java Native Interface)之前,我们需要明确一点:Android应用程序的主要部分是基于Java开发的,并且这些Java类会被编译成Dex格式...

    Android开发书籍 - JNI详解

    ### Android开发书籍 - JNI详解 #### 一、前言与翻译初衷 本书旨在记录JNI(Java Native Interface)编程的经验,并以此为基础,探索更深层次的虚拟机相关知识。作者原本计划编写一本更为深入的虚拟机书籍,但意识...

    Android开发书籍 - JNI详解_导航版

    ### Android开发书籍 - JNI详解_导航版 #### 知识点概述 1. **JNI (Java Native Interface)**:这是Sun Microsystems定义的一个标准接口,允许Java代码与其他语言(主要是C/C++)编写的本地代码进行交互。例如,...

    Android开发书籍 - JNI详解-导航版

    Android 开发书籍 - JNI 详解导航版 本书籍主要介绍了 Java Native Interface(JNI)的内容,并探讨了在 Android 开发中 JNI 的应用。JNI 是 SUN 定义的一套标准接口,允许 Java 代码调用本地代码,本地代码也可以...

    Android 底层接口与驱动开发技术详解

    最后,书中也会涉及到Android应用程序接口(API)与底层驱动的交互,如何通过JNI(Java Native Interface)调用C/C++库,实现Java代码与驱动程序的通信。这部分内容对于开发系统级应用或扩展Android功能的开发者尤为...

    Android 底层接口与驱动开发技术详解 相关资料

    总的来说,"Android 底层接口与驱动开发技术详解"涵盖了Android系统的多个关键层面,包括硬件驱动的编写、HAL的设计、JNI的应用以及系统服务的理解。通过学习这些内容,开发者能够提升对Android系统的整体认知,从而...

    jni详解高清pdf文档

    ### JNI详解:深入理解Java Native Interface #### 一、引言与翻译初衷 JNI(Java Native Interface)作为连接Java世界与本地C/C++世界的桥梁,其重要性不容小觑。本文旨在详细介绍Android环境下JNI的使用,帮助...

    JniCallback.zip_Android jni_android_jni android_jni callback_jni

    在Android开发中,JNI(Java Native Interface)是一个关键的技术,它允许Java代码和其他语言写的代码进行交互。JNI在很多场景下都非常有用,比如优化性能、使用现有的C/C++库、或者像在这个“JniCallback.zip”文件...

    hello-jni.rar_ android jni_android hello jni_android jni hellojn

    《Android NDK与JNI开发详解:从Hello-JNI入门》 Android系统以其开源性和灵活性吸引了大量的开发者,而JNI(Java Native Interface)则是Android平台中连接Java层与原生C/C++代码的重要桥梁。JNI允许开发者在...

    《Android驱动开发与移植实战详解》.pdf.zip

    6. **JNI与Native库开发**:探讨Java Native Interface(JNI)在驱动开发中的应用,以及如何使用C/C++编写和调用本地库。 7. **性能优化**:讨论如何通过调试和分析工具对驱动程序进行性能优化,提高系统效率。 ...

    Android下JNI入门详解

    JNI(Java Native Interface)作为Android平台上连接Java层与C/C++层的关键桥梁,在开发过程中起着至关重要的作用。通过JNI,开发人员能够实现Java与C/C++代码之间的交互,这对于优化性能、利用现有C/C++库以及访问...

    android系统JNI编程详解

    JNI(Java Native Interface)是Java平台的一个重要组成部分,它允许Java代码和其他语言写的代码进行交互。在Android系统中,JNI的使用尤其广泛,因为它能够帮助我们实现高效的性能优化,调用本地库,以及与硬件设备...

    jni.rar_Android jni_JAVA串口 JNI_android_android RS232_jni

    《Android JNI串口通信实践详解》 在移动设备开发领域,Android系统因其开源特性与丰富的功能,成为开发者们的首选平台。然而,对于某些特定的硬件交互,如串口通信(RS232),原生Java API往往无法满足需求,这时...

    Android JNI 开发 步骤

    ### Android JNI开发步骤详解 #### 一、简介 JNI(Java Native Interface)是Java平台标准的一部分,它允许Java代码和其他语言写的代码进行交互。在Android开发中,JNI主要用于调用C/C++编写的本地代码,这可以...

    Android深入研究JNI详解.pdf

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

    Android学习笔记含JNI、USB和ftdi通信方式详解

    ### Android学习笔记含JNI、USB和ftdi通信方式详解 #### 项目四:多线程文件下载 在Android开发中,文件下载是一个常见的需求。利用多线程技术可以提高文件下载的速度,尤其在网络条件较差的情况下更为明显。多...

    android jni详解

    ### Android JNI详解 #### 一、概述 Java Native Interface (JNI) 是一种强大的技术,它允许Java代码调用本地代码(如C/C++),从而在Android应用开发中发挥重要作用。JNI不仅增强了应用性能,还提供了丰富的功能...

Global site tag (gtag.js) - Google Analytics