Android jni技术文档

1、Android JNI简介与调用流程

2、利用Android NDK编写一个简单的HelloWorld

3JNI数据类型的详解;

4JNI数据结构之JNINativeMethod

5JNI中不同种类的引用;

 

AndroidNDK开发(1)————Android JNI简介与调用流程

 

1、JNI简介

JNI全称为Java NativeInterface(JAVA本地调用)。从Java1.1开始,JNI成为java平台的一部分,它允许Java代码和其他语言写的代码(如C&C++)进行交互。并非从Android发布才引入JNI的概念的。

 

2、JNI  调用流程

        众所周知,Android的应用层的类都是以Java写的,这些Java类编译为Dex文件之后,必须靠Dalvik虚拟机( Virtual Machine)来执行。假如在执行java程序时,需要载入C&C++函数时,Dalvik虚拟机就会去加载C&C++的库(System.loadLibrary("libName");)让java层能顺利地调用这些本地函数。需要清楚一点,这些C&C++的函数并不是在Dalvik虚拟机中运行的,所以效率和速度要比在Dalvik虚拟机中运行得快很多。 Dalvik虚拟机成功加载库之后,就会自动地寻找库里面的JNI_OnLoad函数,这个函数用途如下:

(1)告诉Dalvik虚拟机此C库使用哪一个JNI版本。如果你的库里面没有写明JNI_OnLoad()函数,VM会默认该库使用最老的JNI 1.1版本。但是新版的JNI做了很多的扩充,也优化了一些内容,如果需要使用JNI的新版功能,就必须在JNI_OnLoad()函数声明JNI的版本。当没有JNI_OnLoad()函数时,Android调试信息会做出如下提示(No JNI_OnLoad found)

04-29 13:53:12.184: D/dalvikvm(361): Trying to load lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98  

04-29 13:53:12.204: D/dalvikvm(361): Added shared lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98  

04-29 13:53:12.204: D/dalvikvm(361): No JNI_OnLoad found in /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98, skipping init  

(2)因为Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数,所以我们可以在JNI_OnLoad()里面进行一些初始化工作,如注册JNI函数等等。注册本地函数,可以加快java层调用本地函数的效率。

另外:与JNI_OnLoad()函数相对应的有JNI_OnUnload()函数,当虚拟机释放该C库时,则会调用JNI_OnUnload()函数来进行善后清除动作。

 

AndroidNDK开发(2)————利用Android NDK编写一个简单的HelloWorld

1、Android NDK简介

NDK全称为nativedevelopment kit本地语言(C&C++)开发包。而对应的是经常接触的Android-SDK,(software development kit)软件开发包(只支持java语言开发)。Android的NDK提供了一些交叉编译工具链和Android自带的库,这些Android的库可以让开发者在编写本地语言的程序时调用。而NDK提供的交叉编译工具链就对已经编写好的C&C++代码进行编译,生成库。当然了,你也可以自己搭建交叉编译环境,而不用NDK的工具和库。然后生成库,只要规范操作,一样可以生成能让JAVA层成功调用的库文件的。

简单来说利用NDK,可以开发纯C&C++的代码,然后编译成库,让利用Android-SDK开发的Java程序调用。NDK开发的可以称之为底层开发或者jni(java  native interface)层开发,SDK开发可以称为上层开发。

 

2、为何要用NDK

2.1、众所周知,利用SDK编写的代码,生成的APK,很容易就可以反编译了,安全性极为不高,而利用NDK开发的库,不容易被反编译,保密性,安全性都提高了。

2.2、很多开源工程和大型工程都是C&C++代码,把它们转换为纯java语言显然是不可能的。

2.3、C&C++的代码运行速度和效率都比java快很多。

 

3、NDK环境的搭建

http://developer.android.com/sdk/ndk/index.html下载最新版的NDK,然后解压到你喜欢的位置,然后把NDK的所在的目录,配置到环境变量里面就行。

3.1、在windows下面用cygwin开发。环境配置可以参考http://blog.csdn.net/conowen/article/details/7518870

3.2、eclipse开发环境中集成ndk:环境配置可以参考http://jingyan.baidu.com/article/5d6edee22d908799eadeec9f.html

 

4、新建一个NDK工程

新建一个目录,命名为HelloWorld,然后在里面新建一个名为jni的目录(名称一定要是jni,因为ndk-build的时候会在HelloWorld目录下寻找jni的目录,然后进行build),在jni目录下新建如下文件HelloWorld.c和Android.mk。

