NDK开发差不多结束了, 估计后面也不会再碰了诶, 想着还是写个总结什么的,以后捡起来也方便哈。既然是总结,我这里就不会谈具体的细节,只会记录下我觉得重要的东西, 所以这篇随笔不是为萌新学习新知识准备的, 而是复习用的, 有些知识默认读者知道,就算忘了也能根据提示想起来。这里虽然是总结有些地方还是很细的2333.

方法论:

1、 我在实践中大概是这样的流程, 想好大概的java和jni代码交互流程, 然后编写jni接口代码, 然后在接口代码里面调用c++或者c写的方法, 如果不跨线程的话, 我会传JNIEnv指针给本地代码层。这样相当于分了三层, java层, 中间层, 本地层, 这里的中间层指的按照jni规范命名的方法, 本地层不考虑java层逻辑, 而是设计的实现中间层逻辑的各种类的集合。

2、有些项目可能会使用三方的c/c++ sdk, 这些sdk可能并没有按java和jni交互的规范设计, 所以java层无法直接调用sdk里面的方法, 但是计算机里面有个重要的方法, 什么问题都能够通过加个中间层解决, 也可以认为是设计模式里面的适配器思想的范版,具体方法是 我们可以在自己的c/c++代码里面封装第三方的sdk, 然后java层调用我们的c/c++代码来间接的使用三方的sdk的效果。

知识点:

  一、Java和c/c++接口

本地方法通过native关键字来定义, 暗示编译器这个方法的通过其他语言实现, 这个方法通过分号终止, 因为本地方法没有方法体。

虽然我们定义了本地方法, 但是窝们还没有告诉java虚拟机怎么找到这个方法的实现, 这是后我们就要通过下面 这种方式告诉虚拟机去加载哪个动态库了。

static{

System.loadLibrary("hello-jni");

}

loadLibrary方法在静态代码块里面调用, 因为我们想本地方法在类被加载,第一次被初始化的时候动态库能够加载进来了。

 java技术的一个设计目标是平台无关性, java框架的api作为一部分, loadLibrary的设计也一样, 这里动态库的名字是libhello-jni.so, 但是在这个方法里面只需要写库的名字就行了, 也就是模块的名字(), hello-jni, 系统在用的时候会添加前缀和后缀。  loadlibrary搜索的路径在System property里面的key java.library.path里面定义了, loadLibrary方法会搜索这个列表寻找动态库.java library的路径在android里面是 /vendor/lib 和 /system/lib;

要想虚拟机正确的找到本地方法,本地方法需要按照严格的规则命名函数, 这样虚拟机才能找到。

栗子:

java:

package com.demo;

class Sample{

                static{

System.loadlibrary("hello-jni");

}

public native String stringFromJNI();

            }

ndk:

jstring Java_com_demo_Sample(JNIEnv *env, jobject thiz){};

名为stringFromJNI的本地方法, 在c/c++层有一个精确的c层方法对, Java_com_demo_Sample, 试想如果java层方法和c层方法的名称没有精确的规则对应,虚拟机根据java层本地方法拿什么去匹配c/c++层代码, 或者设计者可以设计用注解注明c层代码名, 但是设计者没有这么做。

二、 数据类型

我们都知道java有两种数据类型

* 原始类型: boolean, byte, char, short, int, long, float, double

* 引用数据类型: String, 或者其他的类

1、原始类型

java原始类型和c类型对比

JavaType JNIType C/C++Type Size
Boolean jboolean unsigned char unsigned 8 bits
Byte jbyte char singned 8 bits
Char jchar unsigned short unsigned 16 bits
Short jshort short signed 16 bits
int jint int  signed 32 bits
Long jlong long long signed 64 bits
Float jfloat float 32 bits
Double jdouble double 64 bits

2、java引用类型

java type Native Type
java.lang.Class jclass
java.lang.Throwable jthrowable
java.lang.String jstring
other object jobject
java.lang.Object[] jobjectArray
boolean[] jbooleanArray
byte[] jbyteArray
char[] jcharArray
short[] jshortArray
int[] jintArray
float[] jfloatArray
double[] jdoubleArray
other arrays jarray
   

 原始类型在c/c++里面是直接可以使用的, 因为他们对应着c/c++里面的类型, 但是引用类型c/c++不可以直接操作, 如果想操作的话必须使用JNI提供的接口去操作这些引用类型。

三、引用类型操作

1. 字符串操作

创建String

jstring javastring = env->NewStringUTF("Hello world!");

