Android Studio中的NDK开发

读书笔记:《Android 高级进阶》

NDK简介

NDK 是 Native Developmentit的缩写,是Google在Android开发中提供的一套用于快速创建native工程的一个工具。
使用这个工具可以很方便的编写和调试JNI的代码。

  • NDK是一系列工具的集合

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

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

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

为什么使用NDK,优点是什么?

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

ABI的基本概念

早起的Android系统几乎只支持ARMv5的CPU架构,而发展到现在,Android系统目前支持一下7种不同的CPU架构

  • ARMv5
  • ARMv7(从2010年起)
  • x86(从2011年起)
  • MIPS(从2012年起)
  • ARMv8
  • MIPS6
  • x86_64(从2014年起)

每一种架构关联着一种ABI,那么什么是ABI呢?ABI是Application Binary Interface的缩写,即应用程序二进制接口,它定义了二进制文件(Android平台上专指.so文件)如何运行在相应的系统平台上,包括使用的指令集、内存对齐到可用的系统函数库。在Android系统上,每一个CPU架构对应一个ABI,如前所述,总共有7种,对应到Android Studio中的目录结构如下

    [module_name]
        [src]
            [main]
                [jniLibs]
                    [armeabi]
                    [armeabi-v7a]
                    [x86]
                    [mips]

注意jniLibs目录是放在module下面,在Android Studio中效果如下:

这里写图片描述

在Android Studio中为工程添加C++代码的方式有两种。

  • 引入预编译的二进制(自己编译好或者第三方提供的)
  • 在Android Studio中直接从C/C++源码编译

引入预编译的二进制C/C++函数库
添加预编译的二进制库很简单,默认情况下,Android Studio会到jniLibs目录中查找并拷贝所有的二进制库。假设我们在上面每个ABI目录中都有一个名为libhellondk.so的二进制库,在Android中要使用这个库很简单。

String libName = "helloNDK"; // 库名, 注意没有前缀lib和后缀.so  
System.loadLibrary( libName ); 

直接从C/C++源码编译
Android Studio中对C/C++源码编译成.so文件的步骤主要如下:

  • 配置ndk.dir变量
  • 在Gradle中配置NDK模块
  • 添加C/C++文件到指定目录

配置ndk.dir变量
进行NDK开发第一件要做的事情就是打开工程根目录的local.properties文件,并在其中配置ndk的目录,以便让Android Studio能够做到NDK的可执行文件等。

sdk.dir= /Users/guhaoxin/Library/Android/sdk
ndk.dir= /Users/guhaoxin/Library/Android/ndk

在Gradle中配置NDK模块
为了让Gradle对C/C++代码进行编译,需要配置module的build.gradle文件,打开build.gradle,在其中defaultConfig段落中添加如下代码:

ndk{
    moduleName "moduleName"
}

其中,moduleName要替换成我们自己的C/C++模块名,例如某个NDK项目中配置如下

android {  
    compileSdkVersion 20
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "com.example.ndksample"
        minSdkVersion 9
        targetSdkVersion 20
        versionCode 1
        versionName "1.0"

        ndk {
            moduleName "helloNDK" // <-- This is the name of my C++ module!
        }
    }
    // ... more gradle stuff here ...
} // end of android section

ndk还可以配置更多选项,如下:

ndk {  
    moduleName "myEpicGameCode"
    cFlags "-DANDROID_NDK -D_DEBUG DNULL=0"   // Define some macros
    ldLibs "EGL", "GLESv3", "dl", "log"       // Link with these libraries!
    stl "stlport_shared"                      // Use shared stlport library
}

添加C/C++文件到指定的目录
默认情况下,Gradle会到:

[module]/src/main/jni/

效果如下图:

这里写图片描述

目录中查找C/C++文件进行编译,我们只需要把C/C++文件放到这个目录中即可。当然我们也可以修改jni的目录,例如希望把C/C++文件放到source目录中,可以修改gradle文件如下:

android {

  // .. android settings ..

  sourceSets.main {
      jni.srcDirs 'src/main/source'
  }
}

分平台配置编译(可选)
这一步不是必须的,你可以根据需要,对各个平台进行不同的编译配置,可以设置覆盖前面的编译选项(例如cFlags)。例如你只想编译指定平台的.so,而不是所有的平台。如下:

android {

  // .. android settings ..

  productFlavors {
        x86 {
            ndk {
                abiFilter "x86"
            }
        }
        arm {
            ndk {
                abiFilter "armeabi-v7a"
            }
        }
        mips {
            ndk {
                abiFilter "mips"
            }
        }
    }

} // android

使用.so文件的注意事项

处理.so文件时有一条简单却不知名的重要法则:你应该尽可能地提供专为每个ABI优化过的.so文件,要么全部支持,要么都不支持。我们不应该混合着使用,而应该为每个ABI目录提供对应的.so文件。

使用高平台版本编译的.so文件运行在低版本的设备上

这是大家习以为常的做法,但是这是错误的。因为NDK平台不是向后兼容的,而是向前兼容的。推荐使用App的minSdkVersion对应的编译平台,因此,当我们引入一个预编译好的.so文件时,首先需要检查它被编译所用的平台版本。

混合使用不同的C++运行时编译的.so文件

.so文件可以依赖于不同的C++运行时,静态编译或者动态加载。混合使用不同版本的C++运行时可能导致很多奇怪的Crash。作为一个经验法则,当只有一个.so文件时,静态编译C++运行时是没有问题的,但存在多个.so文件时,应该让所有的.so文件都动态链接相同的C++运行时。

没有为每个支持的CPU架构提供对应的.so文件

这一点前文已经说到,但应该特别注意它,它可能发生在根本没有注意的情况下。例如:你的APP支持armeabi-v7a和x86架构,然后使用Android Studio新增一个函数库依赖,这个函数库包含.so文件并支持更多的CPU架构,例如新增android-gif-drawable函数库。

compile 'pl.droidsonroids.gif:android-gif-drawable:1.1.+'

发布我们的APP后,发现在某些设备上会发生Crash。解决办法是重新编译我们的.so文件使其支持确实的ABIs,或者设置

ndk.abiFilters

显示地指定支持的ABIs。

将.so文件放在错误的地方

  • Android Studio工程放在jniLibs/ABI目录中(当然也可以通过在build.gradle文件中的设置jniLibs.srcDir属性自己指定)。
  • Eclipse工程放在libs/ABI目录中(这也是ndk-build命令默认生成.so文件的目录)。
  • AAR压缩包中位于jni/ABI目录中(.so文件会自动包含到引用AAR压缩包的APK中)。
  • 最终APK文件中的lib/ABI目录中
  • 通过PackageManager安装后,在小于Android 5.0的系统中,.so文件位于APP的nativeLibraryPath目录中;在大于等于Android 5.0的系统中,.so文件位于APP的nativeLibraryRootDir/CPU_ARCH目录中。

只提供armeabi架构的.so文件而忽略其他ABIs的

所有的x86/x86_64/armeabi-v7a/arm64-v8a设备都支持armeabi架构的.so文件,因此,似乎移除其他ABIs的.so文件时一个减少APK大小的好技巧。但事实上并不是:这将影响到函数库的性能和兼容性。

以减少APK包大小为由是一个错误的借口,因为你可以选择在应用市场上传指定ABI版本的APK,生成不同ABI版本的APK。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章