android NDK开发

前景补充:

  1. GUN是自由操作系统计划–GNU is Not Unix
  2. 1990有了GCC-GNU Compiler Collection和大部分UNIX系统的程序库和工具
  3. 1991年Linus Torvalds编写出了与UNIX兼容的Linux操作系统内核并在GPL条款下发布。
  4. GUN MAKE也是其中之一
  5. 操作系统屏蔽硬件差异(cpu架构等),软件只对应不同操作系统(再分个位数)

任务?:linux下写下.out.elf.a.so各种文件格式

关于ABI

ABI(Application Binary Interface): 应用程序二进制接口 描述了应用程序和操作系统之间,一个应用和它的库之间,或者应用的组成部分之间的低接口。
ABI涵盖了各种细节:

  1. 数据类型的大小、布局和对齐;
  2. 调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;
  3. 系统调用的编码和一个应用如何向操作系统进行系统调用;
  4. 以及在一个完整的操作系统ABI中,目标文件的二进制格式、程序库等等。

ABI不同于API ,API定义了源代码和库之间的接口,因此同样的代码可以在支持这个API的任何系统中编译 ,然而ABI允许编译好的目标代码在使用兼容ABI的系统中无需改动就能运行。 ABI掩盖了各种细节,例如:调用约定控制着函数的参数如何传送以及如何接受返回值;系统调用的编码和一个应用如何向操作系统进行系统调用;以及在一个完整的操作系统ABI中,对象文件的二进制格式、程序库等等。一个完整的ABI,像 Intel二进制兼容标准 (iBCS) ,允许支持它的操作系统上的程序不经修改在其他支持此ABI的操作系统上运行。其他的 ABI 标准化细节包括C++ name decoration和同一个平台上的编译器之间的调用约定,但是不包括跨平台的兼容性。在Unix的操作系统中,存在很多运行在同一件平台上互相相关但是不兼容的操作系统(尤其是80386兼容系统)。有一些努力尝试标准化A I,以减少销售商将程序移植到其他系统时所需的工作。然而,直到现在还没有很成功的例子,虽然LSB正在为Linux做这方面的努力。
一套完整的ABI(比如:Intel Binary Compatibility Standard (iBCS)),可以让程序在所有支持该ABI的系统上运行,而无需对程序进行修改。

也就是说,操作系统屏蔽硬件差异,但是程序执行除了api外还有许多其他约定:数据对齐,调用约定等。所以直接复制程序仍不一定能够执行。ABI是为了统一这些的。

api是系统屏蔽底层硬件的。使程序可以编辑一次多平台编译。abi是屏蔽运行环境的,使程序编译一次可以多平台运行

关于java-jni

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。
思考:关于jvm的实现过程。java命令在win下是exe。那么具体到那些函数是交给本地代码呢?已知有loadclass

jni步骤-win

  1. 编写native方法的类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package a;
    public class b{
    public native void testHello();
    public static void main(String[] args){
    System.loadLibrary("B");
    b jniDemo = new b();
    jniDemo.testHello();
    }
    }
  2. javac生成.class文件(java的一系列命令都在与包同目录下,而java的包已文件形式表示)具体命令为javac a/b.java 包下用/后加.java

  3. javah -classpath . -jni a.b 生成.h头文件用于实现(此处坑较多,网上一般的直接javah a.b在我这里都不好用后来这样全部指明才可以)
    javah -classpath . -jni a.b 解决。-classpath是加载类的路径,jni是生成的标准。(加载什么)注意包下用.且不加后缀。生成:a_b.h其只与包、类和方法名有关

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include "jni.h"
    /* Header for class a_b */
    #ifndef _Included_a_b
    #define _Included_a_b
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
    * Class: a_b
    * Method: testHello
    * Signature: ()V
    */
    JNIEXPORT void JNICALL Java_a_b_testHello
    (JNIEnv *, jobject);
    #ifdef __cplusplus
    }
    #endif
    #endif
  4. %JAVA_HOME%/include下的jni.h放于其下

  5. 编写随意名字的dll文件(java类中System.loadLibrary(“”);同名即可):B.cpp:

    1
    2
    3
    4
    5
    6
    7
    8
    #include "a_b.h"
    #include <iostream>
    #include <stdio.h>
    JNIEXPORT void JNICALL Java_a_b_testHello
    (JNIEnv *, jobject) {
    printf("this is C++ print");
    }
  6. 编译该dll,生成时指定平台为64位的(因为下的java是64的)

  7. 将dll置于.class同目录下。最终结果为俩文件:b.class与B.dll 由此可见.h文件其实可以不用自动生成,只是方便知道函数名字而已。
  8. java a.b成功