HelloWorld.c文件代码如下(此JNI是没有jni_onLoad函数的)

#include <string.h>  

#include <jni.h>  

  

jstring  

Java_com_conowen_helloworld_HelloWorldActivity_helloWorldFromJNI( JNIEnv* env,   jobject thiz )  

{  

    return (*env)->NewStringUTF(env, "HelloWorld! I am from JNI !");  

}  

  

 /*注意,这里 

jstring 表示返回值 

Java_com_conowen_helloworld_HelloWorldActivity_helloWorldFromJNI 

写法是Java+Android工程的包名+Android工程的Activity名+方法名,点号用下划线表示,这个写法很严格。 

包名:com_conowen_helloworld 

Activity名:HelloWorldActivity 

方法名:helloWorldFromJNI 

 

JNIEnv* env, jobject thiz是Native方法自带的参数,可以用来转换一个数据类型。也就是说其实这个helloWorldFromJNI是没有形参的。 

*/  

Android.mk代码如下

LOCAL_PATH := $(call my-dir)  

include $(CLEAR_VARS)  

#LOCAL_MODULE表示生成的库的名字,前面的lib和后缀名不用写  

LOCAL_MODULE    := HelloWorld  

LOCAL_SRC_FILES := HelloWorld.c  

  

include $(BUILD_SHARED_LIBRARY)  

然后在终端里面,转到HelloWorld目录下,执行

$ndk/ndk-build  

$ndk是之前配置的NDK环境变量。编译成功后,会在在目录生成libs和obj两个文件夹,libs里面有刚刚编译成的libHelloWorld.so库。

 

5、新建一个Android工程

在eclipse里面新建一个Android工程,java代码如下,然后把刚刚生成的libs文件夹拖到Android工程目录下。

package com.conowen.helloworld;  

  

import android.app.Activity;  

import android.os.Bundle;  

import android.widget.TextView;  

  

public class HelloWorldActivity extends Activity {  

    /** Called when the activity is first created. */  

    @Override  

    public void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

  

        TextView tv = new TextView(this);  

        tv.setText(helloWorldFromJNI());  

        setContentView(tv);  

    }  

  

    public native String helloWorldFromJNI();// native声明,表示这个方法来自Native层。实现过程已经在native层实现了  

  

    static {  

        System.loadLibrary("HelloWorld");// 加载库,前面的lib和,后缀名不用写  

    }  

}  

 

AndroidNDK开发(3)————JNI数据类型的详解

JNI 的数据类型可以简单分为3 类,基本数据类型,数组数据类型,特殊数据类型。

Java 基本数据类型的变量传给JNI层,在JNI 层可以直接使用。除此之外,JNI 需要通过JNIEnv(jni interface pointer)提供方法来操作Java 对象

 

1、void

java的void与JNI的void是一致的。

 

2、基本数据类型

基本数据类型如下表所示。Java 的每种基本数据类型在JNI都有一种类型与之对应。就是在Java 的基本数据类型名称前加上字符‘j’。

 

 

3、数组数据类型

相比较与Java 数组类型用“[]”来表示,JNI 的数组类型是用“Array”表示。

Java类型                      本地类型                         描述

Object[]                jobjectArray                     任何对象的数组

boolean[]              jbooleanArray                  布尔型数组

byte[]                    jbyteArray                         比特型数组

char[]                      jcharArray                       字符型数组

short[]                   jshortArray                       短整型数组

int[]                        jintArray                                     整型数组

long[]                     jlongArray                         长整型数组

float[]                             jfloatArray                         浮点型数组

double[]                jdoubleArray                    高精度浮点型数组

 

4、特殊数据类型

Java类型             本地类型                 描述

Object                    jobject            所有的java 对象

String                      jstring            字符串对象

Class                     jclass                  java 类

xxx                         jfieldID               java 类的变量ID

xxx                         jmethodID      java 类的方法ID

 

相比基本类型,对象类型的传递要复杂得多。不能对Jstring进行直接操作。如下使用方式是错误的,因为jstring不同于C语言中的char *类型。  

Java_com_conowen_test_testActivity_test(JNIEnv *env, jobject obj, jstring str)  

{  

/* ERROR: incorrect use of jstring as a char* pointer */  

printf("%s", str);  

...  

}  

 

正确string类型的转换:

jstring CharTojstring(JNIEnv* env, constchar* pat)

