继续研究NDK

我在阿里云服务器上搭建了Android ndk的开发平台,并且借助这一平台研究了NDK的内部细节。

NDK提供了Android本地编程的接口,让你可以开发高效的依赖库,提高程序的速度,像是图像处理类和游戏类必然是少不了这个东西的,此外对于嵌入式开发者来说,有的时候需要绕过虚拟机直接跟内核通信那么就需要这个东西了。之前看到学长做的项目说,需要在Android上做一个音乐接口,但是翻遍了Android的api发现都实现不了,这个时候其实用ndk去调用底层的库,就可以跟内核的数据流建立连接,就可以做更多地事情。这样我们就可以利用Android兼容性能的同时,塑造更加高效的计算,或者是更加庞大的库。

jni.h提供了一系列的C接口来让我们在虚拟机和native lib之间辗转腾挪。language-agnostic这个词到底怎么翻译我也不好独断。

jni.h provides a thin C++ wrapper around the C API; the underlying calls are language-agnostic.基础调用是语言无关的。

wiki:

elfARM的可执行文件的格式是ELF格式文件,那么我们先看一下jni的函数库是什么样子的。当然了这里的函数库,指的是本地的二进制镜像。

readelf查看ELF文件格式:

ELF文件格式:研究这种东西有助于我们理解更加深奥的开发模型;

ProgramHeaders:

ELF函数头

ABI(application binary interface):

  • 数据类型的大小、布局和对齐;
  • 调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;
  • 系统调用的编码和一个应用如何向操作系统进行系统调用;

假如我们编写了一个待编译的文件hello-jni.c

那么编译命令是:Jni:ndk-build hello-jni.c,编译生成了libhello-jni.so。

这样我们就给Android程序定制了c语言实现的库

android-arch这里可以看到当前对arm支持的一些信息,通过ldd libhell-jni.so命令可以查看当前库的依赖。

编写ndk的项目配置文件,本文中的项目是最简单的hello程序:

android.mk

Generate libhello-jni.so

ABI也是很重要的概念,前面已经解释了。

编写第二个配置文件:Application.mk

编写平台:Target all platform such as arm-v7\x86\ ...

Add a mudule:LOCAL_LDLIBS += -lsth

这里是具体c文件编写的规则:

JNI函数声明:

JNIEXPORT <return> JNICALL Java_<package>_<class>_<function>(JNIEnv* env,jobject,<Args>)

例如:

JNIEXPORT void JNICALL Java_com_Cartoonifier_CartoonifierView_ShowPreview(

JNIEnv* env,jobject ,jint width,jint height,jbyteArray yuv,jintArray bgra){ }

JNIEnv:

native程序中频繁使用JNIEnv*和JavaVM*。而C和C++代码使用JNIEnv*和JavaVM*这两个指针的做法是有区别的,网上大部分代码都使用C++,基本上找不到关于C和C++在这个问题上的详细叙述。

在C中:

使用JNIEnv* env要这样      (*env)->方法名(env,参数列表)

使用JavaVM* vm要这样       (*vm)->方法名(vm,参数列表)

在C++中:

使用JNIEnv* env要这样      env->方法名(参数列表)

使用JavaVM* vm要这样       vm->方法名(参数列表)

我们来看一下和java连接的数据结构:

JNIEnv struct:

这个函数很有趣,体现了虚拟机平台的思想,需要什么类就去找什么类。我见过一种热更新Android系统的方案,那样不需要你去重启系统,利用的就是这个原理,去更改系统的find class的配置文件,然后用继承的思维,加入更新进去的库文件,并且向下兼容,就可以实现热更新。java虚拟机是一种我并不喜欢的思维,他试图用一个模型来解决所有问题。当你要解决问题的时候只需要加一个新类进去。

本地方法不能将JNIEnv从一个线程传递到另一个线程中。相同的 Java 线程中对本地方法多次调用时,传递给该本地方法的JNIEnv是相同的。但是,一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv。

Java通过JNI机制调用c/c++写的native程序。c/c++开发的native程序需要遵循一定的JNI规范,下面的例子就是一个JNI函数声明:

JNIEXPORT jint JNICALL Java_jnitest_MyTest_test

(JNIEnv * env, jobject obj, jint arg0);

JVM负责从Java Stack转入C/C++ Native Stack。当Java进入JNI调用,除了函数本身的参数(arg0),会多出两个参数:JNIEnv指针和jobject指针。

JNIEnv指针是JVM创建的,用于Native的c/c++方法操纵Java执行栈中的数据,比如Java Class, Java Method等。

首先,JNI对于JNIEnv的使用, 提供了两种语法: c语法以及c++语法,如下:

c语法:

jsize len = (*env)->GetArrayLength(env,array);

c++语法:

jsize len =env->GetArrayLength(array);

(注:由于C语言并不支持对象的概念,所以C语法中需要把env作为第一个参数传入,类似于C++的隐式参数this指针).

另外: JNIEnv有几个设计的原则:

第一、JNIEnv指针被设计成了Thread Local Storage(TLS)变量,也就是说每一个Thread, JNIEnv变量都有独立的Copy。你不能把Thead#1使用的JNIEnv传给Thread#2使用。

