Android Studio NDK开发案例一 JNI开发入门

        最近在做Android的项目,用到了JNI,现将NDK的开发流程和方法整理出来,希望能够让刚接触的小伙伴们少走一些弯路。

NDK

        Native Development Kit(NDK)是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C/C++的动态库,并能自动将so和java一起打包成apk。

JNI

        Java Native Interface(JNI)标准是java平台的一部分,JNI是Java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用C/C++代码,C/C++的代码也可以调用java代码。我们知道,不管是linux还是windows亦或是mac os,这些操作系统,都是依靠C/C++写出来的,还包括一些汇编语言写的底层硬件驱动 。java和C/C++不同 ,它不会直接编译成平台机器码,而是编译成虚拟机可以运行的java字节码的.class文件,所以效率就比不上C/C++代码,但是通过JNI技术可以即时编译成本地机器码。JNI调用示意图:


        从上图可以得知,JNI技术通过JVM调用到各个平台的API,虽然JNI可以调用C/C++,但是JNI调用还是比C/C++编写的原生应用还是要慢一点,不过对高性能计算来说,这点算不得什么,享受它的便利,也要承担它的弊端 。

JNI与NDK的关系

        NDK可以为我们生成了C/C++的动态链接库,JNI是java和C/C++沟通的接口,两者与android没有半毛钱关系,只因为安卓是java程序语言开发,然后通过JNI又能与C/C++沟通,所以我们可以使用NDK+JNI来实现“Java+C”的开发方式。

为什么要NDK开发

        NDK开发具有以下优点: 

       1. 项目需要调用底层的一些C/C++的一些东西(java无法直接访问到操作系统底层(如系统硬件等)),或者已经在C/C++环境下实现了功能代码(大部分现存的开源库都是用C/C++代码编写的。),直接使用即可。NDK开发常用于驱动开发、无线热点共享、数学运算、实时渲染的游戏、音视频处理、文件压缩、人脸识别、图片处理等。 
        2. 为了效率更加高效些。将要求高性能的应用逻辑使用C/C++开发,从而提高应用程序的执行效率。但是C/C++代码虽然是高效的,在java与C/C++相互调用时却增大了开销; 
        3. 基于安全性的考虑。防止代码被反编译,为了安全起见,使用C/C++语言来编写重要的部分以增大系统的安全性,最后生成so库(用过第三方库的应该都不陌生)便于给人提供方便。(任何有效的代码混淆对于会smail语法反编译你apk是分分钟的事,即使你加壳也不能幸免高手的攻击) 
        4. 便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。

NDK开发案例一   JNI开发入门

开发工具:Android Studio 2.3.3

NDK:android-ndk-r14b

 

1、打开Android Studio,选择“Start a new Android Studio procet”创建android工程,如下图:

2、根据项目需要修改"项目名称"、"公司域名"、"app包名"、"工程路径",勾选“Include C++ support”选项,该选项用于支持JNI开发,如下图:

3、默认勾选“Phone and Tablet”,根据项目需求选择兼容的Android最小版本。可点击“Help me choose”查看各版本功能。如下图:

4、选择项目活动类型,该案例默认选择“Basic Activity”模板,如下图:

5、设置“活动名称”、“界面布局名称"、"界面标题等",如下图:

6、设置JNI支持标准(有“Toolchain Default”和“C++11”两种标准可供选择),此项根据项目需求自行选择,该案例默认选择“Toolchain Default”,如下图:

选择“Finish”,等待工程创建完成,如下图:

可以看到编译窗口有报错:Error:A problem occurred configuring project ':app'.

先不去管错误,执行步骤7,配置NDK。

7、配置NDK。

        下载安装NDk后,在Android Studio菜单项选择File->Project Structure,配置NDK路径,如下图:

打开gradle.properties,添加

android.useDeprecatedNDK=true

如下图:

重新编译工程,报错(如上图):

        Error:(38, 13) Failed to resolve: com.android.support:design:28.+

        ......

        Error:(36, 13) Failed to resolve: com.android.support:appcompat-v7:28.+

        ......

 

Error解决办法:

        打开\MyApplication\app\build.gradle,在buildTypes {...}中添加代码repositories {...}

buildTypes {
    repositories {
        maven{
            url "https://maven.google.com"
        }
    }
    
    ......
}

如下图:

重新编译工程,未报错。

8、配置生成so库类型。

        打开\MyApplication\app\build.gradle,在cmake {...}中添加代码abiFilters "armeabi", "armeabi-v7a", "x86",如下:

    defaultConfig {
        ......

        externalNativeBuild {
            cmake {
                ......

                abiFilters "armeabi", "armeabi-v7a", "x86"//cpu的类型
            }
        }
    }

