Android源码分析

Android源码分析

Actviity启动流程

核心流程

startActivity() → Instrumentation → AMS → ActivityThread → onCreate() → onStart() → onResume()

源码关键节点

// 1. 客户端进程
Activity.startActivity()

Instrumentation.execStartActivity()

ActivityManager.getService().startActivity()  // 跨进程调用AMS

// 2. 系统进程(AMS)
ActivityManagerService.startActivity()

ActivityStarter.startActivityMayWait()

ActivityStack.resumeTopActivity()

// 3. 回到客户端进程
ActivityThread.handleLaunchActivity()

Instrumentation.callActivityOnCreate()

Activity.performCreate()

Window 与 WindowManager

核心关系

窗口添加流程

AMS

核心功能架构

进程管理源码

WMS

窗口管理核心

ClassLoader 与热修复

ClassLoader:它是 JVM 或 Android Dalvik/ART 虚拟机的一部分,负责在运行时将.class.dex文件加载到内存,并转换成可执行的类或组件。

核心机制

双亲委托模型:当一个类加载器需要加载类时,它首先会委托给其父加载器尝试加载。只有当所有父加载器都无法加载时,自己才会尝试。这保证了Java核心库类的安全性和唯一性。

Android中的主要ClassLoader

关键点:在Android中,每个ClassLoader实例都有一个自己的“已加载类”的命名空间。同一个类被不同的ClassLoader加载,会被虚拟机视为两个不同的类

热修复

定义:指在不重新安装或重启应用的情况下,动态修复线上Bug(通常替换有问题的类文件)的技术。

核心目标:快速修复线上崩溃、功能异常,无需用户下载完整新版本。

双亲委派源码

热修复原理

SharedPreference 源码缺陷

核心问题

优化方案

使用 MMKV

使用 DataStore

拆分小文件

陷阱一:异步写入的“幽灵” - apply()的可靠性

apply()看似是异步无等待的“安全”写入,实则有巨大隐患。

源码逻辑apply()会先将数据写入内存 Map,然后创建一个 awaitCommitRunnable放入 QueuedWork队列,最后异步将 Map 数据写入 XML 文件。

核心陷阱

结论apply()是“发起即忘”,不保证最终一致性。对需要强一致性的数据(如用户设置开关),应使用有返回值的 commit()(注意在主线程用可能导致卡顿)。


陷阱二:主线程的“隐形炸弹” - 首次加载与全量读写

首次加载阻塞:首次调用 getSharedPreferences时,会同步(可能阻塞调用线程)加载整个 XML 文件到内存 Map。如果文件很大或主线程调用,直接导致界面卡顿。

全量更新:每次 commit/apply都是将整个内存 Map 序列化为 XML 并完全覆盖写入文件,而不是增量更新。即使你只修改一个键值,也会重写整个文件。文件越大,I/O 开销越大。


陷阱三:多进程的“假象” - MODE_MULTI_PROCESS

此模式已废弃且完全不可靠。

源码逻辑:它并非真正的跨进程内存共享。只是在每次 getSharedPreferences时,检查文件最后修改时间,如果发现被其他进程修改过,就重新加载整个文件

核心陷阱

绝对禁止:任何需要多进程共享数据的场景,都应使用 ContentProviderMMKV(多进程模式)或 DataStore(多进程)等方案。


陷阱四:类型安全的“幻觉” - 无编译时检查

陷阱getString(key, default)等方法,如果 key 存储的实际类型与读取时声明的类型不符,会在运行时返回默认值,静默失败,难以调试。

解决:考虑对 Key 进行封装,或使用 DataStorePreferenceDataStore提供类型安全接口。


陷阱五:监听器的“内存泄漏”与“失效”

内存泄漏风险:注册的 OnSharedPreferenceChangeListener强引用。如果不反注册,会持有 Context 或 Activity 导致无法回收。

