Android NDK初探

作者: 码晒客 | 来源:发表于2017-05-09 18:19 被阅读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库的调用

    相关文章

      网友评论

        本文标题:Android NDK初探

        本文链接:https://www.haomeiwen.com/subject/mbgbtxtx.html