Android 面试知识复习指南

Java / Kotlin 基础

Kotlin 和 Java 的主要区别是什么?为什么 Android 开发推荐使用 Kotlin?

考点

对 Kotlin 优势的理解,以及现代语言特性的认知。

参考答案

  1. 空安全:Kotlin 在编译期就区分了可空和非空类型,有效避免了烦人的 NullPointerException
  2. 扩展函数:可以在不修改原类的情况下,为类添加新函数,非常实用(如 TextView.setBold())。
  3. 数据类:一行代码 data class User(val name: String, val age: Int) 就自动生成了 getter/setter, equals(), hashCode(), toString(), copy() 等。
  4. 协程:用于简化异步编程,以同步的方式写异步代码,告别"回调地狱",比 AsyncTaskRxJava 更轻量、易用。
  5. 更简洁:大量的语法糖,如类型推断、lambda 表达式、字符串模板、默认参数等,让代码更短、更可读。
  6. 完全互操作: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 的生命周期是怎样的?请详细说明。

考点

最最基础的知识,必须滚瓜烂熟。

参考答案

  1. onCreate():首次创建时调用。进行视图绑定、数据初始化。
  2. onStart():Activity 对用户可见,但还无法交互。
  3. onResume():Activity 进入前台,可以与用户交互。
  4. onPause():Activity 正在失去焦点(如被对话框遮挡)。此方法需快速执行,因为下一个 Activity 要等它执行完才能 onResume
  5. onStop():Activity 对用户完全不可见
  6. 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 的区别
    • IntentServiceService 的子类,它内部有一个工作线程,会按顺序处理传入的 Intent 请求。
    • 处理完所有请求后,IntentService自动停止,无需手动调用 stopSelf()
    • 注意:在 Android 8.0 (O) 之后,后台执行限制使得 Service 的使用场景变少,更推荐使用 WorkManagerJobScheduler 来执行后台工作。

BroadcastReceiver 的动态注册和静态注册有什么区别?

考点

对广播灵活性和生命周期的理解。

参考答案

注册方式 实现 生命周期 特点
动态注册 在代码中(如 Activity 的 onCreate)通过 registerReceiver() 注册。 与注册的组件(如 Activity)绑定,组件销毁时必须调用 unregisterReceiver() 无法接收应用未启动时的广播
静态注册 AndroidManifest.xml 中注册。 独立于组件,即使应用未运行也能接收广播。 即使应用未运行,系统也能唤醒它来接收广播(如开机广播、网络状态变化)。但从 Android 8.0 开始,对大部分隐式广播进行了限制,静态注册的用途大大减少。

AOSP & Framework 核心

AOSP 系统启动流程是怎样的?

考点

对 Android 系统从开机到桌面显示的完整启动过程的理解。

参考答案

精简版概述:Android 系统启动是一个环环相扣的过程,主要经历了 Boot Loader -> Kernel -> init -> Zygote -> System Server -> Launcher 这几个关键阶段。

详细流程

  1. 引导层 (Bootloader)
    • 电源按下,芯片执行预置的 Boot ROM 代码。
    • Bootloader 负责初始化硬件、设置内存等最基本的环境,然后加载并启动 Linux Kernel
  2. 内核层 (Kernel)
    • Linux Kernel 启动,初始化各种硬件驱动、内存管理、进程调度等核心功能。
    • 内核启动完成后,首先运行第一个用户空间进程 init (PID=1)
  3. init 进程
    • init 进程是所有用户空间进程的始祖
    • 它的主要工作是:
      • 解析 init.rc 脚本:定义了系统启动时需要运行的服务、执行的命令以及创建的环境变量。
      • 启动守护进程:如 ueventd(负责设备节点创建)、logd(日志系统)等。
      • 启动 Zygote 和 ServiceManager
  4. Zygote 进程
    • 为什么需要 Zygote? 为了快速启动 Android 应用组件共享资源,节省内存
    • 启动过程
      • init 进程根据 init.rc 配置,启动 Zygote。
      • Zygote 会预加载 Android 框架层所需的几乎所有核心类、资源以及共享库
      • 启动完成后,Zygote 会创建一个 Socket (/dev/socket/zygote),并进入循环监听状态,等待请求。
  5. System Server 进程
    • 这是什么? 这是 Android 系统核心服务的集合地
    • 如何启动? Zygote fork() 出自己,创建出 System Server 进程。
    • 职责:System Server 进程内部会创建并启动各种系统核心服务,并注册到 ServiceManager 中。
  6. 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 引用),大家通过这个网络互相交流、调用服务、传递信息。

为什么要有这样的分工?—— 设计哲学

  1. 解耦与单一职责
    • Zygote 只做一件事,并且做到极致:快速、高效地 fork 进程。
    • Binder 也只做一件事,并且做到极致:安全、高效地进行进程间方法调用。
  2. 依赖关系
    • Binder 机制本身(包括 ServiceManager)的初始化,依赖于 System Server 进程。
    • 而 System Server 进程本身,就是由 Zygote 通过 Socket 机制 fork 出来的
    • 因此,Zygote Socket 是更底层的基石,它甚至在 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 文件?

  1. 权限隔离与安全
    • 这是最核心的原因。每个 Socket 文件都有不同的所有者和组。
    • 只有 rootsystem 才能连接 zygote,防止恶意应用随意创建进程。
    • 只有 radio 组的进程才能与 rild 通信,防止普通应用直接操作危险的射频功能。
  2. 解耦与模块化
    • 每个系统服务(Vold, Rild, Installd)都是独立的守护进程。
    • 它们通过自己专用的 Socket 与系统的其他部分通信。
  3. 可靠性
    • 独立的守护进程如果崩溃,通常可以被重新启动,而不会导致整个系统崩溃。

它们与 Binder 的关系是什么?

你可以把系统内部的通信想象成一个混合架构:

  • Binder:是 "城市主干道"。用于应用层系统服务之间频繁、复杂、面向对象的通信。
  • 各种 Socket 文件:是 "专用高速公路"或"后勤通道"。用于系统底层关键服务之间稳定、安全、有时需要特权的通信。

Android 面试知识复习指南 © 2023 - 涵盖基础到 Framework 的全面知识点