NDK

基本概念

  1. native-原生:指的是用原生代码编写android程序
  2. NDK中对应于不同cpu框架的固定平台c/c++代码,执行格式为linux下的.so与.a与.elf,采用的是交叉编译,生成运行于其它平台的代码
  3. 使用 NativeActivity 的 Android 应用仍会在其自己的虚拟机中运行,与其他应用以沙盒分隔。(android应用运行.so的过程?依靠虚拟机加载调用本地动态库.so?)
  4. 以前编写原生程序一般用的是ndk提供的NDK-build。当然也可以单纯使用gcc(了解项目管理工具与gcc的使用?android是make)AS使用构建原生库的默认工具是CMake,也可以用ndk-build
  5. 通过ABI指定生成的平台,默认下在lib中会生成多平台的.so(操作系统屏蔽底层的疑问见ABI说明)
  6. 与原生的jni不同的是,as中没有生成.h头文件,但仍需jni.h可见仍使用的是jni调用,但是省略了繁杂的步骤,交给编译器as直接确定.so

AS下编辑native—较新方法-使用cmake(常用!)

官方AS的文档十分详细。。
AS可以先写好java类,然后点击错误修正可以自动生成对应c文件与原生函数头。
AS生成的:注意as下如果是cpp文件extern “C” 必加
当你使用错误修正时是生成c版本的时自动不加,当是cpp文件中错误修正时自动加
CMake快速上手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#添加一个库,可用于目标
add_library( # 指定库的名称。生成后会加前后缀根据下面的设置
native-lib
# 将库设置为共享库。
SHARED
# 提供源文件的相对路径。可以多个文件编译为一个库
src/main/jni/native-lib.cpp )
#添加头文件
include_directories(src/main/cpp/include/)
#构建一个给自己用的库,从部分库是源码形式:
add_library( app-glue
STATIC
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )
#添加一个已有的库
add_library( imported-lib
SHARED
#表示这个已经存在,只要导入就可以,set_target_properties()指定路径
IMPORTED )
#指定目标库的位置
set_target_properties( # Specifies the target library.
imported-lib
# Specifies the parameter you want to define.
PROPERTIES IMPORTED_LOCATION
# Provides the path to the library you want to import.
imported-lib/src/${ANDROID_ABI}/libimported-lib.so )
#编译时定位标头文件
include_directories( imported-lib/include/ )
#使用NDKapi 部分库是源码形式包含
find_library( # find用来找到库,这个变量被赋予要找的库的路径
log-lib
# 这里是你要找的库的名字,该库已存在于android中
log )
#第一个参数是最终要生成的,其它都是需要连接进去的
target_link_libraries( # 指定目标库
native-lib
#连接已有的库
imported-lib
#连接自己建的库
app-glue
# 连接的android ndk平台已存在目标,将找到的路径传入即可
${log-lib} )

创建项目

创建支持原生代码的项目与创建任何其他 Android Studio 项目类似,不过前者还需要额外几个步骤:

  1. 在向导的 Configure your new project 部分,选中 Include C++ Support 复选框。点击 Next。
  2. 正常填写所有其他字段并完成向导接下来的几个部分。
  3. 在向导的 Customize C++ Support 部分,您可以使用下列选项自定义项目:
    C++ Standard:使用下拉列表选择您希望使用哪种 C++ 标准。选择 Toolchain Default 会使用默认的 CMake 设置。
    Exceptions Support:如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。
    Runtime Type Information Support:如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。
    点击 Finish。
