安卓framework面试题常考哪些核心知识点?

99ANYc3cd6
预计阅读时长 44 分钟
位置: 首页 安卓 正文

启动流程与系统核心服务

这是 Framework 的核心,也是面试官必问的领域,主要考察你对系统从按下电源键到桌面显示的整个生命周期,以及关键系统服务的理解。

安卓framework面试题
(图片来源网络,侵删)

请简述 Android 系统的启动流程。

考察点: 宏观流程理解,从 Bootloader 到 Zygote,再到 System Server 和 AMS。

回答思路:

  1. Bootloader 阶段: 上电后,加载 Bootloader,它负责初始化硬件,并将 Linux Kernel 加载到内存中。
  2. Kernel 阶段: Linux Kernel 启动,完成硬件驱动初始化,并创建 init 进程(PID=1)。
  3. Init 进程阶段: init 进程是用户空间的第一个进程,它解析 init.rc 脚本,启动关键服务:
    • 启动 vold(用于管理虚拟磁盘)。
    • 启动 netd(网络守护进程)。
    • 启动 rild(RIL 守护进程,负责与基带通信)。
    • 最关键: 启动 Zygote 进程。
  4. Zygote 阶段:
    • Zygote 是 Android 系统的“孵化器”,它是一个虚拟机进程,在系统启动时被创建。
    • Zygote 会预加载 Android 核心类库和资源,为后续应用启动做好准备。
    • Zygote 会创建一个 Socket 通信端口,用于接收 ActivityManagerService (AMS) 的请求来创建新的应用进程。
  5. System Server 阶段:
    • Zygote fork 出 System Server 进程。System Server 是所有系统服务的宿主,运行在独立的进程中。
    • System Server 启动时,会创建并启动各种核心服务,如:
      • ActivityManagerService (AMS)
      • WindowManagerService (WMS)
      • PackageManagerService (PMS)
      • PowerManagerService (电源管理)
      • NotificationManagerService (通知管理)
      • 等等。
  6. 启动 Home/Launcher:
    • System Server 中的 AMS 启动后,会查询 PMS,找到 Home 应用(桌面 Launcher),并启动它的主 Activity。
  7. 用户交互阶段:
    • 当用户点击桌面上的 App 图标时,AMS 会通过 Socket 请求 Zygote fork 出一个新的 App 进程。
    • 新进程创建后,会初始化应用环境,并最终启动目标 App 的主 Activity,界面呈现给用户。

什么是 Zygote?它的作用是什么?

考察点: 对 Zygote 机制的理解。

回答思路:

安卓framework面试题
(图片来源网络,侵删)
  • 定义: Zygote 是 Android 系统的第一个 Java 进程,是所有 Android 应用的“父进程”。
  • 核心作用:
    1. 预加载资源: 在启动时,Zygote 会预加载 Android Framework 的核心类库(如 android.app, android.os 等)、共享库(如 art.so)以及常用的资源文件(如 drawable, layout),这样做的好处是,所有由 Zygote fork 出来的子进程(即 App 进程)都共享这部分资源和代码,大大节省了内存和启动时间。
    2. 进程孵化器: Zygote 监听一个 Socket,当 AMS 需要启动一个新的 App 进程时,会通过这个 Socket 发送请求,Zygote 收到请求后,通过 fork() 系统调用创建一个子进程。fork() 是 Linux 的高效进程创建方式,子进程会复制父进程(Zygote)的整个虚拟机空间和资源,从而快速启动。
    3. 管理 App 生命周期: Zygote 与 ActivityManagerService 紧密配合,负责按需创建和销毁 App 进程。

AMS 是什么?它的主要职责有哪些?

考察点: 对系统核心服务 ActivityManagerService 的理解。

