云计算百科
云计算领域专业知识百科平台

Android 常考面试题详解:从四大组件到性能优化,一篇吃透

在这里插入图片描述

一、前言:Android 面试考什么?

Android 面试的经典结构是:Java/Kotlin 基础 → Android 四大组件 → 消息机制 → View 体系 → 性能优化 → 项目深挖。其中 Android 特有的部分集中在中间三块,也是本文重点。

在这里插入图片描述

以下 25 道高频题按模块整理,每题给出标准答案 + 追问点,背熟能覆盖 80% 的一二面。


二、四大组件

Q1:Activity 的生命周期?异常情况呢?

标准答案:

正常启动:onCreate → onStart → onResume
退到后台:onPause → onStop
回到前台:onRestart → onStart → onResume
销毁: onPause → onStop → onDestroy

异常生命周期(横竖屏切换):
Activity 被销毁重建,走 onPause → onStop → onSaveInstanceState → onDestroy → onCreate → onStart → onRestoreInstanceState → onResume。

数据保存:在 onSaveInstanceState(Bundle) 里存临时数据,onCreate / onRestoreInstanceState 里恢复。

追问点:onPause 里能做耗时操作吗?——不能,新 Activity 的 onResume 要等上一个 onPause 执行完(应用层如此),卡这里会掉帧。

Q2:Activity 的四种启动模式?

模式行为典型场景
standard 每次新建实例入栈 默认
singleTop 栈顶存在则复用(走 onNewIntent) 推送详情页,防连点开 N 个
singleTask 栈内存在则清其上所有 Activity 主页
singleInstance 独占一个任务栈 通话、来电页

追问点:singleTask + 不同任务栈(taskAffinity)的行为;onNewIntent 生命周期顺序(onNewIntent → onRestart → onStart → onResume)。

Q3:Service 的 startService 和 bindService 区别?

维度startServicebindService
生命周期 onCreate → onStartCommand,需主动 stopSelf/stopService onCreate → onBind → onUnbind → onDestroy
与调用者关系 无关联,调用者销毁 Service 还活着 绑定共存亡,可拿到 IBinder 通信
用途 后台下载、播放 与 Activity 交互(如音乐进度条)

追问点:Android 8.0 后台 Service 限制 → 前台服务(startForeground + 通知);保活为什么靠 Service 靠不住。

Q4:广播的有序广播和无序广播?

  • 普通广播(sendBroadcast):异步,所有接收者几乎同时收到,不可拦截。
  • 有序广播(sendOrderedBroadcast):按优先级依次传递,可 abortBroadcast() 截断、可修改结果。
  • 本地广播(LocalBroadcastManager):仅应用内,安全高效(官方已废弃,推荐用 EventBus/Flow 替代)。

追问点:静态注册广播在 Android 8.0 后的限制(大部分系统广播只允许动态注册)。

Q5:ContentProvider 的作用?

