NDK入门学习笔记

## NDK进阶
### as配置ndk环境搭建
1. 配置:ndk目录配置、gradle校验、生成jni头文件
2. 编写流程:编写native方法、生成头文件、加载so、运行测试
3. 遇到的错误:gradle运行出错、加载so时候遇到的错误
4. 结构分析:生成的so在哪里呢、以前的Android.mk文件在哪里呢
5. 什么是ABI
6. Android系统支持的cpu架构有哪些呢?ARMv5、ARMv7、x86、MIPS、ARMv8、MIPS64、x86_64、arm64-v8a


### jni交互部分
1. 调用思想:找到对应的类;找到对应的方法;调用相关方法;
2. C调用java的静态方法:修改静态字段、调用静态方法、调用过程
3. C调用java的实例方法:修改实例字段、调用实例方法、如何创建实例对象
4. 方法签名:命令 javap -s


### ndk调试
1. ndk崩溃的显现
2. 崩溃日志查看:错误信号、寄存器信息、方法调用栈、地址
3. 常见的ndk异常有哪些?野指针、空指针、内存泄漏、堆栈溢出
4. 什么是符号表
5. 解决方案:a.addr2line工具;b.ndk-stack命令:-sym、-dump


### ndk异常
1. 异常产生原因
2. jni层调用java层异常后,跟java层异常有什么不一样的地方?
3. 处理方式:ExceptionCheck、ExceptionOccurred


## 入门
### 环境搭建
1. 下载NDK开发包:ndk-r9d
2. 配置ndk相关环境变量:path中添加D:\cocos2dx\android-ndk-r16b
3. 配置eclipse相关开发工具:eclipse关联adt-23
4. 配置genymotion模拟器:将genymotion-arm的兼容包拖拽到genymoition中
5. 导入ndkdemo:ndk-r9d的sample下有例子


### 命令
1. ndk-build:在项目目录下执行该命令,可以根据c/c++生成so文件,java从so中调用c/c++
2. ndk-build clean:清除之前生成的so文件
3. javah -classpath bin/classes;D:\sdk\platforms\android-26\android.jar -d jni com.example.hellondk.MainActivity: 生成对应的c++中的头文件即.h文件,将android.jar配置到环境变量path中就不需要此处写android.jar的路径了


### 开发第一个ndk项目
1. 创建android项目
2. 创建jni目录
3. 编写nativejava层方法
4. 生成jni头文件:a.编写javaNative代码;b.介绍一个命令javah;添加android.jar包到环境变量;


### NDK相关概念
1. ndk是什么:
2. 什么场景可以应用ndk:a.代码的保护,由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大;b.在ndk中调用第三方C/C++库,因为大部分的开源库都是用C/C++代码编写的。比如人脸识别、音视频处理相关;c.便于移植,用C/C++C写的库可以方便在其他的嵌入式平台上再次使用;
3. 什么是交叉编译?简单来说,就是在一个平台上生成另一个平台上可执行的代码。
4. jni是什么?Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。
5. 什么是链接库?有静态链接库和动态链接库
6. make文件介绍?
7. ndk开发包目录介绍


## jni实现流程
1. 编写Java类代码->*.java
2. 编译成字节代码->*.class
3. 产生C头文件->*.h
4. 编写JNI实现代码 ->*.c
5. 编译成链接库文字 ->*.dll或*.os


### cpp中打印log


### jni的交互处理
#### java层和native层进行字符串的交互处理
1. boolean -> jboolean
2. byte -> jbyte
3. char -> jchar
4. short -> jshort
5. int -> jint
6. long -> jlong
7. float -> jfloat
8. double -> jdouble
9. void -> void 
10. String -> jString


#### 数组的处理
1. 方法在官方文档NDK或者本地ndk-r9d/platform/19/arm/usr/include/jni.h 中可以查到


###### 生成native层的数组拷贝-方式一
1. (*env)->GetIntArrayRegion(env,array,0,5,nativeArray):将array的值赋给nativeArray
2. (*env)->SetIntArrayRegion(env,array,0,5,nativeArray):将nativeArray的值设置给array


###### 直接调用数组指针-方式二
1. jint* data=(*env)->GetIntArrayElements(env,array,NULL):直接获取array的指针

2. jsize len=(*env)->GetArrayLength(env,array):



## 项目基本配置
1. local.properties中添加ndk路径:ndk.dir=D\:\\cocos2dx\\android-ndk-r16b
2. gradle.properties文件中添加 android.useDeprecatedNdk=true  以兼容旧版本NDK
3. 对应module的build.gradle中的defaultConfig内添加
    ndk{
            moduleName "hello"
            abiFilters "x86"
            ldLibs "log"
        }