{

         jclassstrClass = env->FindClass("java/lang/String");

         jmethodIDctorID = env->GetMethodID(strClass, "<init>","([BLjava/lang/String;)V");

         jbyteArraybytes = env->NewByteArray(strlen(pat));

         env->SetByteArrayRegion(bytes,0, strlen(pat), (jbyte*)pat);

         jstringencoding = env->NewStringUTF("utf-8");

         return(jstring)env->NewObject(strClass, ctorID, bytes, encoding);

}

 

char* jstringTostring(JNIEnv* env, jstringjstr)

{

         char*rtn = NULL;

         jclassclsstring = env->FindClass("java/lang/String");

         jstringstrencode = env->NewStringUTF("utf-8");

         jmethodIDmid = env->GetMethodID(clsstring, "getBytes","(Ljava/lang/String;)[B");

         jbyteArraybarr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);

         jsizealen = env->GetArrayLength(barr);

         jbyte*ba = env->GetByteArrayElements(barr, JNI_FALSE);

         if(alen > 0)

         {

         rtn= (char*)malloc(alen + 1);

 

         memcpy(rtn,ba, alen);

         rtn[alen]= 0;

         }

         env->ReleaseByteArrayElements(barr,ba, 0);

         returnrtn;

}

 

所有的Java 类对象,在JNI 都可以用jobect 表示。Java 字符串数据类型String在JNI 用jstring 表示,如果要或者字符串的内容,需要调用JNI 的方法来对jstring 对象进行解析。Java 类在JNI 统一用jclass 来表示。另外是JNI 特殊具有的数据类型jfieldID、jmethodID,一般在JNI 需要使用Java 类的域和方法的情况下会用到,

jfieldID 表示Java 类变量的id,jmethodID 表示Java 类方法的id。

java 中的二维数组,在JNI 对应哪种数据类型了?例如byte[]对应JNI 的数据类型为jbyteArray,那byte[][]在JNI 的数据类型是什么了?其实byte[][]可以拆解为byte[]类的数组,因此byte[][]对应的JNI 数据类型为jobjectArray。

 

AndroidNDK开发(4)————JNI数据结构之JNINativeMethod

 

JNI提供的另外一个功能是在本地代码中使用Java 对象。通过使用合适的JNI函数,你可以创建Java 对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI 通过ID 识别域和方法,一个域或者方法的ID 是任何处理域和方法的函数的必须参数。下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI 函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID 或jmethodID。

 

函数                    描述                    返回类型

GetFieldID          得到一个实例的域的ID        jfieldID

GetStaticFieldID    得到一个静态的域的ID        jfieldID

GetMethodID         得到一个实例的方法的ID     jmethodID

GetStaticMethodID   得到一个静态方法的ID        jmethodID

 

举个例子:

Java

{

private native void printRect();

}

Jni

{

JNIEXPORT void JNICALL Java_com_eyeandroid_basicaldatajni_MainActivity_printRect(JNIEnv* env, jobject this)

{

jclass rect_class = env‐>FindClass("android/graphic/Rect");

if (rect_class == NULL) {

return;

}

jfieldID field = env‐>GetFieldID(rect_class,"left", "I");

jmethodID method = env->GetMethodID(rect_class, "addDevice","(ILjava/lang/String;I)V");

}

}

 

主要是第三个参数比较复杂:

"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Fun();

"(II)V" 表示 voidFun(int a, int b);

这些字符与函数的参数类型的映射表如下:

 

2、第三个参数之基本数据类型

 

3、第二个参数之对象类型与数组类型

 

对象类型:以"L"开头,以";"结尾,中间是用"/" 隔开。如上表第1个

数组类型:以"["开始。如上表第2个(n维数组的话,则是前面多少个"["而已,如"[[[D"表示“double[][][]”)

对象数组类型:上述两者结合,如上表第3个

 

3.1、对象类型与数组类型的举例:

 

4、访问域

访问域的函数。实例的域可以使用对应域的GetXXXField 的变体函数访问。GetStaticXXXField 函数用于静态
类型。设置域的值用SetXXXField 和SetStaticXXXField 函数。

 

类型 Method 方法

GetBooleanField, GetStaticBooleanField, SetBooleanField,SetStaticBooleanField

GetByteField, GetStaticByteField,SetByteField, SetStaticByteField

GetCharField, GetStaticCharField,SetCharField, SetStaticCharField

GetShortField, GetStaticShortField,SetShortField, SetStaticShortField

GetIntField, GetStaticIntField,SetIntField, SetStaticIntField