如下图:

        armeabi、armeabi-v7a和x86都表示CPU的类型。一般的手机或平板都是用arm的cpu。不同的cpu的特性不一样,armeabi就是针对普通的或旧的。arm v5 cpu,armeabi-v7a是针对有浮点运算或高级扩展功能的arm v7 cpu。

        armeabi:默认选项,将创建以基于ARM* v5TE 的设备为目标的库。 具有这种目标的浮点运算使用软件浮点运算。 使用此 ABI (二进制接口)创建的二进制代码将可以在所有 ARM*设备上运行。所以armeabi通用性很强。但是速度慢。

        armeabi-v7a:创建支持基于 ARM* v7 的设备的库,并将使用硬件 FPU 指令。armeabi-v7a是针对有浮点运算或高级扩展功能的arm v7 cpu。

        x86:支持基于硬件的浮点运算的。

        所以,如果项目只包含了 armeabi,那么在所有android设备都可以运行;如果项目只包含了 armeabi-v7a,除armeabi架构的设备外都可以运行; 如果项目只包含了 x86,那么armeabi架构和armeabi-v7a的Android设备是无法运行的;如果同时包含了 armeabi, armeabi-v7a和x86,所有设备都可以运行,程序在运行的时候去加载不同平台对应的so,这是较为完美的一种解决方案,同时也会导致包变大。

9、编写JNI接口程序。

在路径\MyApplication\app\src\main\cpp\下会看到文件native-lib.cpp,因为我们在创建android项目时勾选了C++ support,所以自动生成了JNI接口文件native-lib.cpp并附带一个JNI接口程序stringFromJNI,我们以后就在该文件里面编程JNI接口程序,此案例暂时不对其做修改,用其提供的JNI接口做演示,如下图:

//native-lib.cpp


#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_administrator_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

10、CMake

在Android Studio  2.2以上版本中,使用的是CMake来编译我们的C代码,如下图:

 如果你在cpp文件夹中新建了cpp文件头文件,就需要手动配置CMakeLists.txt文件。 IED自动生成的CMakeLists中默认添加的cpp文件只有native-lib.cppCMakeLists.txt中以#号开头都是注释,这里把它们都删了也就更清楚了,删除注释后的内容如下

add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

第一个参数为库名字,第三个参数为cpp文件路径。 如果你新建了一个Test.cpp,就需要把这个文件配置到CMakeLists中,但这样很麻烦。有没有一种可以自动包含cpp文件夹下源文件的方法呢?当然有,请看下面。

# 查找cpp目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(src/main/cpp/ DIR_LIB_SRCS)

# 生成链接库
add_library (native-lib SHARED ${DIR_LIB_SRCS})

# 导入cpp目录下的所有头文件
include_directories(src/main/cpp/)

你可以把IED生成的那段替换成这段,这样你以后在cpp目录下新建c++文件时,就不用手动配置了,只需要点击菜单栏Build->Refresh Linked C++ Projects,刷新后就可以在Android视图下的cpp中看到你新建的c++文件。

 

CMakeLists需要在\MyApplication\app\build.gradle中设置路径才能生效,如下图:

CMakeLists的路径设置代码是创建android项目时自动生成的,如果将CMakeLists移动到其它目录,只需在这里重新指定目录既可。

cmake入门:https://blog.csdn.net/weixin_40779546/article/details/84821923

11、生成so库文件。

编译工程,在目录\MyApplication\app\build\intermediates\cmake\debug\obj\下生成对应的so文件,如下图:

12、Java调用C代码,此处使用项目自动生成的例程做演示,如下图:

13、创建虚拟设备,如下图:

14、在选定的设备上运行app,显示app调用jni的打印消息"Hello from C++",如下图:

 

第三方so库的使用

        除了调用自己生成的so库外,我们还可能需要调用第三方的so库,Android Studio中和Eclipse中使用so库略有不同。 
我这里以Android Studio 2.3.3为例。 
       AS中默认是配置好so库的路径的,但是并没有给你生成相应的文件夹,所以首先需要新建文件夹。 
切换到Project视图,依次展开app->src->main,然后在main目录下新建一个jniLibs的文件夹,注意大小写和s,建议复制粘贴。 
然后点击菜单栏Build->Make Project,在切换回Android视图,就可以看到多出了一个jniLibs的目录。

  1. 把so库复制到jniLibs目录下,当然注意是放到对应的平台目录下,一般第三方so库提供的时候都会说明是那个平台,比如一般都是armeabi平台,即放到jniLibs/armeabi目录下。
  2. 在java中声明native方法,这里和自己写jni是一样了,唯一的区别是自己写的有cpp文件。所以这里写的native方法在IDE中会红色标识表明找不到对应cpp中的函数,但不要紧张,这不影响编译,也不影响运行。
     
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章