第二、JNIEnv中定义了一组函数指针,c/c++ Native程序是通过这些函数指针操纵Java数据。这样设计的好处是:你的c/c++ 程序不需要依赖任何函数库,或者DLL。由于JVM可能由不同的厂商实现,不同厂商有自己不同的JNI实现,如果要求这些厂商暴露约定好的一些头文件和库,这不是灵活的设计。

而且使用函数指针表的另外一个好处是: JVM可以根据启动参数动态替换JNI实现。

需要强调的是JNIEnv是跟线程相关的。

这些都是数据接口;

调用JavaVM接口:

第一种方式,在加载动态链接库的时候,JVM会调用JNI_OnLoad(JavaVM* jvm, void* reserved)(如果定义了该函数)。第一个参数会传入JavaVM指针。

第二种方式,在native code中调用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)可以得到JavaVM指针。两种情况下,都可以用全局变量,比如JavaVM* g_jvm来保存获得的指针以便在任意上下文中使用。Android系统是利用第二种方式Invocation interface来创建JVM的。

/*

JavaVM *jvm=0;

JNIEnv *env=(JNIEnv *)p;

env->GetJavaVM(&jvm);

*/

调用JNIEnv接口:

在native method中,JNIEnv作为第一个参数传入。那么在JNIEnv不作为参数传入的时候,该如何获得它

JNI提供了两个函数:

(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL)

(*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2)

两个函数都利用JavaVM接口获得JNIEnv接口,上面已经讲到如何获得JavaVM接口。JNI规范也说明,可以将获得JNIEnv封装成一个函数。

JNIEnv* JNU_GetEnv()

{

JNIEnv* env;

(*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_2);//C风格

//jint result = g_jvm->GetEnv((void **) &env,JNI_VERSION_1_2);

return env;

}

JNI_VERSION

下面是来自stackoverflow的一段代码:

#include <jni.h>

#include <iostream>

#include <pthread.h>

using namespace std;

JNIEnv* getEnv(JavaVM *jvm)

{

JNIEnv *env = 0;

jint result = jvm->GetEnv((void **) &env, JNI_VERSION_1_6);//C++

if (result != JNI_OK)

{

JavaVM

    /*虚拟机的线程技术值得深入研究*/

result = jvm->AttachCurrentThread((void **) &env, NULL);

//struct JNIInvokeInterface

if (result != JNI_OK)

{

cout << "Failed to attach current thread " << pthread_self() << endl;

}

else

{

cout << "Successfully attached native thread " << pthread_self() << endl;

}

// ...and register for detach when thread exits

int result = pthread_setspecific(key, (void *) env);

if (result != 0)

{

cout << "Problem registering for detach" << endl;

}

else

{

cout << "Successfully registered for detach" << endl;

}

}

return env;

}

static pthread_key_t key;

static pthread_once_t key_once;

接下来看一下ndk的c函数库,我相信你看到了很多你想要的东西,就连编写高性能服务器的poll.h都出现了。elf可以查看动态链接库的格式。这些函数都太底层了,大部分可能直接和内核通信。安全性更加重要。

NDK platform/android-19/*

NDK.inlcude\c\c++

JNIEnv使用方法:

参数声明 ...jintArray bgra,jbyteArray yuv...

/*......*/

jbyte* _yuv=env->GetByteArrayElements(yuv,0);//在JVM中获取图像数据

jint* _bgra=env->GetIntArrayElements(bgra,0);

/*......*/

env->ReleaseIntArrayElement(bgra,_bgra,0);

env->ReleaseByteArrayElements(yuv,_yuv,0);

问题是:从这里看到指向的是像素缓冲区,也就是java的对象数据,那么就不是copy,那么多线程调用TLS是怎么实现的?指针发生了竞争怎么办?还是就是拷贝,如果是内存拷贝又是怎么改变原实例的数据的。

下面的这个东西是多语言编译器,试图实现多种语言的混合编程。ndk是需要存在的,因为我们开发者有这种需要。不然我们真的就找不到办法去实现windows phone的那种性能了。即使Android虚拟机一直在优化。

引用:

SWIG (Simplified Wrapper and Interface Generator)

Tagline: SWIG is a compiler that integrates C and C++ with languages
         including Perl, Python, Tcl,
Ruby, PHP, Java, C#, D, Go, Lua,
         Octave, R, Scheme (Guile,
MzScheme/Racket, CHICKEN), Scilab,
         Ocaml, Modula-3, Common Lisp
(CLISP, Allegro CL, CFFI, UFFI)
         and Pike. SWIG can also export
its parse tree into XML and
         Lisp s-expressions.

来自 <https://github.com/swig/swig>

