Android 开发者必须了解的系统知识都在这里了

789 阅读12分钟

Android 开发者必须了解的系统知识都在这里了

AOSP

AOSP(Android 开源项目) 是公开发布且可修改的 Android 源代码。 我们使用的 Android 系统主要包含两部分,一部分就是 AOSP,另一部分就是闭源的应用和服务,比如应用商店、GMS等。AOSP 的架构图如下所示:

image.png

各层的术语示意如下:

  • Android Apps:是指仅使用 Android API 开发的应用,可以在应用商店查找或者下载
  • Privileged APPs(特权应用):是位于系统映像某个分区上 priv-app 目录下的系统应用
  • Device Manufacturer Apps(设备制造商应用):由设备制造商提供的定制应用,它是结合使用 Android API、系统 API 并直接访问 Android Framework 实现而创建的应用。由于设备制造商可能会直接访问 Android 框架中的不稳定的 API,因此这些应用必须预安装在设备上,并且只能在设备的系统软件更新时进行更新。
  • Android API:是面向第三方 Android 应用开发者的公开 API
  • System API(系统API):带有 @SystemApi 注解的api。
  • Android Framework:构建应用所依据的一组 Java 类、接口和其他预编译代码。框架的某些部分可通过使用 Android API 公开访问。框架的其他部分只能由 OEM 通过系统 API 来访问。Android 框架代码在应用进程内运行。
  • System Services(系统服务):系统服务是在后台运行,为Android系统提供一系列核心功能的进程。Android 框架 API 提供的功能可以与系统服务进行通信,以访问底层硬件。
  • Android 运行时 (ART):AOSP 提供的 Java 运行时环境。在应用安装时,ART 就会将 DEX 字节码编译成机器码并存储在设备中。应用运行时,将直接执行机器码,避免解释和编译过程。
  • HAL(硬件抽象层):它包含硬件供应商要实现的标准接口。借助 HAL,硬件供应商可以实现设备专属的较低级别功能,而不会影响或修改较高级别层中的代码。
  • Daemons(守护程序):该层中的原生守护程序包括 inithealthdlogdstoraged。这些守护程序直接与内核或其他接口进行交互,并且不依赖于基于用户空间的 HAL 实现。
  • Linux Kernel(Linux 内核):内核是任何操作系统的中心部分,并与设备上的底层硬件进行通信,尽可能将 AOSP 内核拆分为与硬件无关的模块和特定于供应商的模块

@SystemApi 和 @Hide 的区别:@SystemApi 表示的是系统 API;而 @Hide 注解表示该方法对于开发者来说是不可见的。@Hide 注解的方法不一样需要 @SystemApi;而 @SystemApi 注解的方法需要 @Hide

进程

用户空间和内核空间

在 Android 中,每一个App进程都有用户空间和内核空间,其中内核空间是所有进程共享的。

image.png

为什么 Android 的进程需要区分用户空间和内核空间呢?

  • 便于管理与维护系统稳定:将系统数据和用户数据分开存放,可避免数据混乱。不同身份的数据放在不同位置,能让系统数据和用户数据互不干扰,比如系统配置信息存储在内核空间,应用数据存储在用户空间,各自独立管理,从而保证系统稳定运行。
  • 实现数据访问控制,保障系统安全:通过隔离用户数据和系统数据,可分别控制两部分数据的访问权限。这能防止用户程序随意访问和修改系统数据,避免因用户程序的误操作或恶意行为破坏系统,例如用户程序无法直接修改内核中的关键配置,从而确保系统安全。

冷启动、热启动和温启动

