Android 面试知识复习指南
Java / Kotlin 基础
Kotlin 和 Java 的主要区别是什么?为什么 Android 开发推荐使用 Kotlin?
考点
对 Kotlin 优势的理解,以及现代语言特性的认知。
参考答案
- 空安全:Kotlin 在编译期就区分了可空和非空类型,有效避免了烦人的
NullPointerException。 - 扩展函数:可以在不修改原类的情况下,为类添加新函数,非常实用(如
TextView.setBold())。 - 数据类:一行代码
data class User(val name: String, val age: Int)就自动生成了getter/setter,equals(),hashCode(),toString(),copy()等。 - 协程:用于简化异步编程,以同步的方式写异步代码,告别"回调地狱",比
AsyncTask和RxJava更轻量、易用。 - 更简洁:大量的语法糖,如类型推断、lambda 表达式、字符串模板、默认参数等,让代码更短、更可读。
- 完全互操作:100% 与 Java 互操作,可以无缝在现有 Java 项目中使用。
谈谈你对 Kotlin 协程的理解。它如何解决异步问题?
考点
对现代异步编程框架的掌握程度。
参考答案
- 本质:协程是"轻量级线程",它基于线程池,但挂起时不阻塞线程,一个线程中可以运行大量协程,切换开销极小。
- 核心概念:
suspend(挂起)函数。标记为suspend的函数可以在不阻塞线程的情况下暂停其执行,并在适当的时候恢复。 - 如何解决:使用
launch,async/await等构建器来启动协程。通过Dispatchers.IO,Dispatchers.Main,Dispatchers.Default来指定协程运行的线程(上下文)。这样,我们可以轻松地将网络请求、数据库操作放在后台线程,然后在主线程更新 UI,代码是线性的、顺序的,没有嵌套回调。
== 和 equals() 的区别?
考点
对对象相等性判断的深入理解。
参考答案
==:- 在 Java 中,对于基本类型比较的是值,对于引用类型比较的是内存地址(是否是同一个对象)。
- 在 Kotlin 中,
==等价于调用equals(),比较的是内容是否相等。而===才比较地址。
equals():用于比较两个对象的内容是否逻辑相等。默认实现(来自Object类)比较的是地址,但通常我们会重写它(如String,Data Class)。
Android 四大组件
Activity 的生命周期是怎样的?请详细说明。
考点
最最基础的知识,必须滚瓜烂熟。
参考答案
onCreate():首次创建时调用。进行视图绑定、数据初始化。onStart():Activity 对用户可见,但还无法交互。onResume():Activity 进入前台,可以与用户交互。onPause():Activity 正在失去焦点(如被对话框遮挡)。此方法需快速执行,因为下一个 Activity 要等它执行完才能onResume。onStop():Activity 对用户完全不可见。onDestroy():Activity 被销毁前调用,进行资源释放。
特殊情况:当 Activity 被部分遮挡(如出现一个非全屏的 Dialog),只会执行 onPause(),不会执行 onStop()。
Activity 的启动模式(Launch Mode)有哪几种?它们的区别和应用场景?
考点
对任务栈(Task)和 Activity 实例管理的理解。
参考答案
| 启动模式 | 描述 | 应用场景 |
|---|---|---|
| standard(默认) | 每次启动都创建一个新实例。 | 普通 Activity |
| singleTop(栈顶复用) | 如果目标 Activity 已经在栈顶,则复用该实例,并调用 onNewIntent()。否则创建新实例。 |
适用于通知点击等场景,避免重复创建栈顶 Activity。 |
| singleTask(栈内复用) | 在一个新任务栈中只存在一个该 Activity 实例。如果已存在,则清除该实例之上的所有 Activity,使其位于栈顶,并调用 onNewIntent()。 |
适用于应用的主页。 |
| singleInstance | 单例模式。独自运行在一个任务栈中,且该任务栈中只有它自己。其他应用共享该实例。 | 适用于通话界面等需要完全隔离的场景。 |
Service 的两种启动方式及其生命周期?与 IntentService 的区别?
考点
对后台服务的理解。
参考答案
startService():- 生命周期:
onCreate()->onStartCommand()-> (服务运行) ->onDestroy() - 特点:服务与启动它的组件无关,会一直在后台运行,直到自己调用
stopSelf()或被别人stopService()。
- 生命周期:
bindService():- 生命周期:
onCreate()->onBind()-> (服务绑定) ->onUnbind()->onDestroy() - 特点:服务与组件(如 Activity)绑定,多个组件可以绑定到同一个服务。当所有绑定都解除时,服务即被销毁。
- 生命周期:
- 与 IntentService 的区别:
IntentService是Service的子类,它内部有一个工作线程,会按顺序处理传入的 Intent 请求。- 处理完所有请求后,
IntentService会自动停止,无需手动调用stopSelf()。 - 注意:在 Android 8.0 (O) 之后,后台执行限制使得
Service的使用场景变少,更推荐使用WorkManager或JobScheduler来执行后台工作。
BroadcastReceiver 的动态注册和静态注册有什么区别?
考点
对广播灵活性和生命周期的理解。
参考答案
| 注册方式 | 实现 | 生命周期 | 特点 |
|---|---|---|---|
| 动态注册 | 在代码中(如 Activity 的 onCreate)通过 registerReceiver() 注册。 |
与注册的组件(如 Activity)绑定,组件销毁时必须调用 unregisterReceiver()。 |
无法接收应用未启动时的广播。 |
| 静态注册 | 在 AndroidManifest.xml 中注册。 |
独立于组件,即使应用未运行也能接收广播。 | 即使应用未运行,系统也能唤醒它来接收广播(如开机广播、网络状态变化)。但从 Android 8.0 开始,对大部分隐式广播进行了限制,静态注册的用途大大减少。 |
AOSP & Framework 核心
AOSP 系统启动流程是怎样的?
考点
对 Android 系统从开机到桌面显示的完整启动过程的理解。
参考答案
精简版概述:Android 系统启动是一个环环相扣的过程,主要经历了 Boot Loader -> Kernel -> init -> Zygote -> System Server -> Launcher 这几个关键阶段。
详细流程:
- 引导层 (Bootloader)
- 电源按下,芯片执行预置的 Boot ROM 代码。
- Bootloader 负责初始化硬件、设置内存等最基本的环境,然后加载并启动 Linux Kernel。
- 内核层 (Kernel)
- Linux Kernel 启动,初始化各种硬件驱动、内存管理、进程调度等核心功能。
- 内核启动完成后,首先运行第一个用户空间进程
init(PID=1)。
- init 进程
init进程是所有用户空间进程的始祖。- 它的主要工作是:
- 解析
init.rc脚本:定义了系统启动时需要运行的服务、执行的命令以及创建的环境变量。 - 启动守护进程:如
ueventd(负责设备节点创建)、logd(日志系统)等。 - 启动 Zygote 和 ServiceManager。
- 解析
- Zygote 进程
- 为什么需要 Zygote? 为了快速启动 Android 应用组件 和 共享资源,节省内存。
- 启动过程:
init进程根据init.rc配置,启动 Zygote。- Zygote 会预加载 Android 框架层所需的几乎所有核心类、资源以及共享库。
- 启动完成后,Zygote 会创建一个 Socket (
/dev/socket/zygote),并进入循环监听状态,等待请求。
- System Server 进程
- 这是什么? 这是 Android 系统核心服务的集合地。
- 如何启动? Zygote
fork()出自己,创建出 System Server 进程。 - 职责:System Server 进程内部会创建并启动各种系统核心服务,并注册到 ServiceManager 中。
- Launcher 启动
- 当 System Server 中的核心服务启动就绪后,AMS 会向 Zygote 发送一个 Socket 请求。
- Zygote 收到请求后,再次
fork()出一个新的应用进程,用于运行 Launcher 应用。 - Launcher 进程启动其
ActivityThread,加载 Launcher 应用的代码和资源,最终将其 Main Activity 启动起来,我们也就看到了手机桌面。
总结流程图:
Power On -> Bootloader -> Linux Kernel -> init进程 -> (启动) Zygote -> (fork) System Server -> (启动系统服务) -> AMS请求Zygote -> (fork) Launcher进程 -> 桌面显示
Binder 与 Zygote Socket 文件有什么关系?
考点
对 Android 系统启动和运行机制中两个核心组件协作关系的理解。
参考答案
Binder 和 Zygote Socket 不是替代关系,而是协作关系,在系统生命周期的不同阶段、为不同目的而工作。
简单概括:
- Zygote Socket:用于创建新进程。
- Binder:用于进程创建成功后,进程之间的方法调用和数据通信。
职责分工:
| 特性 | Zygote Socket (/dev/socket/zygote) |
Binder (/dev/binder) |
|---|---|---|
| 核心目的 | 进程孵化 | 进程间通信 |
| 工作阶段 | 系统启动时、应用进程创建时 | 系统及应用进程运行时 |
| 通信模式 | 简单的请求-响应("请fork一个进程") | 复杂的远程方法调用(RPC) |
一个生动的比喻:
- Zygote Socket 就像一家医院的 "产科"。它的唯一职责是"生孩子"(创建新进程)。孩子生下来之后,产科的任务就基本完成了。
- Binder 就像社会的 "电话网络"。每个人(进程)都有一个电话号码(Binder 引用),大家通过这个网络互相交流、调用服务、传递信息。
为什么要有这样的分工?—— 设计哲学
- 解耦与单一职责:
- Zygote 只做一件事,并且做到极致:快速、高效地
fork进程。 - Binder 也只做一件事,并且做到极致:安全、高效地进行进程间方法调用。
- Zygote 只做一件事,并且做到极致:快速、高效地
- 依赖关系:
- Binder 机制本身(包括
ServiceManager)的初始化,依赖于 System Server 进程。 - 而 System Server 进程本身,就是由 Zygote 通过 Socket 机制 fork 出来的。
- 因此,Zygote Socket 是更底层的基石,它甚至在 Binder 环境完全准备好之前就必须工作。
- Binder 机制本身(包括
Socket 文件是否只有一个仅用于 Zygote 通信?
考点
对 Android 系统中多种 IPC 机制和系统架构的理解。
参考答案
不,完全不是。系统中存在很多个 Socket 文件,各自服务于不同的系统组件。
/dev/socket/zygote 只是其中最著名的一个,因为它负责"孵化"所有应用进程。整个系统更像一个由许多专用通道构成的通信网络。
常见的系统 Socket 文件:
| Socket 文件 | 所属用户/组 | 主要作用 |
|---|---|---|
zygote |
root:system |
进程孵化:接受请求,fork 新的应用进程和系统进程。 |
adbd |
system:system |
ADB 守护进程:用于与连接到电脑的 ADB 客户端进行通信,执行调试命令、文件传输等。 |
vold |
system:system |
Volume 守护进程:负责外部存储(如 SD 卡)的挂载、卸载、格式化等管理。 |
rild |
radio:radio |
Radio 接口层守护进程:与基带处理器通信,处理所有蜂窝网络相关的功能(通话、短信、移动数据)。 |
为什么需要这么多不同的 Socket 文件?
- 权限隔离与安全
- 这是最核心的原因。每个 Socket 文件都有不同的所有者和组。
- 只有
root或system才能连接zygote,防止恶意应用随意创建进程。 - 只有
radio组的进程才能与rild通信,防止普通应用直接操作危险的射频功能。
- 解耦与模块化
- 每个系统服务(Vold, Rild, Installd)都是独立的守护进程。
- 它们通过自己专用的 Socket 与系统的其他部分通信。
- 可靠性
- 独立的守护进程如果崩溃,通常可以被重新启动,而不会导致整个系统崩溃。
它们与 Binder 的关系是什么?
你可以把系统内部的通信想象成一个混合架构:
- Binder:是 "城市主干道"。用于应用层和系统服务之间频繁、复杂、面向对象的通信。
- 各种 Socket 文件:是 "专用高速公路"或"后勤通道"。用于系统底层关键服务之间稳定、安全、有时需要特权的通信。
Android 面试知识复习指南 © 2023 - 涵盖基础到 Framework 的全面知识点