胡琪

为今天工作,为明天投资,为未来孵化一些东西

安卓NDK开发之Hello JNI

在讲NDK开发之前首先解释几个概念:

  • JNI:java native interface,java本地接口,我们知道java之所以能够做到跨平台就是因为java提供了一个java虚拟机来屏蔽掉和硬件相关的函数调用,我们写的java程序在java虚拟机上运行,但是很明显很多系统功能调用还是需要本地函数来完成(通常为C/C++代码),因此在java源码的很多代码中我们能够看到native关键字命名的函数,如java中的Object类存在大量的native函数,如:

    其中的native关键字就是用来说明该函数是jni函数,函数的声明为java代码,但是函数的具体实现是本地native代码,通常为C/C++代码,调用该函数最终调用的是native层的C/C++代码。
  • NDK:Native Development Kit,是一系列的本地开发工具包,前面说过JNI是在java层调用本地C/C++代码,这个过程比较复杂,而NDK就是谷歌公司开发的用来简化在安卓程序中使用JNI调用本地代码过程的工具。

为何需要NDK

主要包括以下几个原因:

  • 为了更安全:保护自己的App不那么轻易被人破解,因为安卓是基于java语言的,但是java代码编译为dex文件很容易通过逆向分析得到源代码,因此一些重要的涉及到核心技术的代码都不应该直接放在java层实现,一般都是通过C/C++代码生成的so来供安卓java层调用,因为逆向分析so代码的难度远高于分析java代码。
  • 对已经存在的本地代码的复用:任何一门语言都包括了大量的已经稳定的库,为了使用c/c++层已经成熟的一些功能库。
  • 为了更好的控制硬件:控制硬件的代码通常是用C语言编写的,因为C语言包含指针,可以更容易的访问内存。这一点java语言很难做到。

注意:使用Android Studio实际上很容易创建NDK工程,只需要在使用AS向导创建一个新工程的时候勾选include C++ support选项即可。但是此文打算先讲解下如何手动给已有的Android项目增加JNI代码,而不是利用向导直接创建,原因在文末会提到。但是在实际开发中肯定是推荐大家使用AS向导直接创建。

在已有的Android工程中手动增加JNI代码

假设我们已经有一个安卓工程了,现在打算给该安卓项目中的某些函数的实现放到native层。这里为了简单起见,安卓工程只包含一个Activity,整个Activity主界面只包括一个TextView,其内容从本地C++代码中的getString()函数获取的。

然后创建名为NDKTest的类,在该类中定义一个native函数getString(),使用System.loadLibrary(“ndk”);来加载so文件,其中so的名称可以随意,这里指定为”ndk”,代码如下:

然后打开app的build.gradle文件,在android/defaultConfig下面添加ndk节点,如图:

《安卓NDK开发之Hello JNI》

其中moduleName是最终生成的.so文件的名称,和前面System.loadLibrary(“ndk”)中保持一致,abiFilters用来说明生成哪些硬件架构的so,这里只是简单的包括x86和armeabi-v7a,关于对不同硬件架构的so的支持,大家可以参看http://kvh.io/cn/android-ndk-so.html ,ndk中的具体信息需要根据jni代码的使用情况来配置,上面只是一个简单的例子而已,但是一般都包含moduleName和abiFilters信息。

ndk信息配置好之后,依此点击Android Studio菜单栏的Build->Make Project.然后点击Terminal按钮打开终端面板,可以看到当前目录定位到了我们的项目所在的目录,然后在终端中输入如下命令:cd app/build/intermediates/classes/debug将目录切换到项目的app/build/intermediates/classes/debug目录下,之所以切换到该目录下,是因为我们接下来需要要执行javah命令来生成NDKTest类中native函数的JNI头文件,而javah命令会根据包名所在的路径来查找对应的.class文件,而当我们执行Build->Make Project之后,生成的.class文件会在app/build/intermediates/classes/debug目录下生成,如图:

《安卓NDK开发之Hello JNI》

所以我们需要切换到debug目录下,定位到debug目录下之后,输入命令:javah com.htq.baidu.ndk.NDKTest,即javah 包名+类名(其中类为定义了JNI函数的类),然后可以看到在debug目录下,为我们自动生成了JNI的.h文件,如图:

《安卓NDK开发之Hello JNI》

然后我们在整个工程上右键New->Folder->JNI Folder创建一个jni文件,位置默认即可,然后可以看到在main文件夹下产生了一个jni文件,然后我们把前面生成的jni的.h文件复制到该文件夹下,然后在该文件夹下创建一个.cpp的C++文件用来实现该头文件中的函数体,注意cpp文件名与头文件保持一致,在实现函数体的时候建议大家在书写函数名的时候直接从.h文件中复制过去,而不要手动输入,不然很容易出错的。此时工程主要结构如下:

《安卓NDK开发之Hello JNI》

其中.cpp中代码如下:

如果.cpp中jni.h出现红色,提示cannot find jni.h可能是ndk没配置,需要在File->Project Structure中选择你安装的ndk的路径,如图:

《安卓NDK开发之Hello JNI》

然后运行程序,可以看到界面显示hello.this is from native code,这句话正是我们在本地.cpp文件中返回的字符串,说明jni调用成功。如图:

《安卓NDK开发之Hello JNI》

然后我们依此打开app/build/intermediates/ndk/debug/lib目录可以看到自动生成了”x86″和”armeabi-v7a”两种架构的so文件,这两种架构就是前面在ndk节点的abiFilters中配置的信息,其中so的名称为libndk.so,即lib+前面在ndk节点的moduleName的信息+.so。如图:

《安卓NDK开发之Hello JNI》

然后我们用解压缩软件打开apk文件可以看到上面两种架构的so文件已经被打包到apk中了,这样使用android studio进行ndk开发的步骤就完成了。

使用android studio向导直接创建NDK工程

如果不是在一个已经存在的安卓工程中增加JNI模块,而是直接创建一个包含JNI模块的安卓工程的话,可以直接使用AS向导在创建一个新工程的时候勾选include C++ support选项即可。如下所示:

《安卓NDK开发之Hello JNI》

勾选该选项之后AS向导会为我们自动创建一个包含cpp目录的jni工程,该cpp目录下即是我们要进行JNI开发的c/c++代码,只需要在该cpp目录增加我们的native函数实现即可。

注意:在实际开发中推荐大家直接使用AS向导创建NDK工程。前面之所以手动带领大家讲解下如何生成native函数头文件是为了让大家初次接触NDK时对细节有更清楚的掌握。比如了解如何使用javah命令生成头文件能够让我们更清楚的知道native函数在JNI中静态注册的命名规则,函数参数/签名/返回值描述符等概念,方便后面学习JNI函数的动态注册。

 

打赏

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注