Android 系统源码分析从启动流程到 Binder 机制实战解析如何读懂 Framework 层源码解决卡顿崩溃问题
哈喽,欢迎来到 Android 系统源码的深度之旅!
作为一个在 Android 领域摸爬滚打多年的开发者,我深知读源码有多痛苦——文件那么多、调用链那么深,一开始真的很容易迷失。今天我就带你从最底层的系统启动开始,一路深入到 Binder 机制,再教你们怎么真正读懂 Framework 层源码,最后实战解决那些让人头疼的卡顿和崩溃问题。
准备好了吗?咱们开始!
一、Android 系统启动流程:一切从哪里开始
1.1 系统启动的三大阶段
Android 系统的启动是一个相当复杂的过程,但我们可以把它分成三个清晰的阶段来理解:
Zygote 进程启动 → SystemServer 启动 → 各应用进程启动
这就像是一个国家的建立过程——先有孵化器(Zygote),再有中央政府(SystemServer),最后才有各个地方政府和机构(应用进程)。
1.2 Zygote 进程:一切的起点
Zygote 是 Android 系统中第一个启动的 Java 进程,它的名字来源于生物学中的”受精卵”概念——所有应用进程都是它的”分裂”产物。
从源码角度看,Zygote 进程是在 /init.rc 配置文件中定义的:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /proc/acpi/wakeup WAKE
onrestart restart media
onrestart restart netd
关键参数解读:
app_process:Zygote 的实际可执行文件-Xzygote:JVM 参数--zygote:标记为 Zygote 模式--start-system-server:启动后是否启动 SystemServer
1.3 app_process 入口:从 C++ 到 Java 的桥梁
Zygote 进程的入口函数在 app_main.cpp 中:
// frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{
// ... 参数解析 ...
AppRuntime runtime(argv[0], computeAppVmArgs(argv, argc));
// 设置启动参数
int i = runtime.addVmArguments(argc, argv);
// 根据参数决定启动 Zygote 还是其他应用
if (argc > 1) {
const char* rootDir = argv[i++];
if (strcmp("--zygote" == 0) {
// Zygote 模式
runtime.start("com.android.internal.os.ZygoteInit",
startSystemServer ? "start-system-server" : "");
} else if (strcmp("--application" == 0) {
// 应用模式
runtime.start("com.android.internal.os.RuntimeInit", ...);
}
}
return 0;
}
注意这里的关键调用链:
app_main.cpp → AppRuntime::start()
↓
AndroidRuntime::start()
↓
JNIEnv::CallStaticVoidMethod() // 调用 Java 层的 ZygoteInit.main()
↓
ZygoteInit.main()
这展示了 Android 的一个经典设计模式:用 C++ 启动进程,然后用 JNI 调用 Java 层代码。为什么要这样做?因为 Linux 内核只认识 C 程序,所以需要一个 C++ 的入口来初始化 Java 虚拟机(ART),然后再切换到 Java 世界。
1.4 ZygoteInit 的核心初始化流程
ZygoteInit.main() 方法做了几件非常重要的事:
// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String argv[]) {
ZygoteServer zygoteServer = new ZygoteServer();
// 1. 注册 Zygote socket,用于接收创建进程的请求
// socket 路径:/dev/socket/zygote
// 这是一个 Unix Domain Socket
RegisterZygoteSocket();
// 2. 预加载类和资源,加速应用启动
preload();
// 3. 如果是第一启动,则启动 SystemServer
if (argv[1] != null) {
if ("start-system-server".equals(argv[1])) {
startSystemServer(zygoteServer);
}
}
// 4. 进入循环,等待创建进程的请求
zygoteServer.run();
}
preload() 预加载 是一个非常重要的优化点。它会在 Zygote 进程中预加载大量的系统类和资源,这样当 Fork 出新进程时,子进程可以直接继承这些已加载的类和资源,大大缩短了应用启动时间。
预加载的内容包括:
- 核心 Java 类(String、Integer、ArrayList 等)
- 系统资源(字体、颜色、drawable 等)
- OpenGL ES 库
- 系统图标资源
这些资源在 Zygote 启动时就加载到内存中,然后通过 Copy-on-Write(写时复制) 机制共享给所有子进程。
1.5 SystemServer:系统服务的大本营
SystemServer 是所有系统服务的宿主进程,它启动了 Android 系统中几乎所有的系统服务。
// frameworks/base/services/java/com/android/server/SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
// 初始化系统上下文
prepareMainCode();
try {
// 1. 启动时间服务(时钟)
Timers.scheduleBootCompleted();
// 2. 启动本地服务(Native Services)
// 如 SurfaceFlinger、AudioFlinger 等
startBootstrapServices();
// 3. 启动核心服务
startCoreServices();
// 4. 启动其他服务
startOtherServices();
// 5. 启动顶层服务
startUpbootstrapServices();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "Hard crashing..." + ex);
Process.killProcess(Process.myPid());
System.exit(1);
}
}
SystemServer 启动的服务非常庞大,包括:
| 服务类型 | 代表服务 | 作用 |
|---|---|---|
| 启动服务(Bootstrap) | ActivityManagerService | 管理 Activity 生命周期 |
| 启动服务(Bootstrap) | PowerManagerService | 电源管理 |
| 核心服务(Core) | WifiService | Wi-Fi 管理 |
| 核心服务(Core) | NotificationService | 通知管理 |
| 其他服务(Other) | PackageManagerService | 包管理 |
| 其他服务(Other) | InputManagerService | 输入管理 |
| 顶层服务(Launcher) | LauncherApps | 启动器管理 |
注意这些服务是按依赖顺序启动的。比如 PackageManagerService 必须在 ActivityManagerService 之前启动,因为 AMS 需要知道系统中安装了哪些应用。
二、Binder 机制:Android 的 IPC 心脏
2.1 为什么需要 Binder?
在 Android 中,不同的服务运行在不同的进程中,进程之间需要通信(IPC)。Linux 提供的 IPC 方式有管道、消息队列、共享内存、Socket 等,但 Android 为什么选择了 Binder?
主要原因:
- 性能优秀:Binder 使用内存映射(mmap),数据不需要在用户空间和内核空间之间来回拷贝
- 安全性高:Binder 自带身份认证机制,可以验证通信双方的身份
- 代码简洁:Binder 将通信过程封装得很好,开发者不需要关心底层细节
- 单向通信:避免了死锁问题
2.2 Binder 的核心组件
理解 Binder 机制需要掌握以下几个核心概念:
┌─────────────────────────────────────────────────────┐
│ 用户空间 │
│ ┌──────────┐ ┌──────────┐ │
│ │ ServiceA │ │ ServiceB │ │
│ │ (Provider)│ │ (Client) │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ │ IBinder │ IBinder │
│ └─────────┬─────────┘ │
│ │ │
├─────────────────┼───────────────────────────────────┤
│ 内核空间 │
│ ┌───────┴───────┐ │
│ │ Binder Driver │ │
│ │ (binder.ko) │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────┘
关键组件:
- ServiceManager(ServiceManager):Binder 架构中的”通讯录”,管理所有系统服务的注册和查找
- Binder Driver:内核模块,负责实际的进程间通信
- IBinder:所有 Binder 通信的接口基类
- binder_thread:每个进程中使用 Binder 时对应的内核线程
2.3 ServiceManager 的作用
ServiceManager 是 Android 系统中最重要的服务之一,它充当着”注册中心”的角色。
// IServiceManager.aidl - 简化理解
interface IServiceManager {
void addService(String name, IBinder service);
IBinder getService(String name);
String[] listServices();
}
工作流程:
1. ServiceManager 启动时,向 Binder Driver 注册自己
2. 各个系统服务启动时,调用 addService() 将自己注册到 ServiceManager
3. 客户端需要调用某个服务时,调用 getService() 从 ServiceManager 获取该服务的 Binder 对象
4. 通过 Binder 对象进行跨进程通信
让我们看一个实际的代码示例:
// 客户端获取系统服务
IBinder binder = ServiceManager.getService("activity");
IActivityManager am = IActivityManager.Stub.asInterface(binder);
// 调用远程方法
am.startActivity(intent);
// 服务端注册服务
ServiceManager.addService("activity", new ActivityManagerService());
2.4 Binder 驱动的工作原理
Binder 驱动是内核模块(binder.ko),它提供了几个关键系统调用:
// Binder 驱动的主要 ioctls
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_MAJOR _IO('b', 2)
#define BINDER_VERSION _IOWR('b', 3, struct binder_version)
#define BINDER_THREAD_EXIT _IO('b', 4)
#define BINDER_VERSION _IOWR('b', 7, struct binder_version)
#define BINDER_SET_CONTEXT_MGR _IO('b', 8)
#define BINDER_THREAD_INIT _IO('b', 10)
binder_write_read 结构体是 Binder 通信的核心数据结构:
struct binder_write_read {
// 写入数据
signed long write_size;
signed long write_consumed;
unsigned char write_buffer[0];
// 读取数据
signed long read_size;
signed long read_consumed;
unsigned char read_buffer[0];
};
通信过程:
- 客户端调用
binder_write_read发送请求 - Binder 驱动将请求放入目标进程的等待队列
- 目标进程被唤醒,从队列中取出请求处理
- 处理完成后,通过同样的机制返回响应
2.5 AIDL 和 Binder 的关系
AIDL(Android Interface Definition Language)是 Binder 的”语法糖”,它自动生成 Binder 通信的代码。
// ICalculator.aidl
interface ICalculator {
int add(int a, int b);
int subtract(int a, int b);
long multiply(long a, long b);
}
编译后生成的代码(ICalculator.java):
public interface ICalculator extends android.os.IInterface {
public int add(int a, int b) throws android.os.RemoteException;
public int subtract(int a, int b) throws android.os.RemoteException;
public long multiply(long a, long b) throws android.os.RemoteException;
public static abstract class Stub extends android.os.Binder
implements ICalculator {
private static final java.lang.String DESCRIPTOR = "ICalculator";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data,
android.os.Parcel reply, int flags)
throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSACTION_add:
data.enforceInterface(DESCRIPTOR);
int arg0 = data.readInt();
int arg1 = data.readInt();
int result = this.add(arg0, arg1);
reply.writeNoException();
reply.writeInt(result);
return true;
// ... 其他方法
}
return super.onTransact(code, data, reply, flags);
}
}
public static ICalculator asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof ICalculator))) {
return ((ICalculator) iin);
}
return new ICalculator.Stub.Proxy(obj);
}
private static class Proxy implements ICalculator {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
@Override
public int add(int a, int b) throws android.os.RemoteException {
android.os.Parcel data = android.os.Parcel.obtain();
android.os.Parcel reply = android.os.Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
data.writeInt(a);
data.writeInt(b);
mRemote.transact(Stub.TRANSACTION_add, data, reply, 0);
reply.readException();
int result = reply.readInt();
return result;
} finally {
data.recycle();
reply.recycle();
}
}
// ... 其他方法
}
}
从这个生成的代码可以看出:
- Stub:服务端需要实现的抽象类,处理来自客户端的请求
- Proxy:客户端代理类,负责将请求封装成 Parcel 并通过 transact() 发送给服务端
- transact():核心方法,通过 Binder 驱动发送数据
- onTransact():核心方法,在服务端接收和处理数据
2.6 Binder 的内存映射机制
Binder 使用内存映射(mmap)来实现高效的数据传输,避免了传统 IPC 中的数据拷贝:
客户端进程 Binder 驱动 服务端进程
┌──────────┐ ┌──────────┐ ┌──────────┐
│ │ │ │ │ │
│ Parcel │──writeParcel()──>│ Buffer │──readParcel()-->│ Parcel │
│ │ │ │ │ │
└──────────┘ └──────────┘ └──────────┘
│ │ │
│ mmap 共享内存 │ mmap 共享内存 │
└─────────────────────────────┼─────────────────────────────┘
│
┌───────┴───────┐
│ Binder 驱动 │
│ (内核空间) │
└───────────────┘
关键点:
- 客户端调用
Parcel.writeParcelable()将数据写入 Binder buffer - Binder 驱动将这个 buffer 映射到服务端进程的地址空间
- 服务端直接读取 mmap 后的内存,无需额外拷贝
- 数据使用完毕后,内核释放映射
这就是 Binder 比 Socket、管道更快的根本原因。
三、读懂 Framework 层源码:实战技巧
3.1 如何高效阅读源码
很多开发者面对 Framework 源码时感到无从下手,以下是一套实用的阅读方法:
第一步:理解调用链
Android 的 Framework 层代码量非常庞大,但核心调用链是有规律的。比如 Activity 的启动流程:
Activity.startActivity()
→ Activity.startActivityForResult()
→ Instrumentation.execStartActivity()
→ ActivityManager.getService().startActivity()
→ ActivityStackSupervisor.startActivityMayWait()
→ ActivityStack.startActivityLocked()
→ ActivityStack.resumeTopActivityInnerLocked()
→ ActivityThread.performLaunchActivity()
→ Activity.onCreate()
画这个调用链图的时候,你会发现 Framework 的设计非常清晰:
- 上层 API 调用会委托给下层处理
- Binder IPC 发生在
ActivityManager.getService()这一步 - 实际的 Activity 创建在
ActivityThread中完成
第二步:找到关键类
对于每个功能模块,找到它的核心管理类:
| 功能模块 | 核心管理类 | 作用 |
|---|---|---|
| Activity 管理 | ActivityManagerService | 管理 Activity 的生命周期和任务栈 |
| 窗口管理 | WindowManagerService | 管理所有窗口的创建、显示、布局 |
| 包管理 | PackageManagerService | 管理系统安装的应用 |
| 输入管理 | InputManagerService | 处理触摸、按键等输入事件 |
| 电源管理 | PowerManagerService | 管理设备的电源状态 |
| 电池管理 | BatteryService | 管理电池状态和充电 |
| 网络管理 | NetworkManagementService | 管理网络连接 |
| 通知管理 | NotificationManagerService | 管理系统通知 |
| 内容提供者 | ContentProvider | 管理数据共享 |
第三步:追踪数据流向
以一个具体的例子来演示如何追踪数据流。假设我们要理解”点击 Home 键返回桌面”的过程:
// 1. 从 InputManagerService 开始
// frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
private void dispatchKeyEvent(EventEntry entry) {
// 获取当前 focus 的 window
WindowState focusedWindow = mFocusedWindow;
// 分发按键事件
if (focusedWindow != null) {
focusedWindow.interceptKeyEvent(event);
}
}
// 2. 到 WindowManagerService
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public void addWindow() {
// 处理 Home 键
if (event.getKeyCode() == KeyEvent.KEYCODE_HOME) {
// 调用 ActivityManagerService
mService.mActivityManager.handleHomeEvent();
}
}
// 3. 到 ActivityManagerService
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
void handleHomeEvent() {
// 获取当前的 top activity
ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
// 关闭当前 activity
if (top != null) {
stopAppLocked(top, false);
}
// 启动 Home 应用
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
mStackSupervisor.startHomeActivityLocked();
}
通过这样的追踪,你可以清晰地看到数据是如何从输入系统流向窗口管理系统,再流向活动管理系统的。
3.2 常用的源码阅读工具
工具一:IDE 的跳转到定义功能
Android Studio 的 Cmd/Ctrl + 点击 功能是最基本的工具。但更高效的是使用:
- Call Hierarchy(调用层级):查看某个方法被哪些地方调用
- Find Usages(查找引用):查看某个类/方法的所有使用位置
- Structure View(结构视图):查看类的成员结构
工具二:mtrace 工具
mtrace 是一个命令行工具,可以追踪方法调用:
# 查看方法的调用链
mtrace -p com.android.server.am.ActivityManagerService \
-m startActivity
# 查看某个进程的 Binder 调用
mtrace --pid 1234 --binder
工具三:Perfetto / Android Studio Profiler
这些工具可以帮助你在运行时观察 Framework 层的调用情况:
Perfetto 追踪示例:
- 系统启动时间追踪
- Activity 启动追踪
- Binder 调用追踪
- 渲染帧追踪
3.3 源码阅读的最佳实践
实践一:从问题出发,反向追踪
不要试图从头到尾读完所有源码。应该:
- 遇到具体问题(比如某个崩溃)
- 根据异常堆栈找到出错的代码位置
- 向上追踪调用链,理解为什么会出错
- 向下追踪实现,理解底层机制
实践二:绘制调用图
对于复杂的调用链,画调用图是非常有帮助的:
[InputDispatcher] → [WindowManagerService]
↓
[ActivityManagerService] → [ActivityThread]
↓
[ApplicationThread] → [Activity]
实践三:添加日志验证
有时候阅读源码不如实际调试。在关键位置添加日志:
// 在 ActivityStackSupervisor.startActivityLocked() 中添加
if (DEBUG_STATES) {
Slog.d(TAG, "startActivityLocked: r=" + r
+ " callingPackage=" + r.callingPackage);
}
四、实战:解决卡顿问题
4.1 卡顿的本质
卡顿的本质是 UI 线程(主线程)做了太多工作,导致无法在 16ms 内完成一帧的绘制。
Android 的 VSync 机制:
每 16ms(60fps)触发一次 VSync 信号
↓
UI 线程需要完成:
- 测量(measure)
- 布局(layout)
- 绘制(draw)
- 提交(submit)
↓
如果超过 16ms,就会产生掉帧,用户感知为卡顿
4.2 卡顿的常见原因
原因一:主线程网络请求
// 错误的做法
new Thread(() -> {
// 在主线程执行网络请求
String result = httpClient.get("https://api.example.com/data");
// 更新 UI
textView.setText(result);
}).start();
// 正确的做法
new Thread(() -> {
String result = httpClient.get("https://api.example.com/data");
// 回到主线程更新 UI
runOnUiThread(() -> textView.setText(result));
}).start();
原因二:复杂的 Layout 嵌套
<!-- 错误的做法:过多的嵌套 -->
<LinearLayout>
<LinearLayout>
<LinearLayout>
<LinearLayout>
<TextView />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- 正确的做法:使用 ConstraintLayout 扁平化 -->
<ConstraintLayout>
<TextView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</ConstraintLayout>
原因三:主线程数据库操作
// 错误的做法
public void loadUserData() {
// 在主线程查询数据库
User user = db.getUser(userId);
textView.setText(user.name);
}
// 正确的做法
public void loadUserData() {
new Thread(() -> {
User user = db.getUser(userId);
runOnUiThread(() -> textView.setText(user.name));
}).start();
}
原因四:大量的对象创建
// 错误的做法:每次测量都创建新对象
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 每次 onMeasure 都会创建新的 HashMap
Map<String, String> map = new HashMap<>();
map.put("width", String.valueOf(getMeasuredWidth()));
map.put("height", String.valueOf(getMeasuredHeight()));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
// 正确的做法:复用对象
private Map<String, String> cachedMap = new HashMap<>();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
cachedMap.clear();
cachedMap.put("width", String.valueOf(getMeasuredWidth()));
cachedMap.put("height", String.valueOf(getMeasuredHeight()));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
4.3 卡顿检测工具
工具一:Choreographer 追踪
// 使用 Choreographer 追踪 VSync
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
long frameDuration = System.nanoTime() - frameTimeNanos;
if (frameDuration > 16_000_000) { // 超过 16ms
Log.e("卡顿", "帧耗时: " + (frameDuration / 1_000_000) + "ms");
}
Choreographer.getInstance().postFrameCallback(this);
}
});
工具二:TraceView
# 通过 adb 启动 trace
adb shell am trace -p com.example.app
# 分析 trace 文件
# 在 Android Studio 中打开 .trace 文件
工具三:Systrace
# 生成系统级追踪
python systrace.py -o trace.html -t 10 gfx view wm am
# 在浏览器中打开 trace.html 进行分析
工具四:帧率监控
// 监控帧率
public class FrameMonitor {
private static final int TARGET_FPS = 60;
private long lastFrameTime;
private int frameCount;
public void onFrame() {
long now = SystemClock.uptimeMillis();
if (lastFrameTime > 0) {
long delta = now - lastFrameTime;
float fps = 1000f / delta;
if (fps < TARGET_FPS * 0.8f) { // 低于 48fps 报警
Log.w("帧率监控", "帧率过低: " + fps);
}
}
lastFrameTime = now;
frameCount++;
}
}
4.4 卡顿的源码级分析
分析 Activity 启动时的卡顿
在 ActivityThread.performLaunchActivity() 中:
// frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
long start = SystemClock.uptimeMillis();
// 1. 创建 Activity 实例
Activity activity = instantiateActivity();
// 2. 创建 Context
ContextImpl context = createBaseContext();
// 3. 创建 Intent
Intent intent = r.intent;
// 4. Attach
activity.attach(context, ...);
// 5. 创建 Window
activity.mWindow = new PhoneWindow(this);
// 6. 加载 View
activity.mLoadedApplication = app;
// 7. 调用 onCreate
activity.performCreate(icicle);
// 8. 调用 onStart
activity.performStart();
long end = SystemClock.uptimeMillis();
Log.d("Activity启动", "耗时: " + (end - start) + "ms");
return activity;
}
在这个方法中,我们可以发现:
instantiateActivity()会反射创建 Activity 对象activity.attach()会设置 Window、PhoneWindow 等activity.performCreate()会调用onCreate()activity.performStart()会调用onStart()
每一步都可能成为卡顿的瓶颈,需要针对具体情况分析。
分析 View 绘制时的卡顿
在 ViewRootImpl.performTraversals() 中:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals() {
// host 是根 View
final View host = mView;
// 1. 测量
measureChild(host, lp, viewWidth, viewHeight);
// 2. 布局
layoutChild(host, lp, viewWidth, viewHeight);
// 3. 绘制
drawChild(canvas, host, drawingTime);
// 4. 触发 VSync
mChoreographer.postCallback(Choreographer.CALLBACK_DRAW,
mDrawRunnable, null);
}
测量和布局是最耗时的操作,特别是当 View 层级较深时。
五、实战:解决崩溃问题
5.1 常见崩溃类型
类型一:NullPointerException
// 崩溃示例
public void onViewCreated(View view, Bundle savedInstanceState) {
TextView textView = view.findViewById(R.id.text_view);
// 如果 findViewById 返回 null,就会崩溃
textView.setText("Hello"); // NullPointerException!
}
// 防御性编程
public void onViewCreated(View view, Bundle savedInstanceState) {
TextView textView = view.findViewById(R.id.text_view);
if (textView != null) {
textView.setText("Hello");
}
}
类型二:IllegalStateException
// 崩溃示例
public void doSomething() {
// Fragment 已经被移除,但尝试操作它
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.remove(this).commit(); // IllegalStateException!
}
// 正确的做法
public void doSomething() {
if (!isRemoved() && !isDestroyed()) {
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.remove(this).commit();
}
}
类型三:ArrayIndexOutOfBoundsException
// 崩溃示例
public String getItem(int position) {
return mList.get(position); // 如果 position 超出范围,崩溃
}
// 正确的做法
public String getItem(int position) {
if (position >= 0 && position < mList.size()) {
return mList.get(position);
}
return null;
}
类型四:SecurityException
// 崩溃示例:没有权限
public void getDeviceId() {
String deviceId = Settings.Secure.getString(
getContentResolver(),
Settings.Secure.ANDROID_ID
); // 在某些版本中需要权限
}
// 正确的做法:检查权限
public void getDeviceId() {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
String deviceId = tm.getDeviceId();
}
}
5.2 崩溃的源码级分析
分析 ANR(Application Not Responding)
ANR 是 Android 中最常见的崩溃类型之一。触发条件:
1. 主线程在 5 秒内没有响应输入事件
2. BroadcastReceiver 在 10 秒内没有完成 onReceive()
3. Service 在 20 秒内没有完成操作
在 ActivityManagerService 中,ANR 的触发逻辑如下:
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
void noteProcessStart(String processName, boolean isTopApp) {
// 记录进程启动时间
ProcessRecord app = getProcessRecordLocked(processName);
app.startTime = SystemClock.uptimeMillis();
}
void appNotResponding(ProcessRecord app, ...) {
// 发送 ANR broadcast
Intent intent = new Intent(Intent.ACTION_APP_NOT_RESPONDING);
intent.setComponent(new ComponentName("com.android.settings",
"com.android.settings.Settings$AppNotRespondingActivity"));
sendBroadcast(intent);
// 弹出 ANR 对话框
showDialogLocked(app, ...);
// 杀掉进程
Process.killProcess(app.pid);
}
分析 OOM(OutOfMemoryError)
OOM 的触发逻辑在 ActivityThread 中:
// frameworks/base/core/java/android/app/ActivityThread.java
private void handleLowMemory() {
// 清理缓存
ActivityManagerNative.getDefault().trimMemory(
ActivityManager.TRIM_MEMORY_COMPLETE);
// 如果仍然内存不足,杀掉后台进程
if (mLowMemoryNumProcesses > 0) {
removeProcessLocked();
}
}
5.3 崩溃监控与预防
监控方案一:全局异常捕获
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static CrashHandler instance;
private Thread.UncaughtExceptionHandler defaultHandler;
private CrashHandler() {
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
public static CrashHandler getInstance() {
if (instance == null) {
instance = new CrashHandler();
}
return instance;
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
// 1. 记录崩溃信息
Log.e("CrashHandler", "未捕获的异常: ", ex);
// 2. 上报崩溃
reportCrash(ex);
// 3. 交给系统处理
if (defaultHandler != null) {
defaultHandler.uncaughtException(thread, ex);
} else {
Process.killProcess(Process.myPid());
}
}
private void reportCrash(Throwable ex) {
// 上传到服务器
new Thread(() -> {
String log = getStackTraceString(ex);
uploadToServer(log);
}).start();
}
}
// 应用初始化时注册
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Thread.setDefaultUncaughtExceptionHandler(CrashHandler.getInstance());
}
}
监控方案二:ANR 监控
public class AnrMonitor implements Application.ActivityLifecycleCallbacks {
private long activityStartTime;
@Override
public void onActivityResumed(Activity activity) {
activityStartTime = SystemClock.uptimeMillis();
}
@Override
public void onActivityPaused(Activity activity) {
long pauseTime = SystemClock.uptimeMillis();
long duration = pauseTime - activityStartTime;
// 如果暂停时间过长,可能是 ANR
if (duration > 5000) {
Log.w("ANR监控", "Activity 暂停时间过长: " + duration + "ms");
reportAnr(activity);
}
}
}
六、综合实战:从源码到问题的完整链路
6.1 一个完整的案例分析
让我们通过一个真实的案例来展示如何从问题出发,追踪到源码,再解决问题。
问题描述:用户报告 App 在启动时经常卡顿,有时甚至 ANR。
分析步骤:
第一步:收集信息
1. 查看日志:
adb logcat -d > crash.log
2. 查看 ANR 轨迹:
adb shell cat /data/anr/traces.txt
3. 查看帧率:
adb shell dumpsys activity recents
第二步:定位问题
从 traces.txt 中发现:
"main" prio=5 tid=1 Sleeping
| group="main" sCount=1 dsCount=0 flags=1 obj=0x751c0a80 self=0x7f5b3c0000
| sysTid=1234 nice=0 cgrp=default sched=0/0 handle=0x7f5b8c0000
| state=S schedstat=( 123456789 123456789 123 ) utm=12 stm=0 core=0 HZ=100
| stack=0x7fffffff000-0x7fffffff000 stackSize=8MB
| held mutexes=
kernel: futex_wait_queue_me+0x14/0x20
kernel: futex_wait+0xcc/0x1d0
kernel: do_futex+0x12c/0x8d0
kernel: SyS_futex+0x80/0x120
kernel: ret_fast_syscall+0x0/0x58
- sleeping on wake up 0xffffff8c
at java.lang.Object.wait(Native method)
- waiting on <0x12345678> (a java.lang.VMThread) held by tid=1
at java.lang.Thread.sleep(Thread.java:302)
- locked <0x12345678> (a java.lang.VMThread)
at com.example.app.MyApplication.onCreate(MyApplication.java:45)
从堆栈可以看到,卡顿发生在 MyApplication.onCreate() 的第 45 行,是一个 Thread.sleep() 调用。
第三步:查看源码
// MyApplication.java
@Override
public void onCreate() {
super.onCreate();
// 问题代码:在主线程中 sleep
try {
Thread.sleep(500); // 第 45 行
} catch (InterruptedException e) {
e.printStackTrace();
}
// 初始化其他模块
initModuleA();
initModuleB();
initModuleC();
}
第四步:分析原因
Thread.sleep() 在主线程中调用了 500ms,这远远超过了 ANR 的 5 秒限制。而且这个 sleep 可能阻塞了后续的初始化操作。
第五步:解决方案
// 修改后的代码
@Override
public void onCreate() {
super.onCreate();
// 使用异步初始化
new Thread(() -> {
initModuleA();
initModuleB();
initModuleC();
}).start();
}
// 或者使用异步任务
@Override
public void onCreate() {
super.onCreate();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
initModuleA();
initModuleB();
initModuleC();
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
// 初始化完成,更新 UI
}
}.execute();
}
第六步:验证
# 重新编译安装
adb install app.apk
# 监控启动时间
adb shell am start -W com.example.app/.MainActivity
# 查看日志
adb logcat | grep "启动时间"
6.2 另一个案例:Binder 死锁
问题描述:App 在调用某个系统服务时卡死。
分析步骤:
第一步:查看 traces.txt
"main" prio=5 tid=1 TIMED_WAIT
| group="main" sCount=1 dsCount=0 flags=1 obj=0x751c0a80 self=0x7f5b3c0000
| sysT=2345 nice=0 cgrp=default sched=0/0 handle=0x7f5b8c0000
| state=S schedstat=( 234567890 234567890 234 ) utm=23 stm=0 core=0 HZ=100
| stack=0x7fffffff000-0x7fffffff000 stackSize=8MB
| held mutexes=
kernel: __switch_to+0x88/0x9c
kernel: __rt_mutex_slowlock+0x90/0x160
kernel: rt_mutex_timedlock+0x54/0x80
kernel: futex_wait_queue_me+0x90/0xe0
kernel: futex_wait+0xcc/0x1d0
kernel: do_futex+0x12c/0x8d0
kernel: SyS_futex+0x80/0x120
kernel: ret_fast_syscall+0x0/0x58
- waiting for another thread to release a spinlock
at sun.misc.Unsafe.park(Native method)
- parking to wait for <0x12345679> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:226)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireNanos(AbstractQueuedSynchronizer.java:931)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireNanos(AbstractQueuedSynchronizer.java:1240)
at android.os.BinderProxy.transactNative(Native method)
at android.os.BinderProxy.transact(BinderProxy.java:503)
at android.app.IActivityManager$Stub$Proxy.startActivity(IActivityManager.java:8888)
at android.app.Activity.startActivity(Activity.java:3845)
at com.example.app.MainActivity.onClick(MainActivity.java:78)
从堆栈可以看到,死锁发生在 BinderProxy.transact() 调用时。
第二步:分析原因
Binder 死锁通常发生在以下情况:
- 服务端在处理请求时又调用了客户端的 Binder 方法
- 两个进程互相等待对方释放 Binder 锁
第三步:查看源码
// 客户端代码
public void clickButton(View view) {
// 调用系统服务
IActivityManager am = ActivityManagerNative.getDefault();
// 这行代码会触发 Binder 调用
am.startActivity(intent); // 死锁在这里
}
// 服务端代码(可能是某个系统服务)
public void startActivity(Intent intent) throws RemoteException {
// 处理请求
synchronized (this) {
// ...
}
// 问题:在处理过程中又回调了客户端
callback.onResult(result); // 这会导致死锁!
}
第四步:解决方案
// 修改服务端代码,避免回调客户端
public void startActivity(Intent intent) throws RemoteException {
// 处理请求
synchronized (this) {
// ...
}
// 不回调客户端,而是通过其他方式通知
sendNotification(result);
}
七、总结与建议
7.1 读懂源码的关键
- 从调用链入手:找到核心类的关键方法,画出调用图
- 理解设计意图:每个类、每个方法为什么这样设计
- 结合实际场景:用真实的问题来验证你的理解
- 动手调试:源码看懂了,还要实际跑起来调试
7.2 解决卡顿的建议
- 避免主线程耗时操作:网络请求、数据库操作、文件 IO 都要放在子线程
- 优化 Layout:减少嵌套层级,使用 ConstraintLayout
- 缓存计算结果:避免重复计算
- 使用异步加载:图片、数据等使用异步加载
- 监控帧率:实时检测卡顿
7.3 解决崩溃的建议
- 做好异常处理:关键代码加 try-catch
- 进行边界检查:数组、集合操作前检查边界
- 检查权限:使用需要权限的功能前检查
- 监控崩溃:接入崩溃监控 SDK
- 及时修复:看到崩溃及时修复
7.4 最后的话
读源码从来不是一件轻松的事情,但它是成为高级 Android 开发者的必经之路。不要试图一次性读完所有源码,而是带着问题去读,在实践中理解。
记住这句话:“源码是最好的老师,问题是最好的钥匙。”
希望这篇文章能帮助你更好地理解 Android 系统源码,解决实际的开发问题。如果有任何问题或建议,欢迎交流讨论!
注:本文基于 Android 12+ 源码编写,部分 API 在不同版本中可能有差异,请以实际版本为准。