目录
  1. 在 cpp 组中,您可以找到属于项目的所有原生源文件、标头和预构建库。对于新项目,Android Studio 会创建一个示例 C++ 源文件 native-lib.cpp,并将其置于应用模块的 src/main/cpp/ 目录中。本示例代码提供了一个简单的 C++ 函数 stringFromJNI(),此函数可以返回字符串“Hello from C++”。
  2. 在 External Build Files 组中,您可以找到 CMake 或 ndk-build 的构建脚本。与 build.gradle 文件指示 Gradle 如何构建应用一样,CMake 和 ndk-build 需要一个构建脚本来了解如何构建您的原生库。对于新项目,Android Studio 会创建一个 CMake 构建脚本 CMakeLists.txt,并将其置于模块的根目录中。且必须叫这个名字。
  3. cmake与ndk-build的配置都在build.gradle的管理配置下。
运行过程
  1. Gradle 调用您的外部构建脚本 CMakeLists.txt。
  2. CMake 按照构建脚本中的命令将 C++ 源文件 native-lib.cpp 编译到共享的对象库中,并命名为 libnative-lib.so,Gradle 随后会将其打包到 APK 中。
  3. 运行时,应用的 MainActivity 会使用 System.loadLibrary() 加载原生库。现在,应用可以使用库的原生函数 stringFromJNI()。
  4. MainActivity.onCreate() 调用 stringFromJNI(),这将返回“Hello from C++”并使用这些文字更新 TextView。
CMakeLists.txt
使用ndk库

Android NDK 提供了一套实用的原生 API 和库。通过将 NDK 库包含到项目的 CMakeLists.txt 脚本文件中,您可以使用这些 API 中的任意一种。

预构建的 NDK 库已经存在于 Android 平台上,因此,您无需再构建或将其打包到 APK 中。由于 NDK 库已经是 CMake 搜索路径的一部分,您甚至不需要在您的本地 NDK 安装中指定库的位置 - 只需要向 CMake 提供您希望使用的库的名称,并将其关联到您自己的原生库。

将 find_library() 命令添加到您的 CMake 构建脚本中以定位 NDK 库,并将其路径存储为一个变量。您可以使用此变量在构建脚本的其他部分引用 NDK 库。
动态加载:为了确保您的原生库可以在 log 库中调用函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令关联库。让你的.cpp文件可用ndk库
静态加载:NDK 还以源代码的形式包含一些库,您在构建和关联到您的原生库时需要使用这些代码。您可以使用 CMake 构建脚本中的 add_library() 命令,将源代码编译到原生库中。要提供本地 NDK 库的路径,您可以使用 ANDROID_NDK 路径变量,Android Studio 会自动为您定义此变量。

添加自己的文件

用 add_library() 向您的 CMake 构建脚本添加源文件或库
CMake 使用以下规范来为库文件命名:
lib库名称.so
例如,如果您在构建脚本中指定“native-lib”作为共享库的名称,CMake 将创建一个名称为 libnative-lib.so 的文件。不过,在 Java 代码中加载此库时,请使用您在 CMake 构建脚本中指定的名称:

AS下编辑native—过去的方法使用ndk-build

ndk-build使用的是众多make文件其中大部分在ndk\build\core中,我们需要提供俩个:Android.mk与Application.mk
ndk-build通过这些确定生成so

过程
  1. 创建一个新的project,一定要勾选“include C++ support”
    注: 项目默认是用的是CMake,并不会自动生成Android.mk
  2. 在cpp目录下新建Android.mk示例代码如下:

    1
    2
    3
    4
    5
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := xxxx注意这里是生成.so的名字,会自动生成lib前缀
    LOCAL_SRC_FILES := native-lib.cpp
    include $(BUILD_SHARED_LIBRARY)
  3. 修改build.gradle文件,设置使用ndk-build,而不是cmake(在这里改变编译c++部分的工具)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    defaultConfig {
    ...
    externalNativeBuild {
    ndk {
    ldLibs "log"
    abiFilters "armeabi", "armeabi-v7a", "x86", "x86_64", "mips"
    }
    }
    }
    ...
    externalNativeBuild {
    ndkBuild {
    path "src/main/cpp/Android.mk"
    }
    }
  4. 如果要使用C++的库,则创建Application.mk文件(同样置于cpp目录),添加如下代码
    APP_STL := stlport_static