GetLongField, GetStaticLongField,SetLongField, SetStaticLongField

GetFloatField, GetStaticFloatField,SetFloatField, SetStaticFloatField

GetDoubleField, GetStaticDoubleField,SetDoubleField, SetStaticDoubleField

GetObjectField, GetStaticObjectField,SetObjectField, SetStaticObjectField

 

5、访问方法

方法的访问是由CallXXXMethod 函数和CallStaticXXXMethod 函数完成的,XXX 表明了方法的返回值类型。

 

函数

CallBooleanMethod, CallStaticBooleanMethod

CallByteMethod,  CallStaticByteMethod

CallCharMethod, CallStaticCharMethod

CallVoidMethod, CallStaticVoidMethod

CallObjectMethod, CallStaticObjectMethod

….

 

Java

{

private Rect mRect = new Rect(0, 0, 100,100);

private native void printRectSize(Rect mRect);

}

Jni

{

JNIEXPORT void JNICALL Java_com_eyeandroid_basicaldatajni_MainActivity_printRect(JNIEnv* env, jobject this, jobject mRect)

{

jclass rect_class = env‐>FindClass("android/graphic/Rect");

if (rect_class == NULL) {

return;

}

jfieldID field = env‐>GetFieldID(rect_class, "left", "I");

jmethodID method = env‐>GetMethodID(rect_class, "isEmpty", "()Z");

int left = env‐>GetIntField(mRect, field);

bool isEmpty = env‐>CallBooleanMethod(mRect, isEmpty_method, ….);

}

 

AndroidNDK开发(4)————JNI中不同种类的引用

 

在JNI规范中定义了三种引用:局部引用、全局引用和弱全局引用。

局部和全局引用,有着各自不同的生命周期,局部引用能够被自动释放,而全局引用和若全局引用被程序员释放之前,是一直有效的。一个局部或者全局引用,使所提及的对象不能被垃圾回收。而弱全局引用,则允许提及的对象进行垃圾回收。

不是所有的引用都可以在所有上下文中使用的。例如:在一个创建返回引用native方法之后,使用一个局部引用,这是非法的。

 

1、局部引用

多数JNI函数都创建局部引用。例如JNI函数NewObject创建一个实例,并且返回一个指向该实例的局部引用。

局部引用只在创建它的native方法的动态上下文中有效,并且只在native方法的一次调用中有效。所有局部引用只在一个native方法的执行期间有效,在该方法返回时,它就被回收。

在native方法中使用一个静态变量来保存一个局部引用,以便在随后的调用中使用该局部引用,这种方式是行不通的。例如以下例子,误用了局部引用:

 

jstring  

MyNewString(JNIEnv *env, jchar *chars, jint len)  

{  

    static jclass stringClass = NULL;  

    jmethodID cid;  

    jcharArray elemArr;  

    jstring result;  

    if (stringClass == NULL) {  

        stringClass = (*env)->FindClass(env, "java/lang/String");  

        if (stringClass == NULL) {  

            return NULL; /* exception thrown */  

        }  

    }  

    /* It is wrong to use the cached stringClass here, because it may be invalid. */  

    cid = (*env)->GetMethodID(env, stringClass, "<init>", "([C)V");  

    ...  

    elemArr = (*env)->NewCharArray(env, len);  

    ...  

    result = (*env)->NewObject(env, stringClass, cid, elemArr);  

    (*env)->DeleteLocalRef(env, elemArr);  

    return result;  

}  

这种保存局部引用的方式是不正确的,因为FindClass()返回的是对java.lang.String的局部引用。这是因为,在native代码从MyNewString返回退出时,VM 会释放所有局部引用,包括存储在stringClass变量中的指向类对象的引用。这样当再次后继调用MyNewString时,可能会访问非法地址,导致内存被破坏,或者系统崩溃。

局部引用失效,有两种方式:

1)系统会自动释放局部变量。

2)程序员可以显示地管理局部引用的生命周期,例如调用DeleteLocalRef。

一个局部引用可能在被摧毁之前,被传给多个native方法。例如,MyNewString中,返回一个由NewObject创建的字符串引用,它将由NewObject的调用者来决定是否释放该引用。而在以下代码中:

JNIEXPORT jstring JNICALL Java_C_f(JNIEnv *env, jobject this)

 {  

         char *c_str = ...   

 ... 

return MyNewString(env,c_str, …);

 }  