回答思路:

  • 定义: ActivityManagerService (AMS) 是 Android 系统中负责管理所有 Activity 应用组件的核心服务,运行在 System Server 进程中,它是 Android 四大组件的“大管家”。
  • 主要职责:
    1. Activity 管理与生命周期: 负责启动、暂停、停止、销毁 Activity,并管理 Activity 的生命周期回调,它维护了一个 Activity 栈(ActivityStack)来记录当前所有 Activity 的状态。
    2. 进程管理: 管理所有应用的进程,包括创建、调度、优先级管理和回收,当系统内存不足时,AMS 会根据进程的优先级(如前台进程、可见进程、服务进程等)决定杀死哪些进程来回收内存。
    3. 任务栈管理: 管理任务栈,实现“返回”功能,以及处理 SingleTask, SingleInstance 等 launchMode。
    4. Intent 分发: 接收来自系统或用户的 Intent 请求,并分发给相应的组件(Activity, Service, BroadcastReceiver)。
    5. 提供信息查询: 提供查询当前运行中应用、服务、进程等信息的接口(如 getRunningAppProcesses())。

UI 渲染与视图系统

这部分主要考察 App 是如何将界面绘制到屏幕上的,涉及 View 体系、窗口管理、绘制流程等。

View 的绘制流程是怎样的?请简述 Measure, Layout, Draw 三个过程。

考察点: 对 UI 渲染原理的深入理解。

安卓framework面试题
(图片来源网络,侵删)

回答思路: View 的绘制流程从 ViewRootImplperformTraversals() 方法开始,它会依次触发 measure()layout()draw() 三个过程。

  1. Measure (测量) - 决定尺寸:

    • 目的: 测量 View 和其子 View 的大小。
    • 过程:
      • ViewRootImpl 调用顶级 View(通常是 DecorView)的 measure() 方法。
      • 顶级 View 会递归地调用所有子 View 的 measure() 方法。
      • onMeasure() 方法中,View 会根据父容器给的 MeasureSpec 和自身的 LayoutParams 计算出自己的宽高。
      • View 会得到一个宽高值,存储在 mMeasuredWidthmMeasuredHeight 中。
  2. Layout (布局) - 决定位置:

    • 目的: 测量完成后,确定 View 在父容器中的位置(坐标)。
    • 过程:
      • ViewRootImpl 调用顶级 View 的 layout() 方法。
      • 顶级 View 会递归地调用所有子 View 的 layout() 方法。
      • onLayout() 方法中,父容器会根据子 View 的测量尺寸和布局规则(如 gravity, layout_gravity),计算并设置每个子 View 的 left, top, right, bottom 坐标。
  3. Draw (绘制) - 绘制内容:

    • 目的: 将 View 的内容绘制到屏幕上。
    • 过程:
      • ViewRootImpl 调用顶级 View 的 draw() 方法。
      • draw() 方法内部会按顺序执行以下步骤:
        1. 绘制背景 (drawBackground())。
        2. 保存画布状态 (save())。
        3. 绘制 View 的内容 (onDraw()),这是自定义 View 的核心,需要重写此方法来绘制自己的图形。
        4. 绘制子 View (dispatchDraw()),如果有子 View,会递归调用子 View 的 draw() 方法。
        5. 绘制装饰(如滚动条)(onDrawScrollBars())。
        6. 恢复画布状态 (restore())。

请描述一下从点击 App 图标到界面显示,发生了什么?

考察点: 将 AMS、WMS、View 绘制流程串联起来的综合能力。