冷启动热启动温启动
定义系统不存在App进程(APP首次启动或APP被完全杀死)时启动APP当按了Home键或其它情况app被切换到后台,再次启动app的过程包含冷启动的一些操作,由于app进程依然在,温启动只执行冷启动的第二阶段
启动流程第一阶段:加载并启动app;显示空白window;创建app进程。
第二阶段:创建app对象;启动主进程ActivityThread;创建MainActivity;渲染视图;执行onLayout;执行onDraw;第一次绘制完成后,替换空白window
系统将activity带回前台。若activity存在内存中,避免重复对象初始化、渲染、绘制操作;若对象被回收,则需重建对象执行冷启动第二阶段操作:创建app对象;启动主进程ActivityThread;创建MainActivity;渲染视图;执行onLayout;执行onDraw;第一次绘制完成后,替换空白window
区别最完整启动过程,涉及创建进程、初始化对象、绘制界面等;启动时显示空白windowactivity在内存中时,无需重复初始化和绘制;内存不足对象回收时,类似冷启动启动过程介于冷、热启动之间,开销比热启动大,比冷启动小;部分场景有类似冷启动显示空白window情况
启动速度最慢最快介于冷启动和热启动之间

进程回收优先级

当 Android 系统内存紧张时,LMK(Low Memory Killer(低内存杀手))将主动杀死进程来释放内存。LMK 使用一个名为 oom_adj_score 的“内存不足”分值来确定正在运行的进程的优先级,以此决定要杀死的进程。最高得分的进程最先被终止。后台应用最先被杀死,系统进程最后被杀死。下图列出了从高到低的 LMK 评分类别,评分最高的类别,即第一行中的App进程将最先被杀死。

image.png

每一项的术语说明如下所示:

  • 后台应用(Background apps):之前运行过且当前不处于活动状态的应用。LMK 将首先从具有最高 oom_adj_score 的应用开始终止后台应用。
  • 上一个应用(Previous app):最近用过的后台应用。上一个应用比后台应用具有更高的优先级(得分更低),因为相比后台应用,用户更有可能切换到上一个应用。
  • 主屏幕应用(Home app):这是启动器应用。杀死该应用会使壁纸消失。
  • 服务(Services):服务由应用启动,可能包括用于同步或上传到云端的服务。
  • 可觉察的应用(Perceptible apps):用户可通过某种方式察觉到的非前台应用,例如一个显示了小窗口的搜索进程或正在播放音乐的进程。
  • 前台应用(Foreground app):当前正在使用的应用。终止前台应用看起来就像是应用崩溃了,可能会让用户觉得设备出了问题。
  • 持久性(服务)(Persistent):这些是设备的核心服务,例如电话和 Wi-Fi。
  • 系统(System):系统进程。这些进程被终止后,手机可能看起来即将重新启动。
  • 原生(Native):系统使用的极低级别的进程(例如,kswapd(内核交换守护程序))。

跨进程通信(IPC)

IPC方式优点缺点适用场景数据拷贝次数传输数据的大小限制
Bundle简单易用只能传输 Bundle 支持的数据四大组件间的进程间通信1次1M
文件共享简单易用不适合高并发场景,且无法做到进程间的即时通信无并发访问情形,交换简单的数据、实时性不高的场景1次1M
AIDL功能强大,支持一对多并发通信,支持实时通信使用稍复杂,需要处理好线程同步一对多通信且有 RPC 需求1次1M
Messenger功能一般,支持一对多并发通信,支持实时通信不能很好处理高并发情形,不支持 RPC,数据通过 Message 进行传输,因此只能传输 Bundle 支持的数据类型低并发的一对多即时通信,无RPC需求,或者无须返回结果的RPC需求1次1M
ContentProvider在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call 方法扩展其他操作可以理解为受约束的 AIDL,主要提供数据源的 CRUD 操作一对多的进程间的数据共享1次1M
Socket功能强大,可以通过网络传输字节流,支持一对多并发实时通信实现细节稍微有点烦琐,不支持直接的 RPC网络数据交换2次无限制
共享内存支持大数据的高效传输使用稍复杂大数据传输的场景0次无限制

由于 Bundle、aidl、Messager、ContentProvider 内部都是通过 Binder 实现的,因此它们的数据拷贝次数都是一次,同时传输数据的限制大小都是 1M。

Android 虚拟机

Dalvik 和 ART

在 Android 5以前,使用的是 Dalvik 虚拟机;而在 Android 5 以后,默认使用的是 ART 虚拟机。Dalvik VM 和 ART VM 的区别如下:

  • ART早期使用AOT技术,后期使用AOT+JIT混合,而Dalvik使用JIT
  • ART支持64位CPU并兼容32位CPU,而Dalvik只支持32位CPU
  • ART对垃圾收集器进行了改进优化,提高了吞吐量