失效时机:在某些旧版本 Android 上,apply()提交后,监听器可能在主线程排队执行,而不是在写入线程。并且,如果注册时使用 null作为 key 筛选,行为可能不一致。

ContentProvider 组件初始化顺序

1. 启动顺序概览

当一个 Android 应用启动时,组件的初始化顺序如下:

关键点ContentProvider的初始化发生在 Application.onCreate()之前!

2. 详细初始化流程

重要ContentProvider的初始化是在应用主线程中同步执行的!


二、初始化的触发条件

ContentProvider 不会自动初始化,它只在以下情况下被初始化:

1. 首次访问时(按需初始化)

2. 应用启动时自动初始化(Android 7.0+ 优化)

Android 7.0 (API 24) 开始,系统会在应用启动时自动初始化所有在清单文件中声明的 ContentProvider,无论它们是否被使用。

例外:可以通过设置 android:initOrder控制初始化顺序,但无法完全禁用自动初始化。

3. 特殊系统组件的初始化

一些系统组件会隐式初始化 ContentProvider:

JobScheduler触发 JobService

BroadcastReceiver处理广播

应用安装/更新后的首次启动


三、初始化顺序与依赖关系

1. 声明顺序决定初始化顺序

AndroidManifest.xml中,ContentProvider 按照声明顺序初始化:

初始化顺序:AProvider → BProvider → CProvider(因为 initOrder值大的先初始化,未指定则按声明顺序)

2. 危险的隐式依赖


四、初始化对启动性能的影响

1. 启动延迟问题

由于 ContentProvider 在主线程同步初始化,如果有耗时操作,会直接影响应用启动速度

2. 启动性能数据

假设应用有 3 个 ContentProvider:


五、如何优化 ContentProvider 初始化

1. 延迟初始化(Lazy Initialization)

2. 使用异步初始化框架

3. 合并多个 Provider

4. 完全避免 ContentProvider

对于仅应用内部使用的数据存储,考虑替代方案:


六、常见陷阱与最佳实践

1. 避免的陷阱

2. 最佳实践

场景
推荐做法
不推荐做法

数据库初始化

延迟初始化,在首次访问时初始化

在 Provider.onCreate() 中初始化大数据库

网络请求

完全避免,或使用异步回调

同步网络请求

多 Provider 依赖

通过 Application 类协调

Provider 间直接依赖

耗时计算

使用工作线程

在主线程执行

文件 I/O

延迟到首次访问时

启动时读取大文件

3. 监控初始化性能


七、特殊场景分析

1. 多进程应用中的初始化

注意:每个进程(包括主进程、:background 进程等)都会调用一次 onCreate()

2. Library 中的 ContentProvider

许多三方库会声明自己的 ContentProvider,导致应用启动时初始化多个 Provider:

Application

一、Application 的初始化流程

1. 创建时机:进程启动时

Application是在应用进程创建时由系统创建的,比任何 Activity 都早。

java

java

下载

复制

2. 创建过程:makeApplication 方法

java

java

下载

复制

3. Instrumentation 创建 Application

java

java

下载

复制


二、Application 的核心方法分析

1. attach(Context context) 方法

java

java

下载

复制

注意attach()package权限,应用无法重写。开发者只能在 attachBaseContext()中执行初始化。

2. onCreate() 方法

java

java

下载

复制

重点:Application 的 onCreate()调用顺序在所有 ContentProvider 的 onCreate()之后。

3. onTrimMemory() 和 onLowMemory()

java

java

下载

复制


三、Application 的设计模式

1. 单例模式

java

java

下载

复制

注意:多进程应用中,每个进程都有独立的 Application 实例。

2. 门面模式(Facade)

Application 是 Context 的门面,将复杂操作封装:

java

java

下载

复制

3. 观察者模式

Application 管理各种回调:

java

java

下载

复制


四、Application 的生命周期

1. 完整生命周期时序

2. 多进程的生命周期

验证代码


五、源码中的关键设计点

1. Context 继承体系

Application 与 Activity Context 的区别