如果内存不够用了, 这个方法将会返回NULL, 同事虚拟机会抛出一个异常, 所以我们的方法应该返回而不应该继续处理;

2. java字符串转C 字符串

const jbyte* str;

jboolean iscopy;

str = env->GetStringUTFChars(javastring, &iscopy);

if (NULL != str){

printf("java string:%s", str);

if ( JNI_TRUE == iscopy){

printf("this c string is copy from java string.");

}else{

printf("c string is one width java string.");

}

}

注意GetStringChars 和GetStringUTFChars 方法需要调用ReleaseStringChars和ReleaseStringUTFChars 释放内存,有一个设计规则,谁申请的内存,那么谁就赋值释放, 这里调用env获得字符串的过程中,env申请了内存,所以我们要调用env的方法去释放它。

3. 数组操作(注意数组是引用类型)

新建一个数组可以调用本地方法,类似于New<Type>Array 方法的形式构建, <Type>可以使int, char, boolean等等,比如NewIntArray;

jintArray javaArray;

javaArray = env->NewIntArray(10);

if (NULL != javaArray){

...

}

和NewString 方法类似, 如果内存不够用了, 那么New<Type>Array 方法将会返回NULL, 虚拟机将会抛出异常, 所以本地方法应该要立刻返回,而不应该继续执行了。

--操作数组元素

调用Get<Type>ArrayRegion方法可以复制一个java的原始类型数组成为对应的C数组. 可能有人会想,原始类型数组肿么操作要这么麻烦, 还要转成jni对应数组才行啊, 如果这么想的话,那么可能你忘了java数组是引用类型的事情, 引用类型我们是不能再c里面操作的, 但是窝们可以操作原始类型, 所以将java原始类型数组转化成jni 类型数组, 我们就可以做对应操作了。

jint nativeArray[10];

env->GetIntArrayRegion(javaArray, 0, 10, nativeArray);

当然, get到了数据做完修改我们也会需要set回去咯, 这时候调用Set<Type>ArrayRegion方法就可以了,嘛, 这里设计的还是很对称的啦。

        注意一点, 当数组很大的时候, 复制数组会造成性能问题, 所以我们应该get我们需要修改的范围,然后设置回去,  当然Jni提供了一系列不同的方法,可以直接通过指针的方式操作数组, 而不用复制他们。

---直接通过指针操作数组

Get<Type>ArrayElements 方法 允许本地代码直接通过指针操作数组元素, isCopy允许调用者声明是否返回一个c数组指针指向复制或者在堆空间上的固定数组。

jint *nativeDirectArray;

jboolean iscopy;

nativeDirectArray = env->GetIntArrayElements(javaArray, &isCopy);

同样的,我们需要调用Release<Type>ArrayElements方法去释放内存, 否则会造成内存泄漏。

比如不用的时候应该调用env->ReleaseIntArrayElements(javaArray, nativeDirectArray, 0);

第三个参数可以是下面的值:

Release Mode Action
0 Copy back the content and free the native array
JNI_COMMIT

Copy back the content but do not freee the array.

This can be used for periodically updating a Java array

JNI_ABORT free the native array without copyting its content.

---直接新建一个字节缓冲区

本地代码可以直接创建一个字节缓冲区, 这个缓冲区可以给java应用直接使用, 缓冲区的内容直接使用c/c++层字节数组。

unsigned char * buffer = (unsigned char *) (unsigned char *) malloc(1024);

....

jobject directBuffer;

directBuffer = env->NewDirectByteBuffer(buffer, 1024);

                     注意:

                      当然这里的内存不是由java虚拟机申请的了, 所以本地代码需要自己管理这些分配的内存。比如我们可以写个recycle的本地方法,在java层调用这个方法释放内存。

同理我们也可以获得java应用创建的字节缓冲区。调用GetDirectBufferAddress方法会返回一个c字符指针。

访问属性:

java有两种类型的属性, 实例的属性和静态属性, 每种属性都有对应的方法获取。

其实步骤都是获取对应的属性的id, 然后获取属性值。

JNI提供了方法去获得者两种属性例:

public class JavaClass{

private String instanceField = "instance Field";

private static String staticField = "static Field";

}

1) 获取非静态属性id

jfieldID instanceFieldId;

instanceFieldId = env->GetFieldID(clazz, "instanceField", "Ljava/lang/String");

2) 获取静态属性id

jfieldID staticFieldId;

staticFieldId = env->GetStaticFieldId(clazz, "staticField", "Ljava/lang/String;");

最后一个参数是属性的描述, 这个是java虚拟机规范里面的, 可以看下我前面的博客查查肿么写。