跨进程数据共享的标准方案。底层基于 Binder,通过 URI 定位数据(content://com.app.provider/user/1)。

追问点:为什么启动 App 时 ContentProvider 的 onCreate 先于 Application.onCreate?(initOrder 里 installContentProviders 先执行)——高频冷门题。


三、消息机制:Handler 必考五连

Q6:Handler 的完整工作原理?

标准答案(必须按链路背):

Handler.sendMessage(msg)
→ MessageQueue.enqueueMessage(msg, when) 消息入队(按时间排序)
→ Looper.loop() 死循环取消息 (主线程早有 Looper)
→ msg.target.dispatchMessage(msg) 分发给 Handler
→ handleMessage(msg) 处理

角色分工:

角色职责一句话
Handler 发送和处理消息 邮递员
Message 消息载体(含 what/obj/when/target) 信件
MessageQueue 按时间排序的优先级队列 信箱
Looper 死循环取消息,驱动分发 邮局分拣员
ThreadLocal 每个线程存自己的 Looper 工牌

Q7:主线程为什么不会因为 Looper.loop() 死循环卡死?

高频难题。答案分三层:

  • 主线程本来就该常驻——死循环是"有活干活、没活休眠"(epoll 机制),MessageQueue 的 next() 里 nativePollOnce 阻塞时不消耗 CPU,不是忙等。
  • 卡 ANR 的是消息处理慢,不是循环本身——ANR 是某条消息(如 input 事件)5 秒没处理完。
  • 整个 App 生命周期都跑在这个循环里——Activity 的 onCreate 都是被这个循环调度的一条消息。
  • Q8:Message.obtain() 为什么要复用?怎么复用?

    Message 内部维护一个链表池(sPool),obtain() 优先从池里取,recycle() 时清空数据挂回池头。避免高频发消息时大量创建对象触发 GC 抖动。

    追问点:为什么 Message 最大缓存 50 个(MAX_POOL_SIZE);View 的滑动消息同理用 obtain。

    Q9:Handler 导致内存泄漏的原因和解决?

    原因:非静态内部类 Handler 隐式持有 Activity,Message 未处理时被 MessageQueue 持有 → Message → Handler → Activity,Activity 销毁了却无法回收。

    解决:

  • Handler 声明为 static class + WeakReference<Activity>
  • onDestroy 里 handler.removeCallbacksAndMessages(null)
  • Q10:IdleHandler 是什么?

    MessageQueue 空闲时的回调(addIdleHandler)。用过的场景:延迟初始化非紧急任务(如日志上报),等主线程不忙了再干。追问点:ViewRootImpl 的首次绘制就依赖 IdleHandler(等消息队列空了再 measure/layout)。


    四、View 体系

    Q11:View 的绘制流程?

    标准答案:

    ViewRootImpl.performTraversals()
    ├── performMeasure() → onMeasure() 测量:确定 View 大小(MeasureSpec)
    ├── performLayout() → onLayout() 布局:确定 View 位置
    └── performDraw() → onDraw() 绘制:画到屏幕(Canvas)

    MeasureSpec 三种模式:

    模式含义对应
    EXACTLY 确定大小 match_parent / 固定 dp
    AT_MOST 最大不超过 wrap_content
    UNSPECIFIED 不限制 ScrollView 对子 View

    追问点:自定义 View 时 wrap_content 为什么和 match_parent 一样?——onMeasure 默认实现不处理 AT_MOST,要自己 setMeasuredDimension。

    Q12:事件分发机制?

    标准答案(经典 U 型图):

    Activity → Window → DecorView → ViewGroup → … → View

    三个核心方法:
    dispatchTouchEvent() 分发
    onInterceptTouchEvent() 拦截(仅 ViewGroup 有)
    onTouchEvent() 消费

    • 父拦截,子收不到:onInterceptTouchEvent 返回 true → 子 View 收 ACTION_DOWN 之后的系列事件 → 事件交给父的 onTouchEvent
    • 子处理了,父就不处理:子 onTouchEvent 返回 true 消费 → 父不再收 MOVE/UP
    • 请求父不拦截:requestDisallowInterceptTouchEvent(true)(子 View 在 DOWN 后调用)
    • 滑动冲突:外部拦截法(父改 onInterceptTouchEvent 判断方向)/ 内部拦截法(子 disallow)

    Q13:requestLayout / invalidate / postInvalidate 区别?

    方法作用范围触发线程
    requestLayout 从当前节点向上到 ViewRootImpl measure + layout + draw 可任意(最终排到主线程)
    invalidate 只重绘当前 View 只 draw(onDraw) 必须主线程
    postInvalidate 同 invalidate 只 draw 可子线程

    五、性能优化

    Q14:内存泄漏的常见场景和排查?

    高频泄漏场景(背 6 个):

  • 非静态内部类 Handler / Thread / AsyncTask 持有 Activity
  • 单例持有 Context(应持 ApplicationContext)
  • 广播 / EventBus / 回调没反注册
  • WebView 未销毁(新开进程 + 手动 onDestroy)
  • 静态变量持有 View / Bitmap
  • 匿名内部类(编译后持有外部引用)
  • 排查工具:LeakCanary(开发期自动抓)→ Memory Profiler → MAT 分析 hprof(看 GC Root 引用链)。

    Q15:卡顿的排查思路?

    定位:Perfetto / Systrace 抓帧 → 找掉帧的帧
    归因:看主线程在干什么
    ├── 主线程做 IO(读 SP / 数据库)→ 移到子线程 / MMKV
    ├── 主线程做复杂布局 → 降层级(ConstraintLayout / merge / ViewStub)
    ├── 过度绘制 → 开发者选项检查,红色区域优化
    └── 频繁 GC → 内存抖动,找循环里创建对象的地方

    关键指标:帧耗时 16ms(60Hz)/ 8.3ms(120Hz);Vitals 的 ANR 率 < 0.47%、崩溃率 < 1%。

    Q16:Bitmap 优化怎么做?

  • 采样压缩:inSampleSize 按目标尺寸算(先 inJustDecodeBounds 只读尺寸)
  • 格式:ARGB_8888 → RGB_565(无透明需求,内存减半)
  • 硬件位图:inPreferredConfig = HARDWARE,像素存 GPU 显存,不占 Java 堆
  • 及时回收:Android 8.0 后 Bitmap 像素存 native 堆(NativeAllocationRegistry 自动回收),老版本手动 recycle
  • Q17:APK 包体积优化?

    • 资源:图片转 WebP、无用在 R8/资源混淆(AndResGuard)压缩、按密度分包
    • 代码:R8 混淆裁剪(minifyEnabled + shrinkResources)
    • so:abiFilters 只留 armeabi-v7a / arm64-v8a,或按 ABI 分包
    • 动态交付:App Bundle(Play)按需下载

    六、Kotlin / 协程 / 新技术

    Q18:协程和线程的区别?(Android 场景)

    用户态轻量级"线程":挂起不阻塞线程、创建成本几十字节、结构化并发防泄漏。Android 标配用法:

    viewModelScope.launch {
    val user = withContext(Dispatchers.IO) { api.fetchUser() }
    updateUi(user) // 自动回主线程
    }

    追问点:suspend 原理(状态机);launch vs async;viewModelScope 为什么安全(Activity 旋转销毁自动取消)。

    Q19:Flow 和 LiveData 的区别?

    维度LiveDataFlow
    生命周期感知 ✅ 内置 需 repeatOnLifecycle 包一层
    操作符 少(map/switchMap) 丰富(debounce/filter/combine…)
    数据流类型 值持有 冷流/热流
    跨层使用 仅 UI 层 数据层/领域层通用

    结论:新项目 UI 状态用 StateFlow,数据流处理用 Flow,LiveData 只在老项目维护。

    Q20:Jetpack MVVM 的完整数据流?

    View(Activity/Fragment)
    ↕ 观察 StateFlow / LiveData
    ViewModel(持有 UI 状态,不持 Context)
    ↕ 调用
    Repository(单一数据源原则)
    ↙ ↘
    Room(本地缓存) Retrofit(网络)

    核心原则:数据单向流动(UDF)、单一数据源(SSOT)、ViewModel 不持有任何 Context。


    七、冷门但常问

    Q21:Android 的进程优先级?

    前台进程 > 可见进程 > 服务进程 > 缓存进程(后台 LRU)

    系统按优先级从低到高杀内存。追问点:为什么保活越来越难(厂商白名单 + Doze 模式 + App Standby),正路是把进程做轻。

    Q22:Binder 是什么?为什么选它而不是 Socket?

    Android 跨进程通信的底层机制。选 Binder 因为:一次拷贝(mmap 内存映射,Socket/管道要两次)、C/S 架构易用、进程 ID 校验安全性好。

    追问点:AIDL 是 Binder 的封装;四大组件的跨进程全靠 Binder。

    Q23:SP(SharedPreferences)为什么慢?替代品?

    SP 首次加载同步读文件 + 全量解析 XML 到内存(主线程 wait),写入 commit 同步、apply 虽异步但 QueuedWork 可能阻塞。

    替代:MMKV(mmap + protobuf,腾讯)、DataStore(Kotlin Flow,官方推荐)。

    Q24:View.post 为什么能拿到 View 宽高?

    post 的 Runnable 排进消息队列,执行时机在 ViewRootImpl 的 performTraversals(绘制)之后——View 测量完了自然有宽高。

    Q25:为什么 onActivityCreated 被废弃了?Fragment 通信怎么做?

    废弃是推动用 viewLifecycleOwner 观察数据。现代通信方式:ViewModel 共享(同一 FragmentManager scope)> 接口回调 > Result API。


    八、总结:面试复习路线

    第一梯队(必背):生命周期 / 启动模式 / Handler 五连 / 事件分发 / 绘制流程
    第二梯队(加分):性能优化实战案例 / Binder / 协程 Flow / MVVM 数据流
    第三梯队(亮点):冷启动优化 / 包体积 / 线上监控(APM)/ 跨进程方案选型

    最重要的一条建议:背完标准答案后,一定要准备一个自己项目里的真实案例。比如"我们 App 启动 3s → 1.2s"的优化过程,比 25 道八股更能让面试官记住你——八股筛人,案例定薪。


    参考资料

    • 《Android 开发艺术探索》任玉刚(四大组件 + View 体系)
    • 《Android 进阶解密》刘望舒(消息机制 + Binder)
    • Google 官方文档:developer.android.com(Jetpack / 性能)
    • Android 性能优化官方课:Measuring & Improving App Performance
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Android 常考面试题详解:从四大组件到性能优化,一篇吃透
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!