2. 资源管理机制

注意:应用内所有组件的 Resources 实际上共享同一个实例。

3. 组件回调的线程安全性


六、Application 的性能陷阱

1. Application 初始化阻塞

2. 内存泄漏风险


七、Application 的最佳实践

1. 优化启动速度

2. 使用 App Startup 库

3. 避免的常见错误

4. 多进程处理


八、Application 的高级用法

1. 监控 Activity 生命周期

2. 全局异常处理

3. 热修复支持

StrictMode

StrictMode 是 Android 开发中的一个工具类,用于检测在主线程中执行的不合规操作,例如磁盘读写、网络访问等。它帮助开发者识别和修复可能影响应用性能的问题,特别是那些可能导致应用无响应(ANR)的问题。

主要作用:

检测主线程中的磁盘读写操作(违反模式时,可发出警告或崩溃)。

检测主线程中的网络访问。

检测Activity、Service、ContentProvider等组件的生命周期问题,如未关闭的Cursor、数据库连接等。

检测慢函数调用(自定义耗时阈值)。

StrictMode 可以设置不同的策略(Policy),包括线程策略(ThreadPolicy)和虚拟机策略(VmPolicy)。

使用方式:

在Application的onCreate()或Activity的onCreate()中启用StrictMode。

示例代码(通常用于调试版本):

注意:StrictMode 主要用于开发阶段,不应该在发布版本中启用,因为它可能会影响性能,并且可能会因为检测到违规而崩溃应用。

在Android 8.0(API 26)及以上版本,StrictMode 还增加了 detectUnbufferedIo() 等方法,以检测未缓冲的I/O操作。

另外,StrictMode 还允许自定义违规后的处理方式,比如弹出对话框(penaltyDialog)、崩溃(penaltyDeath)等。

通过使用StrictMode,开发者可以在开发阶段及时发现并修复潜在的性能问题,从而提升应用的用户体验。

一、StrictMode 的核心作用

1. 监控主线程的违规操作

检测在 UI 线程上执行的不当操作:

检测类型
具体问题
影响

磁盘读写

主线程读写文件、数据库操作

界面卡顿、ANR

网络访问

主线程发送 HTTP 请求

界面卡顿、ANR

慢函数调用

自定义耗时阈值检测

性能瓶颈识别

资源泄漏

未关闭的 Cursor、文件流等

内存泄漏、资源耗尽

2. 检测内存泄漏

监控 Activity、Service、BroadcastReceiver 等组件的内存泄漏:

Activity 被销毁后仍被持有引用

注册的监听器未正确反注册

静态变量持有 Context 引用

3. 虚拟机策略违规

检测 JVM/ART 层面的问题:

SQLite 对象未关闭

可关闭对象(Closeable)未关闭

类实例计数异常


二、StrictMode 的使用方式

1. 基本配置

2. 更严格的配置示例


三、StrictMode 的检测类型详解

1. 线程策略(ThreadPolicy)

A. 磁盘访问检测

解决:使用工作线程

B. 网络访问检测

解决:使用异步请求

C. 慢调用检测

2. 虚拟机策略(VmPolicy)

A. Activity 泄漏检测

StrictMode 日志

B. 资源未关闭检测

C. 监听器未反注册


四、StrictMode 的输出日志解析

1. 线程策略违规日志

关键信息

policy=31:策略掩码

violation=2:违规类型(2=磁盘读)

~duration=212 ms:违规操作耗时

2. 虚拟机策略违规日志

泄露路径:显示 Activity 如何被持有引用。

3. 违规类型代码

违规码
含义
对应检测方法

1

磁盘写

detectDiskWrites()

2

磁盘读

detectDiskReads()

4

网络

detectNetwork()

8

自定义慢调用

detectCustomSlowCalls()


五、高级用法与技巧

1. 临时关闭 StrictMode

2. 条件性检测

3. 自定义违规处理器

4. 在特定线程上启用 StrictMode

Last updated