获取属性通过Get<Type>Field, 或者GetStatic<Type>Field方法得到, type是属性的类型。 如果内存满了, 者两个会返回NULL。

小提示:

获取一个属性需要调用2个或者3个JNI方法的调用, 建议尽量在本地方法里面获取参数,然后返回到java层, 尽量少的直接用java层的类的属性来获取参数。

 调用方法:

跟获取属性一样, 也要先获取id, 然后才能执行方法, 我们有两种获取方法id的方式, 一种是对class的,也就是静态方法的id,一种是实例的,也就是非静态方法的id.

public class JavaClass{

private String instanceMethod(){

return "Instance Method";

}

private static String staticMethod(){

rerturn "static Method";

}

}

jmethodID instanceMethodId;

instanceMethodId = env->GetMethod(clazz, "instanceMethod", "()Ljava/lang/String;");

jmethodID staticMethodId;

staticMethodId = env->GetStaticMethodID(clazz, "staticMethod", "()Ljava/lang/String;");

和方法id一样, 最后一个参数是方法的描述, 也就是方法签名, 同样的是java虚拟机规范。

接下来就是根据方法id调用方法了,同样使用 Call<Type>Method,或者CallStatic<Type>Method去执行对应的非静态和静态方法。

捕获异常:

java里面是有异常机制的,如果我本地执行java代码, java代码里面抛出了异常, 本地方法这么处理呢? java JNIEnv接口提供了一系列方法来处理异常, 现在来总结下:

public class JavaClass{

private void throwingMethod() throws NullPointerException{

throw new NullPointerException("Null pointer");

}

private nativve void accessMethods();

}

如果我们在accessMethods的本地方法里面调用了throwingMethod方法, 那么我们本地代码里面就要精确的处理throwingMethod方法可能产生的异常。

首先我们肿么会想到, 本地代码里面肿么抛出异常呢, 比如我们定义了一个可以抛出异常的本地方法, 辣么我们实现本地方法的时候肿么抛出异常呢?

jthrowable ex;

..

env->CallVoidMethod(instance, throwingMethodId);

ex = env->ExceptionOccurred();

if (NULL != ex){

env->ExceptionClear();

}

JNI提供了ExceptionOccurred方法去查询虚拟机是否有异常抛出, 本地异常处理需要精确使用ExceptionClear方法来清除异常

问题来啦, 我们肿么在本地代码里面抛出异常呢?

jclass clazz;

...

clazz = env->FindClass("java/lang/NullPointerException"); //这里的参数是java类的内部名, 不要和签名弄混哦

if(NULL != clazz){

env->ThrowNew(clazz, "Exception message.");

}

由于本地代码不归虚拟机控制, 所以啊, 抛出异常后, 我们的方法不应该继续有其他操作了,而是应该返回同时释放本地引用和资源。

后面的只是提一下了:

java里面的关键字Synchronized,肿么 在本地代码实现呢?

例:

if(JNI_OK == env->MonitorEnter(obj)){

//错误处理

}

//同步代码

if (JNI_OK == env->MonitorExit()){

//错误处理

}

、本地线程

本地代码产生的线程java虚拟机是不知道的, 所以JNIEnv是不能跨线程使用的, 如果要使用的话我们需要将本地线程贴到java虚拟机上,去重新获得JNIEnv指针。不过java虚拟机是是可以跨线程的, 所以JavaVM指针是可以全局共享的。

JavaVM* cachedJvm;

..

JNIEnv* env;

//Attach

cachedJvm->AttachCurrentThread(cachedJvm, &env, NULL);

//现在线程可以通过JNIEnv和Java应用交互了

//Detach

cachedJvm->DetachCurrentThread();

话说JavaVm肿么获得呢?

其实只有在本地代码中注册一个回调就可以了, 本地代码在加载的时候会自动执行这个方法。

JavaVM *cachedJvm;

jint JNI_OnLoad(JavaVM *vm, void *reserved){

g_jvm = vm;

if (JNI_OK != vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4)){

return JNI_ERR;

}

return JNI_VERSION_1_4;

}

JNI引用:

引用知识前面的博客总结过了,这里就不写了

