Android NDK开发从入门到放弃小结

版权声明:本文为博主原创文章,欢迎转载,转载请注明出处。 https://blog.csdn.net/jun5753/article/details/81482430

前言:

工作中曾做过一断时间音视频监控类的应用,涉及到NDK开发。NDK开发平时写代码中也会常常用到,如用到第三方的视频剪辑类SDK、友盟统计、qqbugly统计,或多或少都会用到。本文就对自己工作中用到过的NDK的一些经验套路做一些总结和分享,希望对你有所帮助。

NDK是什么?
它是Google为便于Android开发提供的一种原生开发集:Native Development Kit,而且也是一个包含API、构建工具、交叉编译、调试器、文档示例等一系列的工具集,可以帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成APK。

与NDK密切相关的另一个词汇则是JNI,它是NDK开发中的枢纽,Java与底层交互绝大多数都是通过它来完成的,那么接下来看看什么是JNI?

JNI又是什么?
**JNI:**Java Native Interface 也就是java本地接口,它是一个协议,这个协议用来沟通java代码和本地代码(c/c++)。通过这个协议,Java类的某些方法可以使用原生实现,同时让它们可以像普通的Java方法一样被调用和使用,而原生方法也可以使用Java对象,调用和使用Java方法。也就是说,使用JNI这种协议可以实现:java代码调用c/c++代码,而c/c++代码也可以调用java代码。

那我们为什么要使用呢?

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

NDK开发环境的搭建
最早接触NDK时环境用的是Cygwin +eclipse,后来可以直接在Android Studio中来开发。 具体搭建环境此处不在赘述,网上一搜到处都是。

两种搭建方法参考:
https://blog.csdn.net/true100/article/details/51554309
https://blog.csdn.net/qiujuer/article/details/42040963

CPU架构
我们都知道 CPU 是什么,那 CPU 架构到底是什么呢?回归到“架构”这个词本身含义,CPU 架构就是 CPU 的框架结构、设计方案,处理器厂商以某种架构为基础,生产自己的 CPU,就好比“总-分-总”是文章的一种架构,多篇文章可以都基于“总-分-总”架构。
常见的 CPU 架构有 x86、x86-64 以及 arm 等, x86-64 其实也是基于 x86 架构,只是在 x86 的基础上做了一些扩展,以支持 64 位程序的应用,常见的 Intel 、AMD 处理器都是基于 x86 架构的。
而 x86 架构主打的是 pc 端,对于移动端,arm 架构处于霸主地位 ,由于其体积小、低功耗、低成本、高性能的优点,被广泛应用在嵌入式系统中,目前大多数安卓、苹果手机的 CPU 都基于 arm 架构,此处所说的 arm 架构指 arm 系列架构,其中包括 ARMv5 、ARMv7 等等。
最后再看 Android 端 , Android 系统目前支持 ARMv5、ARMv7、ARMv8、 x86 、x86_64、MIPS 以及 MIPS64 共七种 CPU 架构,也就是说除此之外其他 CPU 架构的硬件并不能运行 Android 系统。

交叉编译
在某个平台上,编译该平台的可执行程序,叫做本地编译,比如在 Windows 平台上编译 Windows 自身的可执行程序;在 x86 平台上,编译 x86 平台自身的可执行程序。
在某个平台上,编译另一种平台的可执行程序,就是交叉编译,比如在 x86 平台上,编译 arm 平台的可执行程序,这也是 Android 端使用最多的交叉编译类型。
在交叉编译时,由于主机与目标的体系架构、环境不同,所以交叉编译比本地编译复杂很多,需要一些工具来解决主机与目标不同特性的问题,这些工具构成的工具集就叫做交叉编译链。
既然交叉编译比本地复杂很多,那为什么不使用本地编译,比如在 arm 平台编译 arm 平台的可执行程序呢?这是因为目标平台存储空间和计算能力通常是有限的,而编译过程需要较大的存储空间和较快的计算能力,但目标平台无法提供。

