NDK简介

来源:互联网 发布:长歌门成男捏脸数据 编辑:程序博客网 时间:2024/06/05 12:47

 

NDK简介

 

         The NDK is a toolset that allows you to implement parts of your app using native-code languages such as C and C++. For certain types of apps, this can be helpful so you can reuse existing code libraries written in these languages, but most apps do not need the Android NDK.

        NDK是一个工具集,允许你在你的工程里使用本地语言代码比如说CC++,对于一些特定的工程,NDK对于重用已存在的本地库很有帮助,但对于大多数工程来说并不需要NDK

        特别注意,使用NDK并不能让大多数的工程提高效率,作为一个开发者,应该要平衡效益及缺点,尤其是当使用NDK并不能得到显著改善的时候,它往往会增加程序的复杂程度。一般来说,除非程序必须使用NDK才用它,一定不能因为自己偏好CC++就使用NDK

Android NDK是配合 Android SDK的工具,Google推出NDK的目的不是为了取代Android SDK ,当然也不可能完全取代,它只是作为Android SDK 的一个补充。用来编译应用的原生代码。

  1NDK是一系列工具的集合。

  * NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将sojava应用一起打包成apk。这些工具对开发者的帮助是巨大的。

  * NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出哪些文件需要编译编译特性要求等),就可以创建出so

  * NDK可以自动地将soJava应用一起打包,极大地减轻了开发人员的打包工作。

  2NDK提供了一份稳定、功能有限的API头文件声明。

Google明确声明该API是稳定的,在后续所有版本中都稳定支持当前发布的API。从该版本的NDK中看出,这些API支持的功能非常有限,包含有:C标准库(libc)、标准数学库(libm)、压缩库(libz)、Log库(liblog)、图形处理库(libjnigraphics)。

 

NDK环境搭建

        

        以下只对NDKLinux下进行说明,由于大家都android源码开发环境,所以并不需要单独再去下载NDK,只需要将make文件书写正确,直接执行mm即可。

        

 

 

Android.mk文件编写

        

        Android.mk文件用于描述如何编译源码。但它作为为一个小型的Makefile文件,应尽量少申明变量。接下来看一个简单的例子。

----------------------------------------------------

        LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE   := hello-jni

LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

------------------------------------------------------

        LOCAL_PATH := $(call my-dir)

        Android.mk文件必须在开头位置定义LOCAL_PATH变量,它用于定位源码位置。在些例中,宏定义my-dirNDK编译系统定义,用于指出当前文件夹的路径。

        Include$(CLEAR_VARS)

        CLEAR_VARS是由NDK编译系统定义并指向一些特殊的GUN Makefile,它会为用户清理除LOCAL_PATH以外的许多LOCAL_XXX的变量,例如LOCAL_MODULE等等,这个步骤是必须的,因为所有的Makefile是在同一个GUN Make程序中执行,所以的变量都是全局的。

        LOCAL_MODULE := hello-jni

        LOCAL_MODULE变量必须定义用来声明模块名,这个name必须是唯一的并且不能包含空格,注意,系统会自动为生成的文件(也就是so文件)添加前缀和后缀。在本例中最终生成的so文件名为libhello-jni.so。如果将模块名申明为’libhello-jni’,那么最终生成的so文件名字将为libhello-jni.so,并不会再额外加一个前缀。

        LOCAL_SRC_FILES := hello-jni.c

        LOCAL_SRC_FILES变量必须包含即将打包生成so文件的CC++文件。请注意,不需要把头文件列入其中。

Include $(BUILD_SHARED_LIBRARY)

        BUILD_SHARED_LIBRARY是由NDK编译系统定义的,它指向一个GUN Makefile 脚本,主要管理从最近的申明“Include$(CLEAR_VARS)”处开始收集所有的变量信息(例如LOCAL_XXX),并决定如何编译等问题。

 