## 生成头文件
1. 先进入项目主目录,然后
2. cd app/src/main/java
3. 根据java文件中的native函数生成C的头文件
4. javah -d ../jni com.qingnantech.hellondk.Hello
5. 在jni目录下新建Hello.cpp,将刚刚生成的.h文件通过以下指令引入头文件: #include "com_qingnantech_hellondk_Hello.h" 
6. Hello.java中引入so文件:
   static {
        System.loadLibrary("hello");
    }


## as-so-mk
1. as会自动生成so文件和Android.mk文件,路径在app/build/intermediates/ndk/debug下,新版本as是在app/build/intermediates/cmake/debug/obj/


## ABI
1. 定义:应用程序二进制接口(Application Binary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。在Android系统上,每一个CPU架构对应一个ABI:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64。


## android支持的cpu架构
1. ARMv5
2. ARMv7(从2010起)
3. x86(从2011年起)
4. MIPS(从2012年起)
5. ARMv8,MIPS64和x86_64,arm64-v8a(从2014年起)
6. 一般来说,新版本的架构是支持旧版本架构的so文件的
7. 很多设备都支持多于一种的ABI。例如ARM64和x86设备也可以同时运行armeabi-v7a和armeabi的二进制包。但最好是针对特定平台提供相应平台的二进制包,这种情况下运行时就少了一个模拟层(例如x86设备上模拟arm的虚拟层),从而得到更好的性能(归功于最近的架构更新,例如硬件fpu,更多的寄存器,更好的向量化等)


## jni交互
### 调用思想


### 方法签名
1. 为什么要有方法签名?:解决方法重载的问题


#### 签名规则
1. 基本类型
 * boolean z
 * byte B
 * char C
 * short s
 * int I
 * long J
 * float F
 * double D
 * void V
2. 如果是类,则是 L+类全名(包名中的点用/代替)+;比如java.lang.String对应的是 Ljava/lang/String;
3. 数组类型:如果是数组,则在前面加[然后加类型签名,几位数组就加上几个[比如int[]对应[1,boolean[][]对应[[Z,java.lang.Class[]对应[Ljava/lang/Class


#### 命令 javap -s:打印方法签名
1. 命令行进入 /app/build/intermediates/classes/debug
2. 执行 javap -s com.qingnantech.hellondk.Hello 可以打印方法签名


### 如何添加log
1. 修改build.gradle文件
2. 在build.gradle的ndk选项中添加 IdLibs("log")
3. 在cpp/c文件中添加打印log的方法:ndk-r10d中的samples/native-activity/jni/main.c中有引入的例子,直接拷过来就行
4. #include <android/log.h>
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "nate", __VA_ARGS__))


## ndk调试
### ndk崩溃的显现


### 崩溃日志查看
0. 命令行中输入:adb logcat
1. 错误信号
2. 寄存器信息
3. 方法调用栈


### 常见的ndk异常
1. 野指针:野指针指向的是一个无效的地址:
* 改地址如果是不可读不可写的,那么会马上Crash(内核给进程发送段错误信号SIGSEGV),这是bug会很快被发现。
* 如果访问的地址为可写,而且通过野指针修改了该处的内存,那么很有可能会等一段时间(其他的代码使用了该处的内存后)才发生Crash。这时查看Crash时显示的调用栈,和野指针所在的代码部分,有可能基本上没有任何关联
- 解决方案
- 在指针变量定义时,一定要初始化,特别是在结构体或类中的成员指针变量
- 在释放了指针指向的内存后,要把该指针置为NULL(但是如果在别的地方也有指针指向该处内存的话,这种方式就不好解决了)。
- 注意:野指针造成的内存破坏的问题,有时候光看代码很难查找,通过代码分析工具也很难找出,只有通过专业的内存检测工具,才能发现这类bug
2. 空指针:操作前进行非空判断
3. 数组越界
4. 内存泄漏
5. 堆栈溢出


### 什么是符号表
1. 定义:符号表是内存地址与函数名、文件名、行号的映射表。


### 解决方案
#### addr2ling工具
1. 路径是ndk-r9d/toolchains/x86或其他平台视手机而定/prebuild/darwin/bin/addr2
2. 进入该文件目录,指令帮助:./addr2line -help (addr2line需要是文件的全名)
3. ./addr2line -e obj下的带符号表的so文件目录 日志中的地址 :可以看到日志中对应地址的可以看懂的信息


#### ndk-stack命令
1. -sym 
2. -dump 
3. adb logcat|ndk-stack -sym  so文件全路径
4. ndk的docs中有文档解释一系列指令


## ndk异常
### 异常产生原因
1. 编译时候的异常
2. 运行时候的异常


### jni调用java层异常与java层异常的不同


### 处理方式
1. ExcaptionCheck
2. ExceptionOccurred
3. 出现异常后我们的操作是什么

猜你喜欢

转载自blog.csdn.net/qq_23081779/article/details/80876421