Android 开发核心面试题解析
一、Android 系统启动流程详解
当用户按下电源键触发开机时,Android 系统的启动过程涉及多个关键阶段:
- BootLoader 加载:从 ROM 中预定义的位置加载引导程序 BootLoader 到 RAM 中。
- 内核启动:执行 BootLoader 程序启动 Linux Kernel。
- Init 进程:启动用户级别的第一个进程
init。该进程会解析init.rc脚本进行初始化工作,包括挂载文件系统、创建工作目录以及启动系统服务进程(如 Zygote、Service Manager、Media 等)。 - Zygote 孵化:在 Zygote 进程中进一步启动
system_server进程。 - 系统服务启动:在
system_server进程中启动 AMS(Activity Manager Service)、WMS(Window Manager Service)、PMS(Package Manager Service)等核心服务。 - 桌面显示:服务启动完成后,AMS 打开 Launcher 应用的 Home Activity,最终呈现手机桌面。
二、System_Server 为何在 Zygote 中启动
system_server 选择在 Zygote 中启动而非由 init 直接启动,主要基于以下原因:
- 资源复用:Zygote 作为一个孵化器,提前加载了部分资源(如 JNI 函数、共享库、常用类、主题资源)。
- Copy-On-Write (COW):通过 fork() 机制创建其他进程时,利用 COW 机制可以直接使用这些资源,无需重新加载,显著提升了应用启动速度。
三、Zygote 与 System_Server 的分工
为什么不直接使用 system_server 去孵化应用进程?
- 职责分离:
system_server运行了 AMS、WMS 等服务,这对普通应用程序是不必要的负担。 - 多线程 Fork 风险:进程的
fork()对多线程不友好。它仅将发起调用的线程拷贝到子进程。如果父进程存在多线程,可能导致死锁。system_server内部包含大量线程,不适合直接用于 fork 新进程。
死锁机制分析
在 POSIX 标准中,fork() 复制整个用户空间数据及所有系统对象。若父进程中某子线程持有锁,fork() 后子进程中该线程消失但锁状态被保留。子进程尝试获取该锁时将无法解锁,导致死锁。
四、Binder 通信机制优势
Binder 是 Android 特有的 IPC(进程间通信)机制,相比传统方式具有显著优势:
1. 性能方面
- 共享内存:0 次数据拷贝。
- Binder:1 次数据拷贝。
- Socket/管道:2 次数据拷贝。
2. 稳定性方面
- Binder 基于 C/S 架构,客户端与服务端职责明确且相互独立。
- 共享内存控制复杂,难以维护。
3. 安全性方面
- 传统 IPC 缺乏安全措施,依赖上层协议。
- 传统 IPC 无法可靠获取对方 UID/PID,易被恶意程序利用。
- Binder 支持实名和匿名 Binder,具备高安全性。
五、跨进程通信(IPC)方式对比
| 方式 | 说明 | 底层实现 |
|---|---|---|
| Intent | 简单通信,如拨打电话 | 系统封装 |
| ContentProvider | 数据库存储数据共享 | 底层同样为 Binder |
| Broadcast | 广播通信 | 系统消息传递 |
| AIDL | 接口共享数据,支持 RPC | Binder |
六、多进程应用场景
- 数据隔离:需要向其他应用获取数据或保护敏感数据。
- 模块独立:某些模块(如 WebView)需单独运行在进程中,避免崩溃影响主进程。
- 内存扩展:加大应用可使用的内存空间(获取多份堆内存)。
七、Binder 机制原理与 AIDL 示例
Binder 是 Android 中 Activity、Service、Broadcast、ContentProvider 之间通信的桥梁。
基本流程
- 定义传输对象并进行序列化。
- 在相同包名下定义 AIDL 接口及传输对象。
- 编译生成对应的 Java 类。
- 创建 Service 编写服务端代码。
- 客户端使用
bindService连接。 - 在
ServiceConnection回调中获取服务端接口对象。 - 调用接口发送消息。
AIDL 接口定义示例
// IMyService.aidl
package com.example.demo;
interface IMyService {
void sendMessage(String msg);
String getData();
}
服务端实现
public class MyService extends Service {
private final IMyService.Stub binder = new IMyService.Stub() {
@Override
public void sendMessage(String msg) {
Log.d("TAG", "Received: " + msg);
}
@Override
public String getData() {
return "Hello from Server";
}
};
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
八、ANR(Application Not Responding)详解
ANR 是指应用程序响应不够灵敏,系统弹出对话框提示用户。常见触发条件如下:
- Activity:5 秒内未响应用户输入事件。
- BroadcastReceiver:10 秒内未完成处理。
- Service:20 秒内(前台)无法完成处理。
常见原因
- 主线程被 IO 操作阻塞(网络 IO、磁盘 IO)。
- 主线程中存在耗时计算。
- 错误操作如
Thread.wait或Thread.sleep。
查看与分析
日志路径:/data/anr/traces.txt。
解决方案
将所有耗时操作(网络、SQL、复杂逻辑)移至子线程,并通过 Handler、RxJava 等方式更新 UI。确保界面流畅度,必要时显示进度条。
九、OOM(Out of Memory)与内存优化
OOM 指需要的内存空间大于系统分配的空间,导致程序 Crash。
常见原因
- 加载大图片导致内存溢出。
- 大量内存泄露。
- 线程数量过多,队列容量设置过大。
解决策略
- 引用类型:使用软引用(SoftReference)或弱引用(WeakReference),当内存不足时自动释放缓存 Bitmap。
- 资源回收:不再使用的 Bitmap 调用
recycle()。 - 文件缓存:考虑使用文件缓存替代内存缓存。
- 线程池:使用统一的线程池管理类。
内存限制
一般应用默认堆内存不超过 32M(可通过 adb shell getprop dalvik.vm.heapgrowthlimit 查看)。超过阈值将抛出 OutOfMemoryError。
十、内存泄露、溢出与抖动
内存抖动
- 现象:大量对象短时间内创建又释放,GC 频率过高。
- 解决:避免循环中创建临时对象;避免在
onDraw中创建 Paint 或 Bitmap。
内存泄漏
- 定义:对象生命周期结束但仍被引用,无法被 GC 回收。
- 常见场景与修复:
- 资源未关闭:Cursor、File 等应在 finally 块中关闭。
- Adapter 缓存:使用
convertView复用视图。 - Bitmap 未回收:及时调用
recycle()。 - Context 引用:单例中使用 Application Context 代替 Activity Context。
- Handler 泄漏:静态内部类配合 WeakReference。
- WebView 泄漏:动态添加并在销毁时移除,使用弱引用 Context。
工具推荐
使用 LeakCanary 检测内存泄漏。
十一、WebView 常见问题与安全
- 安全漏洞:API 16 之前
addJavascriptInterface存在远程代码执行风险,需过滤方法或使用新版 API。 - 生命周期:布局中使用时,需在销毁前调用
destroy()并移除 View。 - 耗电问题:关闭页面时及时销毁 WebView。
- 硬件加速:渲染异常时可关闭当前页面的硬件加速。
十二、屏幕旋转后的生命周期
- 首次进入:
onCreate→onStart→onResume。 - 旋转切换:原 Activity 销毁(
onPause→onStop→onDestroy),新 Activity 重建(onCreate→onStart→onResume)。 - 切回竖屏:同上,横屏活动销毁,竖屏活动重建。
建议通过 onSaveInstanceState 保存临时状态,避免重复加载数据。
十三、总结
本文涵盖了 Android 面试中的核心知识点,包括系统启动、Binder 机制、内存管理及组件生命周期。掌握这些内容有助于深入理解 Android 底层原理,提升开发质量与面试通过率。在实际开发中,应注重代码规范与性能优化,避免常见的内存泄漏与 ANR 问题。