回答思路: 这是一个非常经典的综合题,可以看作是启动流程的延伸。

  1. 点击图标: 用户点击桌面(Launcher)上的 App 图标。
  2. Launcher 发起请求: Launcher 通过 Binder 调用 ActivityManagerServicestartActivity() 方法,并传入要启动的 Activity 的 ComponentName
  3. AMS 处理请求:
    • AMS 收到请求后,会做一系列检查,包括权限校验、任务栈校验等。
    • AMS 会查询 PackageManagerService 获取目标 App 的信息(如 AndroidManifest.xml 中定义的 Activity)。
    • 检查要启动的 Activity 是否已经存在于某个任务栈中,如果存在,可能会将任务栈移动到前台;如果不存在,则创建一个新的任务栈。
  4. 创建应用进程:
    • 如果目标 App 的进程不存在,AMS 会通过 Socket 请求 Zygote 进程 fork 出一个新的 App 进程。
    • 新进程创建后,会初始化主线程(Looper, Handler),并绑定 Application
  5. 创建 Context:
    • 在 App 进程中,ActivityThreadmain() 方法会创建 Activity 所需的 Context 对象(ContextImpl)。
  6. 创建并初始化 Activity:
    • Instrumentation 会通过 反射 创建 Activity 实例。
    • 调用 Activityattach() 方法,将 ContextWindow 等对象关联起来。
    • 调用 ActivityonCreate() 方法。
  7. 创建 View 并添加到 Window:
    • onCreate() 中,通常会调用 setContentView() 来加载布局文件。
    • setContentView() 会创建一个 PhoneWindow,并将解析后的布局文件(View 树)添加到 Window 中。
  8. WMS 管理窗口:
    • Activity 生命周期执行到 onResume() 之后,ViewRootImpl 会建立起来。
    • ViewRootImpl 会通过 Session 调用 WindowManagerService (WMS),将 DecorView 添加到窗口管理器中。
    • WMS 会将这个窗口(AppWindowToken)加入到屏幕窗口堆栈中,并计算其显示区域。
  9. 开始 View 绘制流程:
    • ViewRootImplrequestLayout() 会被调用,它会触发前面提到的 Measure -> Layout -> Draw 完整绘制流程。
    • 绘制完成后,SurfaceFlinger 会将各个窗口的 Surface 合成并显示到屏幕上。

View 和 ViewGroup 的区别?onInterceptTouchEvent() 的作用是什么?

考察点: 事件分发机制的理解。

回答思路:

  • View 和 ViewGroup 的区别:

    • View: 是所有 UI 组件的基类,代表一个最基本的 UI 单元,如 Button, TextView,它没有子 View。
    • ViewGroup: 是 View 的子类,是一个可以包含其他 View(子 View)的容器,如 LinearLayout, RelativeLayout,它既是 View(可以显示和接收事件),也是 View 的集合(可以管理子 View)。
  • onInterceptTouchEvent() 的作用:

    • 这是 ViewGroup 的一个方法,用于拦截触摸事件。
    • 当一个触摸事件发生时,事件会先传递到根 ViewGroup,然后由其 dispatchTouchEvent() 方法进行分发。
    • 在将事件分发给子 View 之前,ViewGrouponInterceptTouchEvent() 会被调用。
    • 返回值:
      • 返回 false (默认): 表示不拦截,事件将继续传递给目标子 View 的 onTouchEvent()
      • 返回 true 表示拦截事件,事件将不再传递给子 View,而是由当前 ViewGroup 自己的 onTouchEvent() 来处理。
    • 典型场景: ScrollViewViewPager,当用户在 ScrollView 内部水平滑动时,ScrollViewonInterceptTouchEvent() 会判断出滑动意图是水平的,于是拦截事件,阻止事件继续传递给子 View,自己处理滚动逻辑。

多线程与异步任务

这部分考察 App 的性能和响应能力,是面试的重点。

Android 中的多线程有哪几种方式?它们有什么区别?

考察点: 对 Android 线程模型和异步方案的演进理解。

回答思路:

  1. Thread: Java 原生的线程类,最基础的方式,但需要手动管理线程,处理同步和通信(如 Handler)比较繁琐,容易出错。
  2. AsyncTask: Android 提供的轻量级异步类,它封装了 ThreadHandler,可以在后台线程执行任务,并在主线程更新 UI。
    • 缺点: 在 Honeycomb (API 13) 之后,AsyncTask 被改为串行执行(一个接一个),无法并行,且任务的生命周期与 Activity 绑定,Activity 在任务结束前销毁,可能会导致内存泄漏或崩溃。
  3. HandlerThread: 一个自带 Looper 的线程,它封装了创建 Looper 和消息循环的细节,适合需要在一个独立线程中处理串行任务的场景(如下载、数据库操作)。
    • 特点: 线程会一直存活,可以处理多个耗时任务,但任务是串行的。
  4. IntentService: 一个继承自 Service 的服务,它内部使用 HandlerThread 来处理异步任务。
    • 特点: 适合执行不需要与用户交互的、耗时但不需要结果返回的后台任务,任务执行完后,IntentService 会自动停止,它解决了 AsyncTask 的生命周期问题。
  5. ThreadPoolExecutor (线程池): Java 提供的标准线程池工具类,功能强大,可以精确控制线程池的核心线程数、最大线程数、任务队列、拒绝策略等,是现代 Android 开发中处理并发任务的首选。
  6. RxJava / Kotlin Coroutines: 响应式编程和协程,它们通过操作符和挂起函数,以声明式的方式处理异步任务,极大地简化了异步代码的复杂度,是目前最主流、最优雅的异步方案。