JNI数据类型

        NDKGoogleJNI的一种良好封装,以便于开必者更好地使用JNI。关于数据类型NDKJNI是完全一样的,所以此处开头为JNI数据类型。下边用一张图表来描述JNI中的基本数据类型。

 

        基本数据类型只是在名字上有所变化,统一加了‘j’,JNI数据类型和标准的CC++数据类型有较大区别,例如标准C语言中是没有boolean数据类型的,那么如何在C语言中为jboolean类型赋值呢?

        由于jbooleanC语言中实际上是unsigned 8 bitsC语言中一般char类型只占一个字节,因此可以是这样理解。

        Typedef  unsigned  char  jboolean

        所以可以用下列代码获取jboolean

        -------------------------------------------------

        unsigned char b = 1

        jboolean bool = (jboolean)b

        --------------------------------------------------

        其它在标准C语言中没有的类型也可通过上边的方法得到。如果要得到java中的引用数据类型那就需要另一种方法了,无法像上边这样用“=”赋值再强制转型获得。下图展示了JNI引用类型和java中的引用类型的对应关系。

        

        下边分别用代码展示如何获取jobjectjclassjstring对象。

-----------------------------------------------------------------

JNIEXPORT jobject JNICALL

Java_com_ndk_NDKFirstActivity_getData(JNIEnv *env , jobject obj , jint ji)

{
//
根据类名,获取类

   jclass dataClass = (*env)->FindClass(env ,"com/ndk/Data");

   if(dataClass == NULL)

   {

       return;

   }

   //根据类获取方法字段

   jmethodID cid = (*env)->GetMethodID(env ,dataClass ,"<init>" ,"(I)V");

   if(cid== NULL)

   {

       return;

   }

   LOGI("i init and return a construct in native c");
//根据类、构造方法字段初始化类对象

   return (*env)->NewObject(env ,dataClass ,cid , ji);

}

获取jstring

(*env)->NewStringUTF(env, "Hello from JNI !");

----------------------------------------------------------------------------

        获取jclassjmethodIDjobject类似java中的反射,jclass可通过包名加类名获取,jmethodID代表着方法,本例获取的是构造方法,也是通过方法名获取,”<init>”即方法名,”(I)V”为方法签名,I代表着类型为int的型参,V代表方法。标准C语言中没有string类型,只有用char数组表示,在本例中可以用char数组生成jstring类型。值得注意的是,上面的代码也说明了可以在C语言中反调java中的函数。

        在例子中声明的方法Java_com_ndk_NDKFirstActivity_getData有一定的命名规则,Java前缀,后接包名加类名,最后接方法名,中间以下划线间隔,后边的参数里envobj均不是java本地方法中声明的型参,env可以理解成“整个JNI环境”,obj则是调用此本地方法的类的对象。

 

JNI调用流程

        Dalvik虚拟机成功加载库之后,就会自动地寻找库里面的JNI_OnLoad函数,这个函数用途如下:

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

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

 

NDK代码示例

 

        NDK使用流程一共以下几个步骤:

        一:在java文件中声明native方法,并加载so

        二:用CC++实现native方法

        三:编写Android.mk文件

        四:编译生成so

        五:在java工程目录加把生成的so库添加进来并修改相应的make文件

六:编译java工程,执行

 

Java文件代码:

-------------------------------------------------------------------------

public class HelloJni extends Activity

{

   public void onCreate(Bundle savedInstanceState)

   {

       super.onCreate(savedInstanceState);

       TextView  tv = new TextView(this);

       tv.setText( stringFromJNI() );

       setContentView(tv);

   }

   public native String  stringFromJNI();

   static {

       System.loadLibrary("hello-jni");

   }

}

------------------------------------------------------------------------------

本例中声明了两个native方法,并把加载so文件语言“System.loadLibrary("hello-jni")”放在了static语句块中,static语句块会在onCreate方法之前执行。再看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 !");

}

------------------------------------------------------------------------

        C语言中对本地方法的实现相对简单,方法命名以及envthiz这两个对象之前已经和大家介绍过了,返回的jstring并不是用“=”赋值得来的,而是通过调用NewStringUTF方法得到。

        Android.mk文件

------------------------------------------------------------------------------

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE   := hello-jni

LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

-------------------------------------------------------------------------------

        由于本例中没有用到NDK提供的额外API,比如说Log库及图形库的一些函数,所以Android.mk相对简单,之前已经详细解释过了,如果要用到那些库,只需要添加一二语句即可。

        接下来即是如何编译生成so库的问题了,把Android.mk文件以及实现本地方法的c文件单独放到一个文件夹中,此文件夹名一般约定俗成名为jni,进入文件夹,用mm编译即可。生成的so文件位于out\target\product\msm8660_surf\system\lib中,复制相应so文件到相关的java工程的libs目录的armeabi目录下。

        接下来修改相关java工程的mk文件即可。在mk文件中添加LOCAL_JNI_SHARED_LIBRARIES := lib hello-jni即可。