NDK开发总结的更多相关文章

  1. NDK开发_笔记0

    自谷歌搜索退出中国以来,谷歌对全球第二大市场中国的态度一直保持冷淡.可是北京时间12月8日,谷歌2016开发者大会在北京召开,同时专门针对中国的谷歌开发者网站已经上线:https://develope ...

  2. Android SDK NDK开发总结

    描述:http://talent.baidu.com/external/baidu/index.html#/jobDetail/2/1237247043 android studio实现Jni(C/C ...

  3. Android NDK开发Hello Word!

    在之前的博客中已经为大家介绍了,如何在win环境下配置DNK程序,本篇我将带大家实现一个简单的Hello jni程序,让大家真正感受一下NDK开发的魅力.这里我们选择使用C+JAVA开发Android ...

  4. Android NDK开发初识

    神秘的Android NDK开发往往众多程序员感到兴奋,但又不知它为何物,由于近期开发应用时,为了是开发的.apk文件不被他人解读(反编译),查阅了很多资料,其中有提到使用NDK开发,怀着好奇的心理, ...

  5. NDK开发之一

    2015.07.22 Wiki_Tree: --NDK开发: --NDK特征: --MK文件编写规则: NDK开发: Ndk-build编译时会生成的两个同名的so库,位于不同的目录/project ...

  6. NDK开发—基础知识实战Demo

    简介 前面写了几篇NDK相关的文章: NDK开发-简介&环境搭建(Eclipse,Android Studio) NDK开发-Android Studio+gradle-experimenta ...

  7. NDK开发-零散知识点整理

    JavaVM 标准Java平台下,每一个Process可以产生很多JavaVM对象,但在Android平台上,每一个Process只能产生一个Dalvik VM对象,也就是说在Android进程中是通 ...

  8. NDK开发-Android Studio+gradle-experimental开发ndk

    在最新的Android Studio2.2的preview版中,增加全新的ndk支持,使用了新的gradle,以及DSL语言. 新的NDK需要使用新的Gradle插件和新的Android插件来支持! ...

  9. Android之NDK开发(转)

    Android之NDK开发 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第 ...

  10. android ndk开发(二)实现一个官方demo

    实现了一个官方的demo:bitmap-plasma(水波纹) 源代码就在samples文件夹下,可以自己去找. 界面: 建立项目的步骤和配置环境不明白的可以去看:http://www.cnblogs ...

随机推荐

  1. 黄聪:No 'Access-Control-Allow-Origin' header is present on the requested resource解决办法

    在.htaccess文件里面添加下面代码: <IfModule mod_headers.c> Header set Access-Control-Allow-Origin "*& ...

  2. spring Transaction Propagation 事务传播

    spring Transaction中有一个很重要的属性:Propagation.主要用来配置当前需要执行的方法,与当前是否有transaction之间的关系. 我晓得有点儿抽象,这也是为什么我想要写 ...

  3. sharedPreference

    http://blog.csdn.net/yong199105140/article/details/8425247 SharedPreferences 分类: Android2012-12-24 1 ...

  4. QT学习之路--菜单、工具条、状态栏

    下面一些是 Menu Bar,用于显示菜单;再下面一点事 Toolbar areas,用于显示工具条,Status Bar,就是状态栏. Qt 提供了一个 QStatusBar 类来实现状态栏. Qt ...

  5. VS2010 项目引用了DLL文件,也写了Using,但是编译时提示:未能找到类型或命名空间名称 <转>

    昨天写了一个很小的winform程序,其中引用了自己写的两个dll文件. 本来认为轻松搞定,结果一编译居然提示:未能找到类型或命名空间名称..... 于是删掉两个dll重新引用,再编译结果依旧!很是郁 ...

  6. 剑指Offer:面试题27——二叉搜索树与双向链表(java实现)

    问题描述: 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表.要求不能创建任何新的结点,只能调整树中结点指针的指向. 思路: 将树分为三部分:左子树,根结点,右子树. 1.我们要把根结点与左 ...

  7. 转:MVC单表多按钮提交

    有时候会遇到这种情况:在一个表单上需要多个按钮来完成不同的功能,比如一个简单的审批功能. 如果是用webform那不需要讨论,但asp.net mvc中一个表单只能提交到一个Action处理,相对比较 ...

  8. EMR,电子病历(Electronic Medical Record)

    电子病历 电子病历(EMR,Electronic Medical Record),也叫计算机化的病案系统或称基于计算机的病人记录(CPR,Computer-Based Patient Record). ...

  9. 使用NuGet Package Project快速制作NuGet包

    今天在visual studio gallery发现了一个插件NuGet Package Project,通过它可以在Visual Studio中建立Nuget Package工程,直接生成Nuget ...

  10. 解决PHP在IE中下载文件,中文文件名乱码问题

    if( stripos($_SERVER['HTTP_USER_AGENT'], 'MSIE')!==false ) $filename = urlencode( $filename ); // 输入 ...