为什么在子线程中不能更新 UI?Android 是如何解决这个问题的?

考察点: 对 Android UI 线程模型的理解。

回答思路:

  • 为什么不能?

    • UI 操作是“非线程安全”的: Android 的 UI 控件(如 TextView, Button)不是为多线程并发访问设计的,如果在多个线程同时修改一个 UI 控件的状态,可能会导致不可预期的结果(如界面显示错乱、数据不一致)。
    • View 的绘制与线程绑定: View 的绘制流程(measure, layout, draw)是由主线程(UI 线程)的 Choreographer 驱动的,与屏幕刷新率(通常是 60Hz)同步,只有主线程才能安全地执行这些绘制操作。
  • 如何解决? Android 提供了一套基于 Handler 的跨线程通信机制。

    1. Handler 消息机制:
      • Handler: 用于发送和处理消息,它关联着一个 Looper
      • Looper: 每个线程(除了主线程)都有一个 Looper,它在一个无限循环中从 MessageQueue 中取出消息,并分发给对应的 Handler 处理。主线程默认自带一个 Looper
      • MessageQueue (消息队列): 一个先进先出的消息队列,用来存放所有待处理的消息。
      • Message (消息): 携带数据,可以被 Handler 发送和处理。
    2. 工作原理:
      • 在子线程中,如果需要更新 UI,就创建一个 Message 对象,将要执行的操作(如 setText())封装进去,然后通过 Handler 将其发送到 MessageQueue
      • 这个 Handler 必须是在主线程中创建的,这样它就关联着主线程的 Looper
      • 主线程的 Looper 会在其消息循环中取出这个 Message,并交给对应的 Handler 在主线程中执行。
      • 这样,耗时的操作在子线程,而安全的 UI 操作在主线程,完美解决了问题。

组件通信与数据存储

请简述 Binder 机制。

考察点: 对 Android IPC(进程间通信)核心机制的理解深度。

回答思路: Binder 是 Android 中一种高效的 IPC 机制,它基于 Linux 的 open()ioctl() 等底层驱动实现。

  • 核心思想: “代理模式”“内存映射”

  • 主要组成角色:

    1. Client (客户端): 发起请求的进程。
    2. Server (服务端): 提供服务的进程。
    3. Service Manager (SM): 管理所有 Binder 服务的“中介所”,它维护了一个服务名称和 Binder 引用的映射表。
    4. Binder 驱动: 运行在内核空间,是 Binder 通信的核心,它负责数据的传递、引用的管理和进程间的同步。
  • 通信流程 (以 AMS 和 App 为例):

    1. 注册: System Server 中的 AMS 启动后,会向 Service Manager 注册自己,提供一个 Binder 对象。
    2. 获取引用: App 进程需要与 AMS 通信时,会先向 Service Manager 查询 AMS 的 Binder 引用。
    3. 数据拷贝: App 进程(Client)将请求数据(如 startActivity 的参数)写入用户空间的一块内存。
    4. 内核中转: App 进程通过 ioctl 系统调用,告诉 Binder 驱动:“我要把这块数据发送给 AMS”。
    5. 直接传递: Binder 驱动在内核空间中,不需要进行数据拷贝,直接将 Client 的内存地址映射到 Server 的内存地址。AMS(Server)可以直接读取 Client 的数据。
    6. 返回结果: AMS 处理完请求后,将结果通过同样的方式写回,Binder 驱动再将其映射回 Client 的内存空间,Client 就收到了返回结果。
  • 优点:

    • 高效: 相比传统的 Socket(需要两次数据拷贝:用户空间->内核空间->目标用户空间),Binder 只需要一次拷贝(用户空间->内核空间,然后通过内存映射直接共享)。
    • 面向对象: Client 拿到的不是数据,而是一个 Server 对象的“代理”,可以像调用本地方法一样调用远程服务的方法,非常直观。
    • 安全性: 每个进程都有 UID,Binder 驱动在通信时会进行身份验证,保证了进程间的安全通信。