JNI的开发流程主要分为以下几个步骤:

  1. 编写带有native声明的方法的java类
  2. 使用javac命令编译所编写的java类
  3. 然后使用javah + java类名生成扩展名为h的头文件
  4. 使用C/C++实现本地方法
  5. 将C/C++编写的文件生成动态连接库*(在Android中就是.so库)
  6. java代码中调用native方法

这里写图片描述

你需要了解的一些C/C++开发有方面的知识
C语言基础知识:
1.基本数据类型(基本内置类型)
(1)整型:
短整型: short int 16bit 2个字节 无符号整型 0-65535
整型:int 16bit 2个字节
长整型: long int 32个字节
(2)浮点型:
单精度(float)只能保证6位有效数字;
双精度 (double)至少保证10位有效数字;
长双精度(long double) 至少保证10位有效数字,但是比double提供的精度更高;
(3)字符型
char 8bit
(4)布尔型(c++才有)
bool 只有0代表false,非0即为真(true)
C++标准规定了每个算术类型的最小空间,但是不阻止编译器使用更大的空间
2.构造类型
(1)枚举类型(enum)
(2)数组类型
(3)结构体类型(struct)
(4)共用体类型(union)
(5)类类型(class)C++才有
3.指针类型
4.引用类型
5.空类型(void)
6.数组
由类型名、标识符和维数组成的复合数据类型,类型名规定了存放在数组中的元素的数据类型,维数指定数组中包含的元素个数。
(1)一维数组:
定义: 类型名 标识符[维数];int a1[3];
引用:标识符[维数];元素下标从0开始的;a1[1]
初始化:可以在定义的时候初始化(可以不写维数),也可以在使用前初始化(定义时必须写维数)维数在使用之前,必须是
固定的值(因为数组的长度是固定的)
(2)二维数组:
定义:类型名 标识符[维数][维数];
引用:标识符[维数][维数];
初始化:二维数组的初始化会自动补0,如果提供了全部的元素,那么定义时第一维可以省略,但第二维不能省略;
7.预处理命令:(在代码编辑之前处理的命令)在编译期执行
预处理命令是C++统一规定的,但是它不是C++语言本身的组成部分,不能直接对它们进行编译;
(1)宏定义:
#define 标识符 字符串 pc 3.1415
#define 宏名(参数表) 字符串

(2)条件编译:
#ifndef
#ifdef: 标识符 当指定的标识符已经被#define命令定义过,
程序段1 则只编译程序段1
#else 否则
程序段2 编译程序段2
#endif 限定#ifdef命令的范围

#if 表达式 表达式为真时
程序段1 编译程序段1
#else 否则
程序段2 编译程序段2
#endif 结束#if命令
(3)文件包含:
一个文件将另一个文件的全部内容包含进来
#include

下面介绍一下NDK开发时崩溃时日志怎么看?
NDK开发断点调试比较麻烦,据说在linux环境下可以,有兴趣的话可以去尝试研究一下,在window开发中。一般是通过看堆栈信息来分析。

推荐阅读:
使用 ndk-stack 寻找Android程序Crash的原因
https://blog.csdn.net/change_from_now/article/details/53436581

扩展阅读:
书籍推荐《Android C++高级编程 使用NDK》
JNI函数、C与C++调用函数的区别
https://blog.csdn.net/xiaoyu_93/article/details/52995127

总结:
关于NDK/JNI开发技术,在Android中使用是非常多的,我们在实际开发中或多或少可能会使用到第三方或者需要自己开发相应的so库,所以学习和理解JNI中的一些实现原理还是很有必要的,从以前在Eclipse来实现so库开发到现在可以通过Android Studio来开发so库,会发现会方便很多,这个也是技术的发展带来的一些便捷。以后会在工作中有及时有意识的做一些总结,把自己遇到的坑记录一下,不断探索。

参考资料:
Android之NDK开发(一)C语言初识
https://blog.csdn.net/Jacky_Can/article/details/76408553
Android:JNI 与 NDK到底是什么?
https://blog.csdn.net/carson_ho/article/details/73250163

google官方NDK指南
https://developer.android.com/ndk/guides/?hl=zh-cn

未完待续,后期完善~

猜你喜欢

转载自blog.csdn.net/jun5753/article/details/81482430