Android NDK

Android NDK初探

2017-05-09  本文已影响79人  码晒客

之前对NDK开发一直是个小白,最近花了几天时间研究得到的一些理解在此做个记录分享。结论不足之处拒绝反驳,所有观点仅单方面宣布,后果自负。*.*,本文出处:http://www.jianshu.com/p/201046751a7c

一、什么是NDK?

NDK全称是Native Development Kit(原生开发工具包),NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。也就是说它是一个“开发工具包”,就像SDK一样,区别就在于SDK是面向java开发者的工具集合,而NDK面向的则是C/C++开发者的工具集合(包括对c/c++源码的打包编译工具ndk,一些h头文件等)。附上官方NDK工具包的下载路径:官网ndk下载,需要翻墙。

二、为什么需要使用NDK?

1.代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。
2.可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。
3.提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。
4.便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。

三、JNI、SO介绍

JNI 全称Java Native Interface,这套技术的机制是用于java访问c/c++代码而产生的,说白了NDK开发的核心就是JNI开发,利用java代码来调用遵循JNI规范的c/c++的方法实现某个功能。

so全称Shared Object,本地原生库,先暂时理解为java中的jar包,所有的c/c++的代码在android(Linux)平台中最终都会编译成so库,然后才能被调用。所以ndk开发所编写出的c/c++代码最终的目的都是为了获得这个so库,与java方法形成jni的映射关系从实现调用。

四、开始撸码

本文使用Android Studio2.0进行演示HelloJni

  大概步骤:

     1. java文件中声明native方法,和java方法声明一样,在此基础上加了natvie修饰。
     2. 利用javah命令生成与该类对应的头文件(包含方法信息)
     3. 根据头文件的信息编写c源代码文件
     4. 在app\build.gradle文件中配置ndk的编译信息
     5. 配置NDK工具包路径,编译运行

创建项目:HelloJni

图 1

1. 定义一个java类SayHello,并在里面声明一个静态无参native方法speak,并且创建jni文件夹

图 2

2. 利用javah命令生成与该类对应的jni头文件,生成的头文件的目的主要是用来编写c/c++源文件

图 3

3.根据.h头文件的信息编写c源代码文件 : 创建SayHello.c文件,把头文件里的方法copy到该文件中,并修改成实体方法,下面则是返回一段字符串。如果熟悉了jni方法名称命名规范,完全可自己手写,生成头文件的步骤也可跳过。亲测发现如果包名带有数字的命名规则不好把握,所以建议用javah生成。

图 4

4.在build.gradle中配置ndk的编译信息,配置完成保存同步之后可能出现错误,添加 android.useDeprecatedNdk=true 到gradle.properties 文件中即可解决。

图 5

5.配置下载好的NDK工具包:File->Project Structure->SDK Location(文件路径\android-ndk-r14b目录配置到系统环境变量中,以备后面使用)

图 6

然后回到在java文件中,加载buil.gradle中配置的moduleName的类库名称,这里配置为:SayHello

图 7

最后在MainActivity中测试该方法。

图 8

运行。

图 9

至此,体验了一把基本的ndk开发过程。不过洗脑还没有结束:

在运行完成之后,我们并没有发现工程目录中有so库文件,其实这个so库文件是在运行之后直接打包到了apk文件中的lib目录下了

图 10

由于我们在build.gradle配置了abiFilters打包时只打包x86的文件夹中的so库。所以我们在apk中只看到x86的文件夹,里面存放的就是so库,如果不配置abiFilters,那么将会出现android支持的7种abi,可参见该文章理解ABI。

因为我们可以调用so这个库,显然这个so库是根据我们在jni文件夹下编写的源文件编译生成的,如果我们没有配置,gradle默认就会去编译jni的文件夹下的c/c++的代码生成so库,这个路劲就是src/main/jni,如果这个文件夹没有文件即使配置了ndk{...}信息也不会生成so库,当然gradle还提供自定义配置,下面就看看如何配置:

sourceSets{
  main{
    jin.srcDirs=["src/mian/jni"]  //默认路径,jin.srcDirs指的是需要加入编译的jni的路径,可以自己修改路径的
  }
}
图 11

这个apk安装到x86 abi手机上之后,so库会安装在data\app\包名-数值\ib目录下(可通过Device Monitor工具查看),所以由此可判断System.loadLibrary()加载的库默认是这个路径下的库,也可以调用System.load(data\app\包名-数值\ib\abi\libxx.so)加载绝对路径的so库。

图 12

所以java代码能不能正确的执行so库里的内容取决于so库能否被正确的安装。如果未能正确安装,当虚拟机去System.loadLibrary时就会报错java.lang.UnsatisfiedLinkError

上面的做法只有在打包时才能得到so库,下面就介绍通过ndk开发工具包里的ndk-build单独来编译出so库,这种方式就无需在build.gradle中配置ndk{...}了。

1. 在jni文件家中新建android.mk编译配置文件,参见Android.mk详细配置

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := SayHello
LOCAL_SRC_FILES := SayHello.c

include $(BUILD_SHARED_LIBRARY)

2.在jni文件家中新建application.mk配置需要生成支持的abi so库,参见Application.mk详细配置

APP_CFLAGS += -Wno-error=format-security
APP_ABI := all

这里配置支持所有的abi。

3.在terminal中调用ndk-build工具生成so库

图 13

我们可以看到在main文件夹下生成了libs目录,并且生成了支持所有abi的so库,到此生成so完毕;现在任务就是要让这些so库打包到apk文件中的libs目录下,在build.gradle中配置sourceSets的另一个属性jniLibs.srcDirs,配置的路径下的so库文件都会打包到apk文件中,其默认值为app/libs,所以也可以把这些so文件拷到app/libs中而不配置这个属性用其默认值。

图 14

上图中不配置jni.srcDirs的路径的作用是为了打包时不让编译系统再去编译得到so库(因为我们已经单独生成),虽然上面ndk没有被配置,但是只要的配置这个路径下有c文件就会生成so库,并且名字为libapp.so,这样一来就造成了相同的包存在两个增加app的体积。

最后build apk看看apk里有没有so库:

图 15

运行,大功告成。

下一篇文章介绍:第三方so库的调用

上一篇下一篇

猜你喜欢

热点阅读