SharedPreferences 是线程安全的吗?它的底层实现是什么?

考察点: 对常用数据存储方式的理解和深入分析。

回答思路:

  • 线程安全吗?

    • 在写入操作时是线程安全的。 因为 SharedPreferencesapply()commit() 方法内部都使用了 Editor 对象,而这个对象在修改数据时会使用 synchronized 关键字加锁,确保了在同一时间只有一个线程能进行写入操作。
    • 但在读取操作时,不是绝对安全的。 读取操作没有加锁,如果在读取过程中,另一个线程正在执行写入(commit()),可能会读到不一致的数据,由于写入操作本身是同步的,这种不一致的情况在实际中比较少见,但理论上存在。
  • 底层实现是什么?

    • 数据存储: SharedPreferences 的数据是以 XML 文件的格式存储在应用的私有目录下的(/data/data/<package_name>/shared_prefs/<name>.xml)。
    • 内存缓存: 为了提高读取性能,SharedPreferences 在第一次被加载时,会将 XML 文件中的所有键值对解析并存储在一个内存中的 Map 结构里,后续的读取操作都是直接从这个内存 Map 中获取,速度非常快。
    • 写入过程:
      1. 调用 edit() 获取一个 Editor 对象。
      2. 通过 EditorputXXX() 方法修改内存中的 Map
      3. 调用 apply()commit() 提交修改。
      4. commit() 同步方法,它会立即将内存中的 Map 写入到 XML 文件中,并返回写入结果,会阻塞调用线程。
      5. apply() 异步方法(推荐),它也会先将修改写入内存 Map,但不会立即写入文件,它会创建一个 Runnable 任务,放入一个 IntentServiceSharedPreferencesImpl$EditorImpl$ApplyResult)的队列中,由该 Service 在后台线程执行写入操作。apply() 会立即返回,不阻塞 UI 线程,写入成功后会通知监听器。

高级面试题

这些问题通常用于考察候选人的深度和广度,以及对性能优化的理解。

什么是 ANR?如何定位和解决 ANR?

考察点: 对 App 性能和稳定性的掌握。

回答思路:

  • 什么是 ANR?

    • ANR (Application Not Responding),即应用无响应,当主线程(UI 线程)在规定时间内(通常是 5 秒)没有响应用户输入事件(如点击、按键)或没有执行完某个操作时,系统就会弹出一个 ANR 对话框。
  • ANR 的类型:

    1. Key ANR: 在 5 秒内没有处理输入事件。
    2. KeyDispatchingTimeout: 在前台 Activity 的 onKeyDown()onKeyUp() 中,在 5 秒内没有完成处理。
    3. Broadcast ANR: 在 10 秒内没有处理完一个前台广播(android.intent.action.TIME_TICK 除外)。
    4. Service ANR: 在前台服务的 onCreate(), onStartCommand(), onBind() 方法中,在 20 秒内没有完成执行。
  • 如何定位 ANR?

    1. 查看 Logcat: 发生 ANR 时,系统会打印出关键的 ANR 日志,最重要的是 main 线程的堆栈信息。
      • 使用命令 adb logcat | grep -i "ANR\|ActivityManager"
      • 找到 main 线程的堆栈,通常它会卡在某个方法调用上,Binder_Xxx(表示在等待 Binder 返回)、Sleeping(线程休眠)、或者某个自定义方法,这个堆栈就是 ANR 发生的位置。
    2. 分析 traces.txt 文件:
      • ANR 发生后,系统会在 /data/anr/ 目录下生成一个 traces.txt 文件。
      • 使用 adb pull /data/anr/traces.txt 将其拉到本地。
      • 用文本编辑器或 Android StudioCPU Profiler 打开它,里面详细记录了 ANR 发生时所有线程的堆栈信息,是定位问题的最直接证据。
  • 如何解决 ANR?

    • 根本原因: 主线程执行了耗时操作。
    • 解决方案:
      1. 避免主线程耗时:
        • 将网络请求、数据库操作、文件读写、复杂的计算等耗时任务放到子线程中执行。
        • 使用 HandlerThread, ThreadPoolExecutor, RxJava, Kotlin Coroutines 等工具。
      2. 优化代码逻辑:
        • 避免在 onCreate(), onResume() 等生命周期方法中做耗时操作。
        • 避免在 UI 控件的 setXXXListener 中执行耗时操作。
      3. 使用异步回调: 如果耗时操作必须等待结果,使用异步方式(如 Callback, Future, LiveData)在主线程更新 UI。
      4. 检查 BroadcastReceiver BroadcastReceiver 中有耗时操作,考虑使用 JobSchedulerWorkManager 替代静态广播。
      5. 检查 WebView WebView 的初始化和加载在某些情况下可能导致 ANR,确保在子线程初始化。