继续研究NDK的更多相关文章

  1. android NDK debug 遇到的问题与解决方法

    最近在研究android  NDK 的eclipse调试,遇到点问题,总结一下: 1.Unknown Application ABI :在application.mk里面添加APP_PLATFORM ...

  2. 史上最易懂的Android jni开发资料--NDK环境搭建

    谷歌改良了ndk的开发流程,对于Windows环境下NDK的开发,如果使用的NDK是r7之前的版本,必须要安装Cygwin才能使用NDK.而在NDKr7开始,Google的Windows版的NDK提供 ...

  3. windows系统上安装与使用Android NDK r5 (转)

    windows系统上安装与使用Android NDK r5  很早就听说了android的NDK应用,只是一直没有时间去研究,今天花了点时间在windows平台搭建了NDK环境,并成功运行了第一个简单 ...

  4. Android NDK中的C++调试踩坑标记

    RT, Android NDK中的C++调试, GDB调试比较麻烦,在ADT Eclipse中: 1.配置好NDK给工程加上Native Support 2.编译中加上NDK_DEBUG=1 3.然后 ...

  5. Android jni开发资料--NDK环境搭建

      谷歌改良了ndk的开发流程,对于Windows环境下NDK的开发,如果使用的NDK是r7之前的版本,必须要安装Cygwin才能使用NDK.而在NDKr7开始,Google的Windows版的NDK ...

  6. Eclipse NDK 配置

    一.关于NDK:NDK全称:Native Development Kit. 1.NDK是一系列工具的集合. NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java ...

  7. Linux命令行下编译Android NDK的示例代码

    这几天琢磨写一个Android的Runtime用来加速HTML5 Canvas,让GameBuilder+CanTK 不但开发速度快,运行速度也能接近原生应用.所以花了点时间研究 Android ND ...

  8. JNI与NDK简介

    最近稍微了解一下JNI和NDK. 网上各种教程给人一种二者不分的感觉, 经过自己运行代码, 将两者的关系理了一下. 就目前了解,JNI应该是java自带的一种调用c和c++等语言(native cod ...

  9. NDK编译FFMpeg[Linux]

    最近在研究视频直播相关的技术,了解到了FFmpeg,就在网上查看如何将FFmpeg移植到Android中,查了几天,看的东西不少,就是没有一个可以完全移植成功的,最后通过产看各种资料,结合网上的资料, ...

随机推荐

  1. 按要求编写Java应用程序。 (1)创建一个叫做机动车的类: 属性:车牌号(String),车速(int),载重量(double) 功能:加速(车速自增)、减速(车速自减)、修改车牌号,查询车的载重量。 编写两个构造方法:一个没有形参,在方法中将车牌号设置“XX1234”,速 度设置为100,载重量设置为100;另一个能为对象的所有属性赋值; (2)创建主类: 在主类中创建两个机动车对象。 创建第

    package com.hanqi.test; public class jidongche { private String chepaihao;//车牌号 private int speed;// ...

  2. Redis安装和配置

    1.下载安装redis 在linux服务器上,命令行执行以下命令(cd ./usr local/src 一般源码放在这里(推荐源码安装)) wget http://download.redis.io/ ...

  3. ubuntu14.04下的NVIDIA Tesla K80显卡驱动的安装教程

    搞深度学习如何能够不与浑身是“核”的显卡打交道呢? 人工智能的兴起除了数据量的大量提升,算法的不断改进,计算能力的逐步提高,还离不开软件基础设施的逐步完善.当下的主流的深度学习工具软件无论是Caffe ...

  4. linux权限补充:rwt rwT rws rwS 特殊权限

    众所周知,Linux的文件权限如: 777:666等,其实只要在相应的文件上加上UID的权限,就可以用到加权限人的身份去运行这个文件.所以我们只需要将bash复制出来到另一个地方,然后用root加上U ...

  5. ELF Format 笔记(八)—— 符号的类型和属性(st_info)

    我是天空里的一片云,偶尔投影在你的波心,你不必讶异,更无须欢喜,在转瞬间消灭了踪影.你我相逢在黑夜的海上,你有你的,我有我的,方向:你记得也好,最好你忘掉,在这交会时互放的光亮! —— 徐志摩·偶然 ...

  6. C# ADO.NET编写简单的图书馆管理软件

    使用软件: Microsoft SQL Server 2012 Microsoft Visual Studio 2012 本文地址: http://www.cnblogs.com/go2bed/ 参考 ...

  7. sqlserver 用 RowNumber 分组

    SELECT RECORD.[RECORD_ID] ,RECORD.[WORKFLOW_INFO_ID] ,RECORD.[FORM_CODE] ,RECORD.[APPLY_DATE] ,RECOR ...

  8. SonarLint(Sonar) 代码质量管理

    一.Sonar是什么? Sonar是一个用于代码质量管理的开源平台,用于管理源代码的质量 通过插件形式,可以支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groov ...

  9. POJ2955Brackets[区间DP]

    Brackets Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 6585   Accepted: 3534 Descript ...

  10. NOIP2009pj道路游戏[环形DP 转移优化 二维信息]

    题目描述 小新正在玩一个简单的电脑游戏. 游戏中有一条环形马路,马路上有 n 个机器人工厂,两个相邻机器人工厂之间由一小段马路连接.小新以某个机器人工厂为起点,按顺时针顺序依次将这 n 个机器人工厂编 ...