在VM接收到来自Java_C_f的局部引用以后,将基础字符串对象传递给ava_C_f的调用者,然后摧毁原本由MyNewString中调用的JNI函数NewObject所创建的局部引用。

 

局部对象只属于创建它们的线程,只在该线程中有效。一个线程想要调用另一个线程创建的局部引用是不被允许的。将一个局部引用保存到全局变量中,然后在其它线程中使用它,这是一种错误的编程。

 

2、全局引用

在一个native方法被多次调用之间,可以使用一个全局引用跨越它们。一个全局引用可以跨越多个线程,并且在被程序员释放之前,一致有效。必须调用DeleteGlobalRef手动释放env->DeleteGlobalRef(g_cls_string);和局部引用一样,全局引用保证了所引用的对象不会被垃圾回收。

和局部引用不一样(局部变量可以由多数JNI函数创建),全局引用只能由一个JNI函数创建(NewGlobalRef)。下面是一个使用全局引用版本的MyNewString:

 

jstring  

MyNewString(JNIEnv *env, jchar *chars, jint len)  

{  

    static jclass stringClass = NULL;  

    ...  

    if (stringClass == NULL) {  

        jclass localRefCls =   (*env)->FindClass(env, "java/lang/String");  

        if (localRefCls == NULL) {  

            return NULL; /* exception thrown */  

        }  

        /* Create a global reference */  

        stringClass = (*env)->NewGlobalRef(env, localRefCls);  

        /* The local reference is no longer useful */  

        (*env)->DeleteLocalRef(env, localRefCls);  

        /* Is the global reference created successfully? */  

        if (stringClass == NULL) {  

            return NULL; /* out of memory exception thrown */  

        }  

    }  

    ...  

}  

 

3弱全局引用

弱全局引用是在java 2 SDK1.2才出现的。

调用NewWeakGlobalRef基于局部引用或全局引用创建,不会阻止GC回收所引用的对象,可以跨方法、跨线程使用。引用不会自动释放,在JVM认为应该回收它的时候(比如内存紧张的时候)进行回收而被释放。或调用DeleteWeakGlobalRef手动释放。(*env)->DeleteWeakGlobalRef(env,g_cls_string)

JNIEXPORT void JNICALL  

Java_mypkg_MyCls_f(JNIEnv *env, jobject self)  

{  

    static jclass myCls2 = NULL;  

    if (myCls2 == NULL) {  

        jclass myCls2Local =  (*env)->FindClass(env, "mypkg/MyCls2");  

        if (myCls2Local == NULL) {  

            return; /* can’t find class */  

        }  

        myCls2 = NewWeakGlobalRef(env, myCls2Local);  

        if (myCls2 == NULL) {  

            return; /* out of memory */  

        }  

    }  

    ... /* use myCls2 */  

}  

弱全局引用在一个被native代码缓存着的引用不想阻止基础对象被垃圾回收时,非常有用。如以上例子,mypkg.MyCls.f需要缓存mypkg.MyCls2的引用。而通过将mypkg.MyCls2缓存到弱引用中,能够实现MyCls2类依旧可以被卸载。

上面代码中,我们假设了MyCls类和MyCls2类的生命周期是相同的(例如,在同一个类中被加载、卸载),所以没有考虑MyCls2被卸载了,然后在类MyCls和native方法的实现Java_mypkg_MyCls_f还要被继续使用时,再被重新加载起来的情况。针对于这个MyCls2类可能被卸载再加载的情况,在使用时,需要检查该弱全局引用是否还有效。如何检查,这将在下面提到。

比较引用可以用JNI函数IsSameObject来检查给定的两个局部引用、全局引用或者弱全局引用,是否指向同一个对象。

(*env)->IsSameObject(env, obj1, obj2)  

返回值为:

JNI_TRUE,表示两个对象一致,是同一个对象。

JNI_FALSE,表示两个对象不一致,不是同一个对象。



在java VM中NULL是null的引用。如果一个对象obj是局部引用或者全局引用,则可以这样来检查它是否指向null对象:

(*env)->IsSameObject(env, obj, NULL)  或者:NULL == obj  

 

而对于弱全局引用,以上规则需要改变一下:

我们可以用这个函数来判断一个非0弱全局引用wobj所指向的对象是否仍旧存活着(依旧有效)。(*env)->IsSameObject(env, wobj, NULL)  

返回值:

JNI_TRUE,表示对象已经被回收了。

JNI_FALSE,表示wobj指向的对象,依旧有效。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值