Android 开发者必须了解的系统知识都在这里了
AOSP
AOSP(Android 开源项目) 是公开发布且可修改的 Android 源代码。 我们使用的 Android 系统主要包含两部分,一部分就是 AOSP,另一部分就是闭源的应用和服务,比如应用商店、GMS等。AOSP 的架构图如下所示:
各层的术语示意如下:
- 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(守护程序):该层中的原生守护程序包括
init
、healthd
、logd
和storaged
。这些守护程序直接与内核或其他接口进行交互,并且不依赖于基于用户空间的 HAL 实现。 - Linux Kernel(Linux 内核):内核是任何操作系统的中心部分,并与设备上的底层硬件进行通信,尽可能将 AOSP 内核拆分为与硬件无关的模块和特定于供应商的模块
@SystemApi 和 @Hide 的区别:@SystemApi 表示的是系统 API;而 @Hide 注解表示该方法对于开发者来说是不可见的。@Hide 注解的方法不一样需要 @SystemApi;而 @SystemApi 注解的方法需要 @Hide
进程
用户空间和内核空间
在 Android 中,每一个App进程都有用户空间和内核空间,其中内核空间是所有进程共享的。
为什么 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 |
区别 | 最完整启动过程,涉及创建进程、初始化对象、绘制界面等;启动时显示空白window | activity在内存中时,无需重复初始化和绘制;内存不足对象回收时,类似冷启动 | 启动过程介于冷、热启动之间,开销比热启动大,比冷启动小;部分场景有类似冷启动显示空白window情况 |
启动速度 | 最慢 | 最快 | 介于冷启动和热启动之间 |
进程回收优先级
当 Android 系统内存紧张时,LMK(Low Memory Killer(低内存杀手))将主动杀死进程来释放内存。LMK 使用一个名为 oom_adj_score
的“内存不足”分值来确定正在运行的进程的优先级,以此决定要杀死的进程。最高得分的进程最先被终止。后台应用最先被杀死,系统进程最后被杀死。下图列出了从高到低的 LMK 评分类别,评分最高的类别,即第一行中的App进程将最先被杀死。
每一项的术语说明如下所示:
- 后台应用(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