Android Framework
知识手册
从一个窗口的启动、绘制与送显出发,理解系统如何协调任务、多屏、输入和跨进程调用。
建议先读启动与窗口送显,再学习分屏跨屏和 Binder。正文可直接用于口述,追问补充用于深入理解;源码类名和机制以目标 Android 分支为准。
没有找到相关内容
试试 “Binder”、“跨屏”、“绘制” 或 “Perfetto”。
启动与系统服务
01AMS ATMS WMS PMS DMS 和 IMS 分别负责什么?必会
AMS 主要管理进程、Service、Broadcast、ContentProvider 等组件运行;Android 10 起,Activity 和任务管理的主要职责拆分到 ATMS,包括启动、任务栈、生命周期和多窗口协调。WMS 管理窗口、尺寸位置、可见性、焦点、Insets 以及与转场相关的窗口状态。PackageManagerService 管理包解析、安装、组件查询和包状态;DMS 管理逻辑显示设备、显示配置和显示生命周期;IMS 通常指 InputManagerService,负责输入设备和输入分发体系。SurfaceFlinger 运行在独立进程,负责图层合成和送显。PMS 也可能指 PowerManagerService,面试时我会先确认语境。
这些服务大多在 system_server 内运行,但服务内部包含不同线程和 Native 组件。InputMethodManagerService 管理输入法,不能和 InputManagerService 混为一谈。
02一次 Activity 冷启动从点击图标到显示界面经过哪些阶段?必会
Launcher 发出启动请求,通过 Instrumentation 和 Binder 进入 ATMS。系统解析目标组件,检查权限与启动限制,根据启动模式选择任务和目标 Display,并协调已有 Activity 的状态。如果目标进程不存在,AMS 的进程管理路径向 Zygote 请求创建进程。子进程进入 ActivityThread.main,建立主线程 Looper 并向系统注册。系统发送应用绑定和 Activity 启动事务,应用完成 Application 与相关 Provider 初始化,创建 Activity 并执行生命周期。窗口被添加到 WMS 后,ViewRootImpl 调度首轮布局绘制,应用提交缓冲区,最终由 SurfaceFlinger 与 HWC 完成合成和显示。
如果进程已经存在,进程创建和应用绑定阶段可以省略。旧版本把更多 Activity 管理职责放在 AMS,具体类名和调用顺序要结合目标分支。
03调用 startActivity 返回是否说明目标界面已经显示?必会
不能这样理解。startActivity 发出的是启动请求,普通启动接口返回时,系统可能已经完成了同步阶段的检查和任务决策,但目标进程创建、生命周期执行、布局绘制和送显仍可能继续进行。我要判断启动完成,会分别检查启动请求、Activity 状态、窗口首帧和实际呈现时间。把 startActivity 包在主线程 Handler.post 中,只是把请求推迟到当前消息之后执行,不能自动减少系统启动耗时,也不能证明界面已经显示。
NEW_TASK 改变任务选择行为,并不等于开启新进程或开启新线程;它也不是通用的启动优化开关。
04standard singleTop singleTask singleInstance 有什么区别?必会
standard 通常创建新实例。singleTop 只在目标实例恰好位于目标任务栈顶时复用,并通过 onNewIntent 接收请求;不在栈顶仍可能新建。singleTask 会按任务与实例匹配规则查找可复用实例,复用时通常清除其上方 Activity,并调用 onNewIntent;具体放置还受 taskAffinity 和 Intent flags 影响。singleInstance 的实例独占一个任务,其他 Activity 放到其他任务;较新版本还有 singleInstancePerTask,允许不同任务拥有各自实例。实际分析时,我会同时看 launchMode、taskAffinity、NEW_TASK、CLEAR_TOP 和 MULTIPLE_TASK,而不是只看一个配置。
CLEAR_TOP 命中 standard 实例时,目标实例也可能被销毁并重新创建;是否走 onNewIntent 要结合 SINGLE_TOP 等条件。
05Task Activity 进程和 Window 的关系是什么?必会
Task 是系统组织用户操作和返回行为的逻辑容器,内部按层级组织 ActivityRecord;Activity 是应用中的页面组件,Window 承载其界面窗口。进程是代码运行和资源隔离的单位,这几个概念不一一对应。一个进程可以运行多个 Activity,一个任务可以包含来自不同应用和进程的 Activity。一个 Activity 通常有主窗口,还可能通过 Dialog 等创建附加窗口。应用进程死亡后,系统仍可能保留任务记录,之后根据保存的状态恢复页面。
不能把任务栈当作 Java 调用栈,也不能认为 NEW_TASK 一定创建一个全新的任务。
06Zygote 和 SystemServer 是怎么启动的?必会
设备先经过 Bootloader、内核和 init。init 根据 rc 配置启动 app_process,进入 Zygote 初始化,预加载部分常用类和资源,并建立用于接收进程创建请求的 socket。主要 Zygote 启动 system_server,SystemServer 再通过 SystemServiceManager 按启动阶段初始化系统服务,并进入运行循环。应用进程一般由 Zygote fork 并进行 UID、GID、SELinux 等身份配置,这能复用预加载内容和写时复制页。部分设备启用 USAP 进程池,所以不能把所有应用创建都描述成收到请求后才现场 fork。
ServiceManager 负责 Binder 服务的注册与发现,SystemServiceManager 管理 Java 系统服务启动和启动阶段,两者职责不同。
07ActivityThread 为什么叫主线程 Application 和 Provider 在什么时候初始化?高频
ActivityThread 是应用侧协调组件生命周期和主线程消息的核心类,本身并不是 Thread 子类。进程入口 ActivityThread.main 创建主 Looper,向系统注册应用 Binder 接口,然后由主线程处理绑定应用和生命周期事务。应用绑定阶段会创建 Application 对象、安装适用的 ContentProvider,并调用 Application.onCreate;Provider.onCreate 往往早于 Application.onCreate。Provider 初始化如果包含重 I/O 或 SDK 工作,就可能直接进入冷启动关键路径。
Application 构造或 attachBaseContext 与 Application.onCreate 不是同一个时机;延迟 Provider 初始化之前必须确认 SDK 的真实初始化契约。
08如果要新增一个系统服务 应该怎样设计和接入?高频
我会先明确服务职责和权限边界,设计 Binder 接口、客户端 Manager 和错误返回,再决定服务由 system_server 还是独立进程承载。若采用 Java SystemService,会在适当启动阶段注册,通过 publishBinderService 提供跨进程接口,必要时通过 LocalServices 提供进程内接口。耗时工作放在受控工作线程,接口要考虑调用者 UID、用户空间、死亡恢复和并发访问。最后补充 dumpsys 状态、日志、接口兼容和服务端测试,验证未解锁、用户切换、服务重启等边界场景。
LocalServices 是 system_server 内的对象访问机制,不是另一种跨进程通信;跨 system/vendor 等稳定边界需要另外考虑 Stable AIDL 和版本兼容。
09Android 多用户和跨用户调用需要注意什么?高频
Android 的应用身份同时包含 userId 和 appId,同一个包在不同用户下的数据、运行状态和权限可能不同。跨用户启动、绑定或查询必须使用正确的 UserHandle 和对应系统权限,并验证目标用户是否运行、是否解锁、是否处于允许交互的状态。服务端不能因为包名相同就合并用户状态,缓存和回调也应按用户隔离。涉及开机或解锁前访问的数据,要区分设备加密存储 DE 和凭据加密存储 CE,并处理用户切换时的监听和资源清理。
在座舱单用户迁移场景,我会先检查原有多用户隔离假设,尤其是缓存键、服务连接、Provider 和 Display 对应关系。
绘制与窗口动画
10Activity Window DecorView ViewRootImpl 和 WMS 是什么关系?必会
Activity 负责页面生命周期,通常通过 PhoneWindow 管理自己的 Window。DecorView 是窗口内 View 树的根,包含应用内容和窗口装饰。ViewRootImpl 是应用侧 View 树与系统窗口之间的桥梁,管理遍历、输入和与 WMS 的通信,它不是一个 View。应用侧 WindowManager 经由 WindowManagerGlobal 创建 ViewRootImpl,后者通过 IWindowSession 等接口向 WMS 注册窗口。WMS 的 WindowState 表示系统侧窗口状态,SurfaceControl 则用于管理对应图层的几何和层级。
setContentView 主要把布局放入 DecorView 的内容容器,并不代表已经完成 addWindow、首轮绘制或送显。
11一个窗口添加到系统时 addWindow 和 relayoutWindow 分别做什么?必会
应用 WindowManager.addView 会建立 ViewRootImpl,并通过 Session 请求 WMS 添加窗口。WMS 检查窗口类型、token、权限和 Display,创建或关联 WindowState,设置输入相关信息。首轮及后续遍历中,应用会向系统请求 relayout,WMS 根据窗口属性和显示配置计算窗口几何、Insets、可见性和 Surface 状态,并把应用需要的结果返回。应用据此更新 View 树并渲染。不同 Android 版本里 Surface 创建和同步事务的细节有变化,我会按目标源码确认,避免把所有工作都归结为 addWindow。
BadTokenException 应先查 token 是否有效、Activity 是否已经结束、窗口类型是否正确和调用上下文是否匹配。
12一个 Button 从创建到真正显示在屏幕上 完整链路是什么?必会
布局加载后,Button 被加入 Activity 的 View 树。ViewRootImpl 在需要更新时安排 traversal,由 Choreographer 在对应帧执行测量、布局和绘制。measure 决定尺寸,layout 决定位置,draw 组织绘制内容;ViewGroup 通过 dispatchDraw 绘制子 View。硬件加速下,应用主线程更新 RenderNode 等绘制记录,RenderThread 驱动渲染并生成窗口缓冲区。缓冲区经图形缓冲传递机制交给 SurfaceFlinger,SurfaceFlinger 根据图层位置和遮挡进行合成,再由 HWC 和显示设备呈现。Button 通常和其他普通 View 一起绘入窗口内容,并不独占一个 SurfaceFlinger 图层。
onDraw 或 draw 返回只能说明应用侧相应工作完成,不能作为屏幕已经显示的证据。
13measure layout draw 和 dispatchDraw 分别负责什么?必会
measure 依据父容器约束和自身期望确定 measuredWidth 与 measuredHeight;layout 根据父容器给定的边界确定实际位置和大小,ViewGroup 再在 onLayout 中放置子 View;draw 组织背景、自身内容、子内容和前景等绘制阶段。普通控件的主要内容在 onDraw 中绘制,ViewGroup 通过 dispatchDraw 遍历并绘制子 View。自定义 View 重点实现测量和自身绘制,自定义 ViewGroup 还必须负责子 View 的测量和布局。
ViewGroup 是否绘制自身内容还受 willNotDraw 等条件影响;dispatchDraw 负责子节点,不等于整个 draw。
14MeasureSpec 和 wrap_content match_parent 是怎么协作的?高频
MeasureSpec 包含模式和大小:EXACTLY 表示尺寸已经确定,AT_MOST 表示不能超过指定上限,UNSPECIFIED 表示这次测量没有给出同样的大小限制。父容器综合自己的约束、padding、已占用空间和子节点 LayoutParams,通过 getChildMeasureSpec 等逻辑生成子节点约束。wrap_content 通常意味着在约束内使用内容期望尺寸,match_parent 则希望使用父容器可分配的空间,但具体结果依赖父容器的模式。自定义控件可用 resolveSizeAndState,把内容尺寸与约束合并。
不要直接把 wrap_content 等同于 AT_MOST,也不要把 match_parent 在所有情况下都等同于 EXACTLY。
15invalidate 和 requestLayout 有什么区别?必会
invalidate 表示内容需要更新,通常触发后续绘制;requestLayout 表示尺寸或布局关系可能变化,需要重新测量和布局。只改变颜色、进度或绘制内容时,我通常使用 invalidate;改变宽高、文本尺寸或影响父子布局的属性时,需要 requestLayout,很多属性 setter 会自行调用。实际 traversal 会根据脏标记合并多次请求,不是每调用一次就立刻完成一轮绘制。对已经挂载的 View,更新应在所属 UI 线程进行。
两者都不是立即送显的同步接口;不要为了保险在每次更新时无条件同时调用。
16硬件加速下主线程 RenderThread 和 GPU 分别做什么?高频
主线程处理输入、动画属性、测量布局以及 View 的绘制记录更新。HWUI 用 RenderNode 等对象描述绘制内容和属性,RenderThread 接收同步后的场景,执行渲染相关工作并向 GPU 提交命令。GPU 完成实际绘制后,产出缓冲区供后续合成。主线程仍可能因布局、记录更新、同步和其他任务过慢导致错过帧期限;RenderThread 和 GPU 也可能各自成为瓶颈。因此硬件加速不能理解成 UI 工作全部移到了后台线程。
部分 RenderThread 动画在主线程短时阻塞时仍可继续,但不能由此保证所有 View 动画或所有窗口转场持续流畅。
17SurfaceFlinger 和 HWC 如何分工?必会
SurfaceFlinger 收集图层缓冲区和状态,处理可见性、层级、变换、裁剪等合成信息,并与 Hardware Composer 协商各图层的合成方式。能由显示硬件直接处理的图层采用 device composition;需要客户端合成的图层由 SurfaceFlinger 的渲染路径处理,再把结果交给 HWC。HWC 对接设备显示能力并执行呈现相关操作。因此 App 负责生成内容,窗口系统负责组织窗口与图层状态,SurfaceFlinger 和 HWC 协作把内容呈现出来。
不能说 SurfaceFlinger 会重新调用 Button.onDraw,也不能说它对每一层都必然用 GPU 合成。
18Surface SurfaceControl BufferQueue 和 fence 分别是什么?必会
Surface 通常代表内容生产端可以绘制或提交缓冲区的目标;SurfaceControl 管理合成图层及其位置、缩放、透明度、裁剪、父子关系等状态。BufferQueue 在生产者和消费者间协调缓冲区的申请、排队、获取与复用,缓冲区可以通过句柄共享,传递并不是每帧把整张图复制到系统服务。fence 用于同步异步 GPU 和显示硬件工作,避免消费者读取尚未写完的内容,或生产者过早复用仍在使用的缓冲区。现代版本还会涉及 BLASTBufferQueue 和事务内提交缓冲区,具体实现要看分支。
acquire fence 和 release fence 对应不同使用阶段;Fence 的等待可能发生在下游,不应只看主线程耗时。
19VSync Choreographer 和 doFrame 是什么关系?必会
VSync 提供与显示节奏相关的时序信号。应用有动画、绘制或帧回调工作时,Choreographer 请求相应帧调度,收到事件后在所属 Looper 线程执行 doFrame,依次处理输入、动画、布局绘制和提交阶段;部分版本还有 Insets 动画等阶段。它让多种 UI 工作围绕同一帧时间协作。屏幕刷新不代表静态应用每次都执行完整的 doFrame,也不代表每次都会生成新缓冲区。60Hz 的帧周期约为 16.67ms,120Hz 约为 8.33ms,实际期限还要结合流水线和调度偏移。
主线程已有长任务时,VSync 到达也不能抢占正在执行的 Java 代码,帧回调会延后。
20doFrame 结束是不是表示这帧已经显示 如何确定首帧?必会
doFrame 结束只是应用侧帧处理到达某个阶段,后面还可能有 RenderThread、GPU、缓冲区等待、SurfaceFlinger 合成和实际呈现。确定首帧时,我会区分首次应用内容绘制、缓冲区提交和屏幕呈现,用 Perfetto 的应用与 SurfaceFlinger FrameTimeline、对应 Surface 或 layer,以及 present 相关信息关联起来。Actual Timeline 的区间也不能脱离所属轨道解释:应用帧与 Display 帧的终点含义不同。对于点击到首帧响应,要统一输入起点和实际呈现终点。
doFrame 后面的数字可能是 Trace 参数或帧标识,应看该轨道说明,不能直接解释成耗时或已显示帧数。
21View 动画 属性动画和窗口动画有什么区别?必会
传统 View Animation 主要改变绘制时的视觉变换,通常不改变 View 的布局位置;属性动画直接更新对象属性,例如 translationX、alpha 或 width,是否触发布局取决于属性实现。窗口动画处理的是整个 Window 或 Task 的系统转场,常围绕 SurfaceControl 对图层或 animation leash 做位置、缩放、透明度与裁剪变化,能覆盖跨进程应用窗口。给页面根 View 做动画可以实现页面内部效果,但控制其他应用的任务转场需要相应系统机制和权限。
属性动画改变 translationX 并不等同于改变 LayoutParams;修改 width 这类布局属性可能带来每帧重新布局。
22Shell Transition RemoteAnimation 和 animation leash 有什么用?高频
窗口转场需要把任务或窗口状态变化与动画一起协调。传统 RemoteAnimation 允许受授权组件接管一组远程动画目标;启用 Shell Transitions 的分支会把变化参与者收集到 transition 中,交给 WM Shell 的处理器组织开始、播放和结束。animation leash 是用于动画控制的临时父图层,把目标挂到 leash 下,就能统一修改目标的变换而保留其业务图层结构。动画完成或取消后,要归还控制权、恢复最终层级和状态,并调用完成回调,防止系统一直等待转场。
系统负责确定窗口和任务最终状态,动画控制器负责播放,SurfaceFlinger 负责合成呈现;不要把三者都称为 SurfaceFlinger 动画。
23出现黑屏 白屏或窗口看不见时应该如何定位?必会
我会先确认 Activity 是否成功启动,再沿窗口、图层、缓冲区和呈现逐层检查。看 dumpsys activity 确认任务、Display 和生命周期;看 dumpsys window 与 Winscope 确认 WindowState、可见性、bounds、Insets 和焦点;看 SurfaceFlinger 图层是否存在、是否被遮挡、alpha 或 crop 是否错误、是否收到有效缓冲区。若应用没有产出帧,再检查主线程、RenderThread、Surface 生命周期和 GPU;若帧已产出但没显示,则重点查图层变换、事务同步和显示合成。
白屏可能来自应用背景或 starting window,黑屏也可能来自无内容或合成状态,颜色本身不能证明责任层。
分屏多屏与应用适配
24分屏 Freeform 和画中画在系统层有什么区别?必会
它们都是多窗口能力,但任务组织和交互策略不同。分屏把任务安排在划分的显示区域,协调两侧 bounds、分隔条和转场;Freeform 允许任务以可移动、可调整尺寸的窗口存在,厂商小窗可能在此基础上扩展,也可能采用自定义模式;画中画用于视频等持续内容,窗口尺寸、交互和生命周期有专门策略。系统侧要同时协调任务的 windowing mode、窗口配置、图层位置和输入焦点,应用侧要能处理窗口尺寸变化。
厂商小窗不等于标准 PiP,面试时要先明确具体实现和 Android 分支。
25如果让两个应用进入分屏 系统侧需要协调哪些内容?必会
系统侧首先选择参与分屏的 Task,并依据设备支持和应用兼容策略建立分屏容器关系。在采用 WM Shell 的实现中,SplitScreenController 和 TaskOrganizer 等参与任务组织,WindowContainerTransaction 可以批量提交容器层级、bounds 和模式变化。WMS 与 ATMS 更新窗口和 Activity 配置,应用执行必要的重建或重新布局;Shell 再协调 SurfaceControl 事务和转场。还需要处理分隔条拖动、输入焦点、IME、最小尺寸和退出分屏。
TaskOrganizer、WindowContainerTransaction 属于系统级机制,普通应用不能假定可以任意重组其他应用任务;旧分支会使用不同类名。
26多窗口中是否只有一个 Activity 处于 RESUMED 状态?必会
Android 10 及之后支持 multi-resume,多个可见且可聚焦的顶部 Activity 可以同时处于 RESUMED,因此不能用 onResume 唯一判断谁正在接收用户操作。可见性、生命周期、窗口焦点和 top-resumed 是不同维度。窗口焦点可以通过相应焦点回调判断;相机等独占资源需要结合 onTopResumedActivityChanged、资源可用性和异常回调处理。PiP 等不可聚焦情形可能进入 PAUSED,但仍然可见,业务不应无条件在 onPause 停止所有画面更新。
onTopResumedActivityChanged 也不是资源绝不被抢占的保证,必须处理设备断开和资源失效。
27VirtualDisplay 是什么 和录屏 镜像以及副屏 Launcher 有什么关系?必会
VirtualDisplay 是通过显示管理系统创建的逻辑显示,输出通常写入提供的 Surface,因此它可以用于录屏、投屏或承载独立界面。镜像显示复用已有显示内容,独立显示则拥有自己的窗口或任务内容;这些能力取决于创建参数、显示属性和权限。创建 VirtualDisplay 并不自动得到 Launcher,是否支持 Home、系统装饰和第三方任务需要系统侧配置。MediaProjection 可以把捕获内容输出到虚拟显示,但它和具备完整任务承载能力的系统副屏不能直接画等号。
普通应用创建的显示在 private、trusted、secure 等属性和可启动任务范围上受限制,不能默认可以承载任意第三方应用。
28如何把 Activity 启动到指定 Display?必会
可通过 ActivityOptions.setLaunchDisplayId 指定目标显示,再把 options 传给 startActivity。启动是否成功还取决于目标 Display 是否存在、调用者是否允许在该显示启动、显示是否私有或可信,以及启动模式是否复用现有任务或实例。系统端会结合调用上下文、任务历史和启动选项决定最终位置。我会检查任务实际归属的 displayId,并用 isActivityStartAllowedOnDisplay 等接口判断适用的启动权限,不能只看启动参数是否设置成功。
已有任务跨屏迁移和创建一个新 Activity 是两种不同操作;是否复用必须结合 launchMode 和 Intent flags。
29任务从一个屏幕迁移到另一个屏幕时需要处理什么?必会
迁移会改变任务所属的显示容器,可能同时改变窗口尺寸、密度、方向和 Insets,系统需要更新配置与窗口 Surface 状态。应用可能收到配置变化,也可能被销毁重建,所以必须恢复页面、滚动位置和业务状态,并在正确的 Display 或 Window Context 下获取资源和创建窗口。还要协调输入焦点、IME、Dialog、视频 Surface 和独占资源。迁移过程中我会让临时动画层与任务最终位置协同,处理显示拔除、进程死亡和转场取消,避免画面已迁移而业务状态仍指向旧屏。
跨屏后不一定保持同一个 Activity 实例;只处理 onConfigurationChanged 也不能覆盖所有重建和进程死亡。
30三指跨屏时 一个窗口飞出另一个窗口飞入 怎样控制时机?高频
一种系统侧实现是由统一控制器识别三指手势并确定源任务、源屏和目标屏,把任务迁移纳入同一转场。控制器先准备源画面的动画 leash,必要时用允许捕获的快照维持连续视觉,同时准备目标显示配置和任务内容。随后按照同一动画进度更新两端位置、裁剪和透明度,在适当的内容就绪点交接真实窗口,并在完成或取消时恢复最终层级与焦点。关键是使用目标窗口就绪和系统同步机制,不能靠固定延时猜测目标已经画好。
SurfaceControl 事务可以一起提交相关状态,但两块物理屏的刷新周期和硬件呈现未必完全同步,仍需测量实际显示差异。此回答是实现方案,不自动代表个人项目已使用全部机制。
31折叠屏适配应该关注哪些变化?必会
我会把折叠屏适配分为窗口可用尺寸、折叠姿态和状态连续性。布局依据当前窗口空间做单栏、双栏等调整,使用 WindowMetrics、资源限定符和约束布局,避免硬编码整块屏幕宽高;借助 Jetpack WindowManager 的 FoldingFeature 等信息识别折痕、遮挡和分离区域,把按钮等关键内容放在合适的位置。折叠展开、旋转和多窗口都可能引起配置变化或重建,因此数据状态和 UI 状态需要可恢复。还要验证键盘、Insets、横竖屏和视频 Surface。
折叠屏不一定是两个 Display,也不是把原界面按屏幕尺寸等比拉伸即可。
32第三方应用不能修改源码时 怎么做布局和折叠屏适配?高频
我会先复现并区分是应用固定尺寸、方向限制、资源选择、窗口 Insets,还是系统兼容策略导致的问题。可通过 manifest 分析、布局检查、日志和窗口 Trace 确定触发条件,再评估系统侧兼容配置,例如方向和宽高比策略、letterbox、尺寸兼容和厂商白名单。系统侧方案应尽量缩小到明确的包和版本范围,并验证升级后的行为;能与应用方协作时,再推动应用自身改为自适应布局。系统策略无法保证修复应用内部所有硬编码和业务假设。
是否忽略 resizeableActivity、方向和宽高比限制受 Android 版本、targetSdk、设备和厂商实现影响,不应按旧规则一概而论。
33多屏触摸 返回键和输入法怎样找到正确的窗口?必会
触摸首先需要正确的输入设备与 Display 关联,InputReader 处理坐标映射,InputDispatcher 根据显示、窗口命中区域、层级和可接收输入状态选择目标窗口。键盘和返回操作更依赖焦点及系统导航策略,不能简单认为总是送给主屏或最后一个 onResume 的 Activity。IME 还要结合输入目标窗口与该显示的输入法策略。我会联合检查 dumpsys input、window 和 display,确认设备映射、focused window、touchable region、坐标变换和 IME target。
BACK 可能进入系统导航和预测性返回流程,HOME 也可能由系统策略处理,不能把它们都视为普通 View 的 onKeyDown。
Binder 安全与并发
34Binder 的完整调用过程是什么?必会
客户端拿到目标服务的 Binder 引用,远程情况下使用 Proxy 把接口参数写入 Parcel,再通过 transact 进入 Binder 驱动。驱动依据目标 Binder 节点和句柄把事务送到服务进程,服务端 Binder 线程读取事务,由 Stub.onTransact 解码参数并调用具体实现。同步调用在服务端处理后返回 reply,客户端读取结果;oneway 调用不等待业务结果。Binder 同时支持对象引用传递和调用者身份信息,因此系统服务可以校验实际 calling UID。ServiceManager 主要参与服务发现,不会转发每一次业务调用。
Binder 既有 Java API,也有 Native 库和内核驱动,AIDL 生成代码只是接口封装的一部分。
35为什么经常说 Binder 只需要一次数据拷贝?高频
典型 Binder 事务中,驱动把发送端用户空间的事务数据拷入接收端映射的 Binder 缓冲区,因此常被概括为一次驱动数据拷贝,相比常规经过内核中转再复制到接收端的路径,减少了一次复制。但这不代表整次调用完全没有其他复制,参数编解码、Parcel 构造、对象分配和业务处理仍可能增加开销。它也不意味着大图片适合直接塞进 Parcel,大数据更适合共享内存、文件描述符或流式接口。
这个说法描述典型传输路径,不能当作所有 Binder 使用方式的严格总拷贝次数。
36AIDL 是怎么工作的 方法一定运行在主线程吗?必会
AIDL 根据接口定义生成 Stub、Proxy 和编解码代码,客户端与服务端依照相同契约跨进程调用。远程调用通常由服务进程的 Binder 线程执行,Service.onCreate 等组件生命周期在主线程执行,并不意味着 AIDL 方法也在主线程。同进程情况下,asInterface 可能直接返回本地实现,方法就在调用者线程执行。服务端如果要更新 View,应显式切换到 UI 线程;如果要执行耗时工作,应使用合适的工作线程并设计异步结果。
远程回调接口也可能在客户端 Binder 线程执行,需要考虑线程安全和回调顺序。
37同步 Binder 和 oneway 的区别是什么?必会
普通 AIDL 方法即使返回 void,也默认是同步调用,调用线程要等待服务端事务完成。oneway 不等待远程业务完成或返回值,因此适合某些通知型接口,但服务端仍需要执行工作,调用仍有封装和入队等成本。针对同一个 Binder 节点的异步事务有串行化语义,不能把 oneway 理解为每个请求自动并行。客户端不能依靠 oneway 获取业务成功结果,必要时要设计回调、确认、超时和状态查询。
本地直接调用不会因为 AIDL 标记了 oneway 就自动变成后台异步调用。
38Binder 为什么会导致主线程卡顿或 ANR?必会
主线程执行同步 Binder 时,需要等待远端处理;远端可能卡在 I/O、锁、另一个 Binder 调用,或者没有空闲 Binder 线程。于是客户端即使自身代码很短,也可能长时间阻塞。我会从 Perfetto 关联客户端 transact 与服务端执行,检查调度等待、锁竞争、线程池占用和耗时子调用,再决定减少请求次数、缓存、批量查询、异步化或移出主线程。简单把所有接口改成 oneway 不能解决服务端拥塞和业务结果依赖。
如果大量请求在服务端争夺同一把锁,增加 Binder 线程也可能只让更多线程等待。
39跨进程调用为什么容易死锁 怎样避免?高频
典型情况是进程 A 持锁调用 B,B 又回调 A,而回调需要同一把锁或另一条形成环路的依赖。Binder 还存在嵌套调用和重入,不能因为只有一个 Binder 线程就忽略状态被重入修改。我会避免持业务锁发起外部 Binder 调用或回调,锁内只读取或更新最小状态,锁外执行远程操作,必要时使用快照和版本校验保证一致性。定位时要同时取各进程线程栈,画出锁持有和等待关系。
改用 oneway 可以改变等待方式,但不能消除所有锁、队列和状态一致性问题。
40如何处理服务端进程死亡和 Binder 断开?高频
远端调用可能抛出 RemoteException,已有 Binder 可以通过 linkToDeath 注册 DeathRecipient,绑定服务还需要处理 ServiceConnection 的断开和绑定失效等回调。发现远端死亡后,我会使旧代理失效、清理旧回调和本地状态,再按受控策略重新连接,并重新注册监听。对于具有副作用的请求,重试前要区分是否已经执行,使用请求 ID 或幂等语义,避免重复操作。恢复过程中还要检查目标用户和服务可用性,防止无限重连。
isBinderAlive 只反映一次检查时的状态,检查通过后远端仍可能立即死亡。
41Binder 能否传递大数据 Parcelable 和 Serializable 有什么区别?高频
Binder 事务缓冲区有限,而且进程内多个在途事务会共享相关额度,不能把常见的约 1MiB 当作某个接口每次都能安全发送的大小。大数组、图片或大量列表可能触发 TransactionTooLargeException,宜改为分页、文件描述符、共享内存或只传标识。Parcelable 显式控制字段读写,更适合 Android IPC;Serializable 使用通用 Java 序列化机制,通常有更多运行时开销和对象处理成本。两者都要考虑字段兼容,Parcelable 的读写顺序和类型必须匹配。
Intent extras 和保存的页面状态也可能进入 Binder 传输链路,不能只检查自写 AIDL。
42Binder.getCallingUid 和 clearCallingIdentity 应该怎么用?必会
服务端应基于 Binder.getCallingUid 等真实调用身份校验权限和用户,不能只相信客户端传来的包名或 userId。权限检查通过后,若需要以系统服务自身身份执行内部操作,可以保存 clearCallingIdentity 返回的 token,并在 finally 中 restoreCallingIdentity。清除身份之前完成调用者检查,否则可能把无权限调用升级成系统身份。异步转到工作线程时,原始 Binder 调用上下文不会自然保留,我会显式保存需要的 UID 和用户信息。
getCallingUid 不能在任意后台线程里直接当作最初请求者身份;传入的包名也要与 UID 关系匹配。
43普通权限 运行时权限 签名权限和特权权限有什么区别?必会
普通权限通常在安装阶段按规则授予;危险权限对适用版本和 targetSdk 的应用需要运行时授权;签名权限依据签名及平台支持的相关规则决定是否授予。privileged 标志允许符合条件的特权应用走相应授权路径,但需要放在正确的特权应用位置并满足同分区 allowlist 等要求。系统目录中的 APK 并不自动拥有所有系统权限。接口访问还可能叠加 AppOps、用户范围和 SELinux 检查,所以我会分别查权限授予与具体操作的限制。
priv-app allowlist 不能让一个只有纯 signature 保护级别的权限绕过签名要求。
44有权限但操作仍失败 是否可能和 SELinux 有关?必会
可能。Android 权限主要由 Framework 等层根据应用身份校验,Linux DAC 根据 UID、GID 和文件权限控制访问,而 SELinux 根据进程 domain、对象 type、对象类别和操作实施强制访问控制,几层都通过才可能成功。定位时看 avc: denied 中的 scontext、tcontext、tclass 和被拒绝的权限,先确认进程和文件或服务标签是否正确,再判断是否确实需要策略规则。正式修复应使用符合最小职责的策略并检查 neverallow,不能把关闭 SELinux 当作最终方案。
avc 中 permissive=1 的记录只是审计,不一定是当次功能失败的实际拦截原因,必须结合日志和时间线。
45synchronized 可以用在哪里 实例锁和类锁怎么区分?高频
synchronized 可以修饰实例方法、静态方法,也可以包围代码块。实例方法锁住当前 this,静态方法锁住对应 Class 对象,代码块锁住括号中指定的对象。只有竞争同一个监视器的代码才相互互斥,所以两个不同实例的 synchronized 方法通常不会互相挡住;实例锁和类锁也不是同一把锁。它同时提供临界区互斥和相应的内存可见性。我会明确共享数据归属,再选固定锁对象和较小的临界区。
不要用不断变化的锁引用,也不要拿字符串常量等可能被外部共享的对象随意当锁。
46volatile AtomicInteger 和 synchronized 分别适合什么场景?高频
volatile 适合状态发布和可见性需求,例如工作线程读取退出标记,但不能让 count++ 这种读改写组合自动变成原子操作。AtomicInteger 通过原子操作支持单变量计数和状态更新,适合明确的原子读改写;如果业务要求同时修改多个字段并维护不变量,需要 synchronized 或其他整体同步策略。选择时我先看需要保证的是可见性、单操作原子性还是多步骤的一致性,避免因为用了一个 volatile 就认为对象整体线程安全。
给引用加 volatile 也不会自动让其指向的集合或对象内部字段全部线程安全。
47CPU 和 IO 线程池如何配置 拒绝策略如何选择?高频
CPU 密集任务的并行度通常从可用核心数附近开始测量,避免过多线程争抢;IO 密集任务可适度增加并发,但要结合设备、下游能力和内存限制,并使用有界队列。拒绝策略取决于业务:必须保留的任务要显式失败或进入可靠队列;可丢的预加载或过期刷新可以采用受控丢弃;CallerRuns 会让提交线程执行,能形成背压,但如果提交者是 UI 或 Binder 线程就可能造成卡顿。AbortPolicy 会抛出 RejectedExecutionException,需要调用方处理。
读取文件也会占磁盘、内存和线程,并不是纯读取就可以无限并发;DiscardOldestPolicy 也不保证丢掉的任务在业务上最不重要。
输入 Handler 与 View
48一次触摸从硬件到 View 经过哪些环节?必会
触摸硬件通过驱动产生输入事件,EventHub 读取 Linux 输入设备事件,InputReader 解析设备数据、坐标和 Display 映射,InputDispatcher 再依据窗口状态选择目标,并通过 InputChannel 等通道交给应用。应用的 ViewRootImpl 输入管线处理事件,进入窗口与 Activity 的分发,再由 DecorView 和 ViewGroup 分发到具体 View。完整链路还包含系统策略处理、输入法和事件完成反馈。因此触摸无响应时,要分别判断硬件事件有没有产生、系统选中了谁、应用是否及时消费,而不是只检查 onTouchEvent。
InputChannel 不等于每个 MotionEvent 都要经一次普通 Java AIDL 调用;输入传输有专门通道。
49dispatchTouchEvent onInterceptTouchEvent 和 onTouchEvent 是什么关系?必会
dispatchTouchEvent 是分发入口;ViewGroup 的 onInterceptTouchEvent 决定是否拦截并由父容器处理;onTouchEvent 负责当前 View 的实际消费。一组手势通常由 DOWN 建立触摸目标,后续 MOVE 和 UP 继续交给该目标。父容器可以在中途决定拦截,此时子 View 会收到 CANCEL,父容器开始处理后续事件。解决滑动冲突时,我会根据方向和距离确定拦截条件,必要时由子 View 请求 requestDisallowInterceptTouchEvent,保证整组手势有一致归属。
子 View 不消费 DOWN 时,通常不会成为后续事件的触摸目标;多指拆分等情况需要结合 ViewGroup 的具体配置。
50OnTouchListener onTouchEvent 和 onClick 有什么关系?必会
在 View 分发触摸时,满足条件的 OnTouchListener 通常先于 onTouchEvent 被调用。如果 OnTouchListener 返回 true,表示它消费了事件,默认 onTouchEvent 就可能不再处理,进而影响按压状态和点击。OnClickListener 不是独立的底层触摸入口,普通点击通常由 View 的触摸逻辑判断有效点击后调用 performClick 触发。自定义触摸处理中如果自己识别点击,应调用 performClick 以兼顾点击语义和无障碍,而不是直接执行某段点击业务。
点击还可以由键盘、无障碍等途径触发,所以 onClick 不只来自触摸 UP。
51Handler Looper MessageQueue 分别负责什么?必会
Handler 负责把 Message 或 Runnable 提交到指定线程,并定义处理方式;MessageQueue 按执行时间等规则管理待处理消息;Looper 在所属线程不断取出可执行消息,再交给对应 Handler 分发。线程调用 Looper.prepare 创建相关环境,loop 开始循环。主线程已经由系统建立 Looper,后台线程可以使用 HandlerThread。Handler 不是执行消息的新线程,new Handler(Looper.getMainLooper()) 绑定的是现有主线程,post 的 Runnable 也在主线程运行。
延迟消息是在提交时进入队列,时间到了且线程有机会执行时才被取出,不是到期后才首次进入队列。
52主线程 Looper 一直循环 为什么不会一直占用 CPU?高频
Looper 在没有可执行工作时会进入 nativePollOnce 等等待路径,底层通过 poll 或 epoll 等机制等待事件或超时,线程处于阻塞而非持续忙轮询。新消息、到期消息或输入等事件会让线程继续处理。消息循环持续存在并不等于 CPU 持续繁忙,真正影响响应的是某个任务执行过久、队列积压或唤醒异常。排查时我会结合线程状态,区分 Running、可运行但未被调度和阻塞等待。
主线程栈停在 nativePollOnce 常常表示当下空闲,单凭这一帧栈不能认定它就是 ANR 根因。
53IdleHandler 是把任务延迟进入消息队列吗?高频
不是。IdleHandler 注册的是队列空闲时的回调,当没有立即可执行消息、或者下一条普通消息还未到执行时间等适用条件出现时,MessageQueue 会调用 queueIdle。它运行在 Looper 所属线程,返回 true 表示保留,返回 false 表示移除。它适合短小、可延后的工作,但不保证在某个固定时刻执行,也不适合持续重 I/O 或长计算。我会把真正耗时的初始化交给后台线程,IdleHandler 只做轻量触发。
主线程忙时,IdleHandler 可能很晚才执行;它也不是自动产生后台并发的工具。
54同步屏障和异步消息有什么用?高频
同步屏障能让适用队列中的同步消息暂时等待,同时允许后面的异步消息越过屏障。在常见 ViewRootImpl 实现中,scheduleTraversals 会插入屏障并通过 Choreographer 安排 traversal,使帧调度相关异步消息能得到处理,随后移除屏障。它用于协调绘制调度,并不改变主线程一次只执行一个任务的事实,也不能打断当前正在运行的长任务。异步消息仍然在对应 Looper 线程执行,不等于线程池任务。
具体 MessageQueue 数据结构和锁实现可能随 Android 分支变化,应抓住屏障的调度语义,不死记所有版本都相同的内部实现。
55如何设计一个合格的自定义 View?高频
我会先明确控件的尺寸规则、绘制内容和交互方式,通过属性解析支持 XML 配置,onMeasure 把内容尺寸与父容器约束合并,onDraw 根据 padding 和实际尺寸绘制。绘制对象和可缓存几何尽量预先创建,避免每帧分配大量对象。触摸逻辑正确处理 DOWN、MOVE、UP 和 CANCEL,点击走 performClick,并补充无障碍、状态保存和不同密度适配。若需要安排子 View,就使用自定义 ViewGroup 并实现子节点测量和 onLayout。
尺寸相关计算通常放在 onSizeChanged 等适合的阶段;不能假设构造方法中已拿到最终宽高。
56SurfaceView TextureView 和普通 View 的区别是什么?高频
普通 View 通常绘入所在窗口的内容缓冲区。SurfaceView 管理专用 Surface,内容可以由独立渲染者生成,系统再与界面合成,适合视频和持续渲染等场景;TextureView 通过 SurfaceTexture 获取内容,并参与普通 View 树的硬件加速合成,因此易于做常见 View 变换,但可能增加合成成本。SurfaceView 的缩放、裁剪、层级和同步能力随系统版本演进,不能一概说它不能做动画。选择时要结合渲染来源、性能、变换需求和安全内容要求。
使用 SurfaceView 不代表框架自动替你创建渲染线程,渲染线程仍由业务或播放器决定。
57动画卡顿时 怎样减少 View 层的开销?高频
我会先确认耗时发生在动画计算、布局绘制还是 GPU。只做移动、缩放和透明度变化时,尽量使用合适的属性和已有绘制内容,避免每帧改 LayoutParams 导致反复 measure 和 layout。自定义绘制中缓存不随帧变化的对象和计算,减少无效 invalidate、重度透明叠加和过度绘制。需要硬件 layer 时要测量其内存与合成成本,并在动画结束后恢复;同时检查图片大小和每帧分配引起的 GC。
动画是否达到目标帧率应看实际帧期限和呈现结果,不能只观察 ValueAnimator 回调是否持续执行。
58setSelected setMargins 和 findViewById 必须在主线程吗?高频
对于已经挂载到 UI 的 View,我会在其所属 UI 线程修改状态和布局。setSelected 可能影响 drawable 状态与界面刷新,修改 margins 并设置 LayoutParams 会影响布局,因此应放在 UI 线程。findViewById 是查找操作,本身不直接触发绘制,但读取正在变化的 View 树也不应随意跨线程。后台线程适合准备数据和纯计算,再把结果交回 UI 线程。未挂载对象的构建有特殊条件,不能把它推广为已经显示的 View 可以任意跨线程操作。
没有抛 CalledFromWrongThreadException 不等于这次跨线程访问就有线程安全保证。
包 资源 电源 网络和显示管理
59一个 APK 从安装请求到完成安装经过什么流程?必会
安装通常从 PackageInstaller 的 session 开始,安装数据写入并提交后,包管理路径解析 APK,检查包名、版本、签名、SDK、ABI 和依赖等条件。系统准备安装上下文,扫描包信息,将新包与已有系统状态进行 reconcile,最后 commit 更新包设置与组件等状态。文件和应用数据目录等操作会与 installd 协作,编译优化的安排取决于系统版本及 ART 服务实现。安装完成后更新用户可见状态并发出对应结果和通知。
安装流程会涉及校验和事务一致性,不能把它概括为复制 APK 再写一条数据库记录。
60应用升级为什么通常能保留数据 什么情况下会失败?必会
升级会替换或更新代码包,并在满足身份与兼容规则时沿用原有应用数据。关键条件包括包名一致、签名匹配或符合系统认可的签名轮换规则,以及版本和包结构满足安装策略;随意更换签名通常无法直接覆盖安装。即使系统保留了数据,应用也必须正确迁移自己的数据库和持久化格式,否则仍可能启动失败。卸载、重新安装、回滚和多用户场景要分别分析,不能默认行为相同。
版本降级是否允许受调试状态、安装标志和平台策略等限制;系统应用更新还涉及只读系统分区与数据分区中的更新包。
61安装失败时 如何判断是签名 版本 ABI 还是包结构问题?高频
我会先取得完整安装结果码和 PackageManager、PackageInstaller 日志,保留失败现场,再按实际错误检查。签名类错误核对证书和当前已安装包;降级错误核对 versionCode 和安装策略;ABI 错误检查设备支持的 ABI 与 APK 的 native 库;拆分包问题核对 base 和 splits 是否完整且一致。还要检查存储空间、SDK 限制、共享用户和 required library 等条件。针对多用户,要确认包的全局安装状态与目标用户状态,避免只看 Launcher 图标。
可使用 dumpsys package、pm path、设备 ABI 属性和 APK 分析工具获得证据,不要一上来清除用户数据掩盖原因。
62Android 资源是怎样选中的 RRO 能改变什么?高频
应用通过 Resources 和 AssetManager 等层访问编译后的资源,系统根据当前 Configuration 选择语言、密度、尺寸、方向等限定符最匹配的候选。屏幕或窗口配置改变时,资源选择也可能变化。Runtime Resource Overlay 允许通过 overlay 包覆盖目标包的适用资源,映射和启用受 overlayable、签名、分区和权限等规则约束。RRO 适合更换颜色、尺寸、字符串及其他被允许覆盖的资源;修改 Java 控制流程或任意第三方资源不能靠普通 RRO 自动实现。
资源尺寸问题要确认拿到的是正确窗口或 Display 的 Context,不能用一个全局 Context 解释所有屏幕配置。
63PowerManagerService 主要管理什么 WakeLock 会做什么?高频
PowerManagerService 协调系统唤醒状态、用户活动和 WakeLock 等电源相关策略,并与显示电源管理及底层能力交互。WakeLock 表达某种保持唤醒需求,例如 PARTIAL_WAKE_LOCK 主要用于保持 CPU 执行能力,不能简单等同于屏幕常亮。使用时要明确持有范围、设置合理超时,并在工作完成或取消时释放。屏幕常亮需求通常使用适合的窗口或 View 策略,同时遵守产品的电源设计,避免让后台任务长期阻止休眠。
显示亮灭还需要 DisplayPowerController 等路径协调,不能认为 PowerManagerService 直接负责每一帧显示。
64设备异常耗电 应该怎样定位?高频
我会先统一测试条件,包括亮度、网络、温度、前后台状态和运行时长,再确认耗电集中在 CPU、显示、网络还是唤醒。通过 batterystats、Battery Historian 或可用的系统电源 Trace 查看 WakeLock、唤醒、任务和网络活动,再结合 Perfetto 查长时间运行和频繁唤醒的线程。修复可能包括合并周期任务、减少轮询、限制后台并发、正确释放 WakeLock 和停止不可见内容的无效刷新,最后在相同条件下复测功耗和功能。
CPU 利用率不高也可能因持续唤醒、屏幕或网络状态而耗电,不能只看一个百分比。
65网络请求在应用侧和系统侧分别经过哪些环节?高频
应用网络库建立请求,进行 DNS、连接、TLS 和协议通信,实际数据走 socket 与内核网络栈。ConnectivityService 维护默认网络、网络能力和选择策略,netd 等组件参与路由、DNS 和网络规则等管理,但不是每个 HTTP 数据包都通过 Java 系统服务转发。请求失败时,我会分开检查默认网络与验证状态、DNS、路由、TLS 证书、代理、超时和服务端响应。设备连着 Wi-Fi 只代表建立了链路,不保证互联网可用。
对热点、VPN、多网络并发等场景还要确认请求使用的是哪个 Network,以及该网络是否能到达目标地址。
66网络切换后请求失败或服务收不到状态 怎样处理?扩展
我会使用 ConnectivityManager 的 NetworkCallback 观察网络可用性、能力和丢失事件,把状态变化纳入连接管理。Wi-Fi 和蜂窝切换时,原有连接可能失效,需要取消或重建相关请求,并设置有限重试、退避和超时;涉及重复写入的请求必须有幂等设计。服务侧不能只缓存一次开机时的网络状态,还要处理回调注销、进程恢复和权限限制。定位时对照回调、路由和请求时间线,确定是通知缺失还是连接本身不能继续使用。
网络回调的到达不意味着所有远端服务立即可访问,业务仍需处理具体请求失败。
67DMS 的逻辑 Display 物理显示设备和 SurfaceFlinger 显示有什么关系?高频
物理显示设备对应实际输出能力,DMS 通过显示适配器等路径管理设备信息,再向上提供逻辑 Display、模式和配置。逻辑 Display 是窗口和任务放置时使用的系统显示抽象,虚拟显示也可以进入这一体系。WMS 基于 Display 的尺寸、密度和状态组织窗口;SurfaceFlinger 处理具体显示合成与输出。它们协作处理显示新增、移除、模式切换和电源状态,不能假定一个 displayId 永远等于某个物理连接器编号。
多屏定位时同时记录逻辑 displayId、物理显示标识、显示端口和输入关联,避免只用一个数字串联所有层。
性能优化与故障定位
68启动慢时 如何找出真正的关键路径?必会
我会先定义测量起止点,区分点击响应、应用初始显示和主要内容可用,再在统一设备状态下多次采样。Perfetto 中沿输入、ATMS、进程创建、bindApplication、Provider、Application、Activity、View traversal、RenderThread 和呈现阶段串联关键路径,判断时间是在执行、等锁、等 Binder、等 I/O 还是等调度。优化围绕首帧必要依赖,把可延后工作移出关键路径,而不是把所有任务同时丢到线程池。之后用相同口径复测分布并检查功能与资源代价。
TTID 和 TTFD 有明确统计语义,产品自己的点击响应与完成指标不能未经核对就直接改名。
69你做过什么启动优化 如何证明效果?必会
我做过座舱应用冷启动专项,通过 Perfetto 把启动拆成初始化、主线程执行、布局绘制和首帧相关阶段,找出重任务和不必要的初始化依赖。具体采用过 AsyncLayoutInflater、后台执行重任务、ViewStub 按需加载、IdleHandler 调度可延后工作,并调整初始化顺序和强依赖。多次实车复测后,冷启动响应延时从 380ms 降到 240ms,约下降 37%;完成延时从 1200ms 降到 700ms,约下降 42%。面试时我会先解释项目中这两个指标的起止定义,再展示对应 Trace 和复测条件。
不要把已有项目指标直接宣称为 Android TTID 或 TTFD;如果追问单项收益,应依据保留的实验记录回答,不能把总收益分摊成虚构数据。
70Perfetto 里怎样判断是 App 卡顿 还是 SurfaceFlinger 卡顿?必会
我会先从 FrameTimeline 找到未满足预期时序的帧,再把应用 Surface 帧与 Display 帧关联。应用侧检查主线程是否及时进入 doFrame、measure 和 draw 是否过长、RenderThread 和 GPU 是否按时完成,以及是否存在锁或 Binder 等待;系统侧检查 SurfaceFlinger 合成、事务与缓冲区等待、HWC 和呈现时序。如果应用按时产出内容而 Display 帧仍然延后,就进一步检查合成和显示路径;若应用自身晚提交,就回到应用关键路径。
判断 Running 和 Runnable 可以区分自身计算过重与未及时获得 CPU;不同帧类型和轨道必须按 Trace 的实际定义解读。
71ANR 和 system_server 卡死怎么排查?必会
ANR 要先确定类型和触发时间,包括输入分发、组件执行以及前台服务等场景,再结合 ANR reason、线程栈、日志和 Perfetto 找等待链。主线程可能在执行长计算,也可能在等 I/O、锁或同步 Binder,远端拥塞也会传导到应用。system_server 卡死还要检查关键线程、全局锁和服务间调用,Watchdog 日志能提示被监控线程或锁的异常。单次线程栈只能反映采样时刻,我会把锁持有者、Binder 服务端和调度一起关联。
常见的输入超时约 5 秒不是所有 ANR 的统一阈值;不同类型、平台版本和状态有不同规则。
72内存泄漏 GC 和低内存杀进程怎样区分?高频
内存泄漏是本该释放的对象仍被引用,常见原因包括静态持有 Activity、未注销监听和延迟任务引用。GC 是运行时回收可达性已消失对象的过程,频繁分配也可能触发更多 GC 和部分暂停,但不是每次 GC 都表示泄漏。lmkd 等系统机制则可能在内存压力下选择终止进程。我会分别分析 heap dump 的引用路径、分配与 GC 时间线、dumpsys meminfo 的 Java 与 Native 内存,以及系统低内存相关记录,避免只看 Java heap 就忽略图形或 Native 占用。
Handler 泄漏要追到仍排队的消息或持久对象如何持有 Activity,不能简单说所有非静态 Handler 必然泄漏。
73第三方 ContentProvider 拖慢启动 可以直接删除并延迟初始化吗?高频
不能在不了解依赖的情况下直接删。Provider 可能承担 SDK 自动初始化、组件注册或提供跨进程数据,我会先检查合并后的 manifest、Provider 的用途以及 SDK 是否提供正式的手动初始化入口。若支持手动初始化,可以用 manifest merger 配置移除对应自动初始化,再在业务需要且线程条件满足时调用明确的 API,并处理多进程、多用户和重复初始化。若没有公开替代入口,就需要分析实现或和供应方确认,不能凭空假设有 SDK.init。
AsyncLayoutInflater 的回退条件和控件线程要求也要验证,后台 inflate 完成后仍需在 UI 线程挂载,避免首屏分块出现。
74窗口转场闪烁 错位或动画结束不恢复 怎样排查?高频
我会同时收集 WM、SurfaceFlinger 和 Shell Transition Trace,在 Winscope 按转场 ID 对齐系统容器状态与实际图层状态。先核对参与目标和最终 bounds,再看 leash 的父子关系、matrix、crop、alpha 与 visibility,检查开始和结束事务是否正确应用,目标内容是否及时就绪,以及完成或取消回调是否执行。若只在跨屏或旋转中出现,还要核对 Display 配置和坐标转换。Perfetto 用来补充各线程执行和帧期限,最终定位到具体错误事务或状态交接。
动画视觉结束与系统转场 finish 是两个环节,遗漏 finish 可能让后续转场或状态更新受到影响。
75Java 崩溃和 Native 崩溃应该怎样处理?扩展
Java 崩溃先看异常类型、最早有效调用栈和前置日志,确认线程、生命周期和对象状态。Native 崩溃则查看 tombstone 的 signal、fault address、崩溃线程和 backtrace,结合匹配构建的符号进行符号化,并检查内存越界、释放后使用、空指针和并发问题。跨 JNI 边界还要注意局部与全局引用、线程 attach、未清除异常和资源释放。若问题涉及系统服务或图形进程,我会保留 bugreport、版本信息和复现步骤,再联合对应进程的 Trace 分析。
符号必须匹配同一构建和架构,不能用其他版本的 so 得出看似完整但错误的调用栈。
项目追问 技术攻关和工程交付
76你的经历如何对应这个 Framework 岗位?必会
我的主要经历是 HarmonyOS 智能座舱 Android 原生应用开发,并承担多屏交互模块设计负责人职责,覆盖多屏协同、跨屏辅助、显示适配和系统联调。我做过框架动画相关开发与性能分析,其中冷启动专项用 Perfetto 定位并改善关键路径。这些经历与岗位里的窗口、多屏、动画和重难点定位有直接关联。对于 AMS、PMS 等核心服务,我会明确区分自己实际修改过的路径、联调中分析过的机制和正在补充的知识,并用一个具体问题说明自己能如何沿应用到系统逐层定位。
岗位明确要求 Framework 研发经验。若追问实际提交过哪些系统改动,要说具体文件、职责和验证,不能把上层应用经验直接换算成核心服务维护年限。
77怎样把多屏项目讲出技术细节 而不只描述功能?必会
我会先说明真实的业务场景和自己负责的部分,再解释一个操作如何贯穿手势入口、跨模块状态协调、任务或页面的显示位置变化、动画执行,以及焦点和生命周期恢复。接口设计要说明传递什么标识、哪个模块拥有最终状态、失败怎样通知和回滚;多屏适配要说明如何区分 Display 和用户空间,何时处理配置变化。最后用实际遇到的一类问题说明如何定位并回归,例如画面位置、输入归属或启动时序。具体用了哪种任务接口、动画控制器或 IPC,以自己的真实实现为准。
这是一种面试表达结构。没有做过的 Shell Transition、leash 或快照方案,应作为可讨论方案解释,不能写进个人已完成经历。
78跨模块问题 各方都认为不是自己导致的 怎么推进?高频
我会先统一复现步骤、设备版本和预期行为,收集同一次操作的日志、窗口状态与 Trace,再把问题拆成可以验证的接口交接点。例如确认应用是否发出请求、系统是否选对任务和 Display、窗口是否产出有效缓冲区、输入是否路由到目标。每个交接点都用可观察的状态和时间戳验证,先找到最早偏离预期的位置,再和对应模块确定修复和回归范围。对已有线上影响,会同时评估最小可控的缓解措施,但保留根因分析和后续修复。
同一条 log 显示请求成功,并不能证明所有下游操作都已成功;日志、状态和 Trace 要相互验证。
79开发一个 Framework 特性 技术文档应该写什么?高频
文档首先说明需求触发场景、目标行为和验收标准,再描述当前链路、职责分配和具体改动位置。接口部分写清参数、调用权限、线程、同步或异步语义、失败与取消处理;状态部分覆盖正常流、配置变化、进程死亡、用户切换和设备移除。还应记录 Android 分支、兼容策略、性能约束、调试入口以及回滚方法。评审时让不同模块能够确认各自负责的输入、输出和最终状态,测试时可以据此逐项验证。
复杂跨进程流程适合用时序图,状态迁移适合用状态图;不要只贴源码调用栈而不解释交接条件。
80修改系统窗口或任务代码后 怎样保证不影响其他场景?必会
我会先列出受影响的任务和窗口状态,针对启动、返回、旋转、分屏进入退出、小窗、跨屏、IME、锁屏、多用户与显示断开建立回归矩阵。核心逻辑加适合的服务端测试,涉及权限和窗口管理行为时执行相关 CTS 或现有平台测试,再在目标设备验证动画、输入和性能。新增兼容策略尽量限定到必要条件,避免全局改动扩大影响;同时保留可观察的日志和配置开关,方便定位和回滚。对重复切换和异常中断进行压力验证,确认没有残留 leash、资源和状态。
单次功能演示成功只能证明一条路径,系统特性还需要验证退出、失败、重建和多参与者场景。
技术资料
用于核对系统机制与版本差异。阅读源码时,请切换到项目实际使用的分支。
- 01系统服务启动 SystemServer
- 02Zygote 与 USAP
- 03任务和返回栈
- 04SurfaceFlinger 与 WindowManager
- 05BufferQueue 与 Gralloc
- 06Shell Transitions 与 Winscope
- 07多显示设备概览
- 08指定显示的 Activity 启动策略
- 09Multi Resume
- 10多显示输入路由
- 11多窗口与版本差异
- 12Binder 线程与同步异步事务
- 13AIDL 概览
- 14Binder 事务缓冲区限制
- 15特权权限 allowlist
- 16Android SELinux
- 17输入链路
- 18Looper
- 19IdleHandler
- 20自定义 View 绘制与测量
- 21配置变化
- 22APK 安装核心路径
- 23Runtime Resource Overlay
- 24WakeLock
- 25ConnectivityManager
- 26启动时间与 TTID TTFD
- 27Perfetto FrameTimeline
- 28ANR
- 29Native 崩溃定位