描述一下你对 Jetpack Compose 的理解,它和传统 View 体系相比有什么优势?

考察点: 对 Android UI 开发新范式的了解和思考。

回答思路:

  • 理解:

    • Jetpack Compose 是 Google 推出的一个现代化的 Android UI 工具包,它使用 声明式 UI 的编程范式来构建界面。
    • 它允许开发者使用 Kotlin 代码来描述 UI 的状态和 UI 应该如何根据状态变化,而不是像传统 View 体系那样通过 XML 布局和命令式代码(findViewById, view.setText())来操作 UI。
  • 与传统 View 体系的优势:

    1. 更少的样板代码: 不再需要编写 findViewByIdAdapterViewHolder 等大量模板代码,UI 的逻辑和状态管理都集中在 Compose 函数中。
    2. 强大的状态驱动: UI 的状态(如数据)和 UI 的表现(界面)是紧密绑定的,当状态发生变化时,Compose 会自动、高效地重新渲染 UI,开发者只需要关心“状态是什么”,而不关心“如何更新 UI”。
    3. 完全的 Kotlin 支持: UI 布局就是 Kotlin 代码,可以直接使用 Kotlin 的所有语言特性(如 lambda、高阶函数、协程),代码更简洁、更易于维护和测试。
    4. 开发效率高: 预览功能允许开发者在不运行 App 的情况下,实时看到 UI 代码的渲染效果,极大地缩短了开发-调试-修改的循环。
    5. 性能更好: Compose 底层使用 Skia 图形库进行渲染,并利用了高效的重组机制,它只重组发生变化的部分,而不是整个视图树,性能接近原生。
    6. 更易于维护: 由于 UI 是声明式的,代码逻辑清晰,易于理解,也更容易进行单元测试。

面试准备建议

  1. 理论与实践结合: 不要只背答案,要理解每个知识点背后的原理,不要只记 ANR 的定义,要能说出为什么会发生、怎么查、怎么改。
  2. 画图辅助: 对于启动流程、事件分发、Binder 机制等复杂流程,自己动手画图是加深理解最好的方式。
  3. 准备项目经验: 面试官很可能会问:“你在项目中遇到过最复杂的 Framework 问题是什么?你是怎么解决的?” 准备 1-2 个你真实经历过的、能体现你深度思考能力的案例。
  4. 关注源码: 对于核心类(如 ViewRootImpl, ActivityThread, ActivityManager),至少要能说出它们的主要方法和调用关系,能阅读源码是 Framework 工程师的加分项。
  5. 了解前沿: 除了传统 Framework,也要对 Jetpack Compose、Hilt、WorkManager 等现代 Android 开发技术栈有所了解。

祝你面试顺利!

-- 展开阅读全文 --
头像
TeamViewer9安卓版怎么用?
« 上一篇 12-03
USB设备无法识别怎么办?
下一篇 » 12-03

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]