Android 的虚拟机是基于寄存器的,而JVM是基于栈(操作数栈)。使用基于寄存器的设计可以让应用更快,更省内存,主要体现在:

  • 指令更少
  • 数据移动次数更少、临时结果存放次数少
  • 映射真实机器的寄存器

虽然基于寄存器执行效率好,但是可移植性差,难跨平台。但是对于 Android 来说,Android平台是统一的,因此不需要解决移植性问题。

内存限制

在 Android 中,OOM 主要有两种,分别是 Android 虚拟机的堆内存溢出Linux 进程的虚拟内存溢出

堆内存溢出(这里是 Android 虚拟机视角):当程序需要创建的对象数量超过了堆内存的限制,导致无法再分配更多的对象,就会抛出堆内存溢出错误。这通常是因为程序中存在内存泄漏或者内存消耗过大的问题。一般我们应用能用的堆内存,就是 256M/512M,这个是虚拟机给我们应用设置的限制。比如,有以下错误信息:

java.lang.OutOfMemoryError: Failed to allocate a 48 byte allocation with 3610680 free bytes and 3526KB until OOM, target footprint 536870912, growth limit 536870912; giving up on allocation because <1% of heap free after GC.

我们可以通过 adb shell getprop dalvik.vm.heapsize 来查看 Android 虚拟机的堆的大小限制。除此之外,还可以使用 adb shell getprop dalvik.vm.heapstartsize 来查看堆内存的初始大小; adb shell getprop dalvik.vm.heaptargetutilization 来查看堆内存的目标利用率。

而虚拟内存溢出(这里是 Linux 进程视角):当程序可用的内存,即 native 层的内存无法支撑调用,无可用的进程虚拟内存时,抛出的内存溢出错误,这个是 Linux 进程的限制。比如,有以下错误信息:

java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Out of memory. See process maps in the log.

值得注意的是,这两种 OOM 其实是互相独立的,比如发生了堆内存溢出,并不一定会发生虚拟内存溢出,而发生虚拟内存溢出,也不一定发生堆内存溢出。

文件系统

文件名字长度的限制

在 Android 中文件或目录的最大长度是 255 个字符;而路径的最大长度是 4096 个字符。如果超过了这些限制则会报错。

文件描述符限制

在 Android 系统中,执行打开文件、网络套接字、ContentProvider、线程等操作都需要分配文件描述符(File Descriptor,简称 fd)。但是文件描述符的分配是有限制的,在一些老的设备上,默认的 FD_SETSIZE 为 1024,因此在比较古老的 Android 手机上,很容易出现 FD 超限的问题。在新版本的 Android 中,虽然大部分厂商已经将 fd 上限调得很大了,但是依旧可能存在由于 Android 应用本身的问题,导致 FD 超限。

FD 超限比较典型的堆栈如下:file descriptor >= FD_SETSIZE

shell
复制代码
signal:6 (SIGABRT),code:-6 (SI_TKILL),fault addr:--------
Abort message:
FORTIFY: FD_SET: file descriptor >= FD_SETSIZE
backtrace:
#00 pc 000000000006b364  /system/lib64/libc.so (tgkill+8)
#01 pc 00000000000693f4  /system/lib64/libc.so (pthread_kill+68)
#02 pc 0000000000021338  /system/lib64/libc.so (raise+28)
#03 pc 000000000001bad8  /system/lib64/libc.so (abort+60)
#04 pc 000000000001ef20  /system/lib64/libc.so (__libc_fatal+128)
#05 pc 000000000001ef44  /system/lib64/libc.so (__fortify_chk_fail+32)
#06 pc 0000000000072484  /system/lib64/libc.so (__FD_SET_chk+32)
#07 pc 0000000000011168  /system/lib64/libjavacrypto.so
#08 pc 000000000001a2e0  /system/lib64/libjavacrypto.so
#09 pc 0000000073ba4808  /data/dalvik-cache/arm64/system@framework@boot.oat

参考