Android 面试题全解(结合 AOSP 源码 · 截至 Android 17 / API 37)
版本基线:Android 17(Cinnamon Bun,API 37,2026-06 发布,AOSP 源码已开源)。源码路径引用 AOSP 仓库(android.googlesource.com,主分支 frameworks/base 等)。
阅读提示:答案按"结论 → 原理 → 源码佐证"组织,面试时讲出源码类名/方法名是加分项。
一、语言基础(Java / Kotlin)
1. == 与 equals() 的区别?hashCode() 与 equals() 的关系?
==对基本类型比较值,对引用类型比较内存地址;equals()默认也是地址比较,String、Integer等重写了它比较内容。- 约定:equals 相等的两个对象,hashCode 必须相等;反之不必。HashMap 先用 hashCode 定位桶(bucket),桶内再用 equals 比较,所以重写 equals 必须重写 hashCode,否则 HashMap/HashSet 会出现"对象相等但存到了不同桶"的诡异 bug。
- 源码佐证:
java.util.HashMap#putVal中(k = p.key) == key || (key != null && key.equals(k))。
2. String、StringBuilder、StringBuffer 区别?
- String 不可变(final char\[\]),拼接会产生新对象;"a"+"b" 编译期常量折叠,但循环中
s += x等价于new StringBuilder().append(),会产生大量临时对象。 - StringBuilder 可变、非线程安全;StringBuffer 方法带
synchronized,线程安全但开销大。单线程一律用 StringBuilder。 - 源码佐证:
StringBuilder继承AbstractStringBuilder,扩容newCapacity = (oldCapacity << 1) + 2。
3. final、finally、finalize 区别?
- final:修饰类不可继承(String)、方法不可重写、变量不可重新赋值(引用不可变、对象内容可变)。
- finally:try-catch 后必然执行的代码块;注意 System.exit() 或 try 中死循环 不会执行,且 Android 中不建议在 finally 中 return(会吞掉异常)。
- finalize:Object 的空方法,GC 回收前由 FinalizerDaemon 线程调用一次;Android 官方明确反对使用(不确定何时执行、可能复活对象导致泄漏),替代方案:try-with-resources、Cleaner(API 33+ java.lang.ref.Cleaner)、引用队列。
4. 抽象类 vs 接口?
- 抽象类:可以有成员变量、构造方法、非抽象方法、任意访问修饰符;单继承;适合"is-a"且含共享实现。
- 接口:默认 public 抽象方法(Java 8 后可 default/static 方法)、常量;多实现;适合"has-a"能力契约。
- Kotlin 差异:接口可含属性声明(无幕后字段)和带默认实现的方法;抽象类可持有状态。Kotlin 类默认 final,需
open才可继承。
5. Java 四种引用类型?
- 强引用:正常 new;OOM 也不回收。
- 软引用 SoftReference:内存不足时回收,适合做缓存(Android 中 Bitmap 曾用,现推荐 LruCache)。
- 弱引用 WeakReference:GC 遇到就回收;WeakHashMap、Handler 静态内部类 + WeakReference 防泄漏的套路。
- 虚引用 PhantomReference:随时可能被回收,必须与 ReferenceQueue 配合,用于跟踪对象回收、替代 finalize。
- 源码佐证:ART 的 GC 按引用强度决定回收时机(
java.lang.ref包,ReferenceQueue + FinalizerReference)。
6. HashMap 底层原理?扩容机制?为什么容量是 2 的幂?
- JDK8 起:数组 + 链表 + 红黑树;链表长度 ≥ 8 且容量 ≥ 64 转红黑树,≤ 6 退化。
- 哈希扰动:
(h = key.hashCode()) ^ (h >>> 16),让高位参与桶定位,减少碰撞。 - 定位桶:
i = (n - 1) & hash。容量为 2 的幂时,(n-1) & hash 等价于 hash % n,且扩容时元素要么留在原索引、要么原索引 + oldCap(看 hash 新增的那位是 0 还是 1),迁移无需重新取模。 - 默认容量 16,负载因子 0.75,扩容为 2 倍;Android 的
android.util.ArrayMap/SparseArray在 key 为 int 或数据量 <1000 时更省内存(两个数组而非 Entry 节点 + 二分查找)。
7. ConcurrentHashMap 如何实现线程安全?
- JDK8:CAS + synchronized 锁桶头节点 (锁粒度细化到桶,替代 JDK7 的 Segment 分段锁);
sizeCtl控制初始化/扩容;扩容时多线程协助迁移(ForwardingNode)。 put:桶空则 CAS 插入;桶非空 synchronized(f) 锁住首节点后链表/红黑树插入。- Android 注意:集合类默认线程不安全,UI 层跨线程更新应走 Handler/协程主线程切换。
8. synchronized vs ReentrantLock?
- synchronized:JVM 层面(monitorenter/monitorexit 字节码,ACC_SYNCHRONIZED 标志);自动释放;锁升级(偏向→轻量→重量);不可中断、非公平、不支持条件队列多组。
- ReentrantLock:API 层面(AQS);可尝试获取 tryLock、可中断 lockInterruptibly、可指定公平锁、可多 Condition(实现生产者消费者精准唤醒);必须手动在 finally unlock。
- 源码佐证:ReentrantLock 内部 Sync 继承
java.util.concurrent.locks.AbstractQueuedSynchronizer,state 计数实现可重入。
9. volatile 的作用?能否保证原子性?
- 保证可见性 (MESI 缓存一致性,读写直接到主存)与禁止指令重排序(内存屏障);不保证原子性(i++ 是读-改-写三步)。
- 典型场景:状态标志位、DCL 单例(
private static volatile Singleton instance)。 - Android 源码大量使用:
android.view.View#mPrivateFlags等状态位(部分场景);AtomicInteger 兜底计数。
10. 什么是内存泄漏?Java 常见场景?
- 对象不再被使用但 GC Roots 仍可达,无法回收,最终可能 OOM。
- 常见场景:静态变量持有 Activity/Context、Handler 非静态内部类延迟消息、匿名内部类(隐式持有外部类)、未反注册的广播/监听器、WebView、线程池任务持外部引用、资源未关闭(Cursor、Stream)。
- 检测:LeakCanary(弱引用 + ReferenceQueue + 手动触发 GC);MAT 分析 hprof( dominator tree 找 GC Roots 引用链)。
11. Kotlin let / run / with / apply / also 区别?
| 函数 | 接收者(this/it) | 返回值 | 典型用途 |
|---|---|---|---|
| let | it | Lambda 结果 | 判空 + 转换 ?.let{} |
| run | this | Lambda 结果 | 配置并计算返回值 |
| with | this(非扩展) | Lambda 结果 | 对同一对象多次操作 |
| apply | this | 接收者本身 | 构建者模式配置 |
| also | it | 接收者本身 | 副作用:日志、校验 |
均为内联函数(inline),无运行时 lambda 对象开销(除非参数化类型)。
12. Kotlin 协程是什么?与线程的区别?launch 与 async 区别?
- 协程是用户态轻量级线程,基于 CPS 状态机挂起(suspend),单线程可跑数万协程;线程切换走内核,协程切换只在用户态,成本更低。
- launch 返回 Job( fire-and-forget ),async 返回 Deferred(可 await 结果,类似 Promise)。
- 调度:Dispatchers.Main(关联主线程 Looper 的 HandlerDispatcher,见
kotlinx.coroutines.android.HandlerDispatcher)、Default(CPU 密集,线程池=核数)、IO(阻塞 IO,弹性扩容)。 - 源码佐证:suspend 函数编译后带
Continuation参数,挂起点编译为状态机 label 分支;Android 17 时代官方推荐 Flow + 协程替代 RxJava/LiveData 组合。 - 注意:协程不 magically 免死锁/免泄漏,viewModelScope/lifecycleScope 自动绑定生命周期取消。
13. data class / sealed class / object 的作用?
- data class:自动生成 equals/hashCode/toString/copy/componentN;主构造参数至少一个 val/var;适合做模型/不可变值对象(DTO)。
- sealed class:受限类层级,编译期可知全部子类,when 表达式可穷尽(不用 else),做状态机/事件类型安全。
- object:单例(编译为静态 INSTANCE 字段 + 私有构造);companion object 替代 static;
object : ClickListener{}匿名对象。 - 补充:Kotlin 1.x 后 sealed interface 也可;value class(内联类)包装类型无装箱开销。
14. lateinit vs lazy?
- lateinit:仅 var、非空类型、自定义 getter/setter 不行;访问未初始化抛 UninitializedPropertyAccessException;
::prop.isInitialized检查(仅类内可用);适合 DI 注入点。 - lazy:仅 val,首次访问时同步初始化(默认 SYNCHRONIZED 模式,也有 PUBLICATION/NONE);适合重型对象延迟创建。
- 底层:lazy 代理模式(SynchronizedLazyImpl 双重检查)。
二、Android 基础
15. Activity 生命周期?onSaveInstanceState / onRestoreInstanceState 何时调用?
- 标准生命周期:onCreate → onStart → onResume → onPause → onStop → onDestroy(另加 onRestart)。
- 异常销毁(旋转、内存不足被杀、深色模式切换等配置变更):onPause/onStop 之后调 onSaveInstanceState(Bundle) 保存 UI 状态;重建后 onCreate(Bundle) 或 onRestoreInstanceState 中恢复。
- 源码佐证:
ActivityThread#handleRelaunchActivity、Activity#performSaveInstanceState;View 层级状态通过View#saveHierarchyState(SparseArray)保存,id 是 key------这就是为什么自定义 View 要被恢复状态必须有 id。 - 正常 finish() 不会走 onSaveInstanceState;onPause 中不能安全假设是否重建。
16. Activity 四种启动模式?
- standard:默认,每次都新建实例,进当前任务栈顶。
- singleTop:栈顶复用(onNewIntent);适合防连点重复打开。
- singleTask:栈内唯一;启动时若已存在则清其上方所有 Activity 并复用;适合首页/主界面。
- singleInstance:独占一个任务栈,全局唯一;适合全局唯一入口(如来电界面)。
- 栈管理源码:
ActivityRecord+Task(Android 12 起 ActivityTaskManagerService 重构,旧 ActivityManagerService 拆分出 ATMS)。
17. A 启动 B,两者生命周期顺序?
- A.onPause → B.onCreate → B.onStart → B.onResume → A.onStop。
- A 的 onPause 先执行,B 的窗口绘制完成后才回调 A.onStop(等待第一帧,避免 A 闪黑/白屏)。
- 透明/对话框主题 Activity B:A 不会走 onStop(只到 onPause),因为 A 仍可见。
- 源码佐证:
ActivityThread#handleResumeActivity中调用 Activity 可见性调度;TransactionExecutor 按 ClientTransaction 顺序分发生命周期回调。
18. Service 两种启动方式?前台 Service 如何保活?
- startService:onCreate → onStartCommand(可多次)→ stopSelf/stopService → onDestroy;与启动者解耦,长期后台任务。
- bindService:onCreate → onBind(只一次,返回 IBinder);客户端拿到 Binder 通信;全部 unbind 后销毁;适合 IPC/局部通信。
- 两者可同时存在,需同时 stop + unbind 才销毁。
- 前台 Service:startForegroundService()(API 26+ 强制)后 5 秒内必须 startForeground(id, Notification),否则 ANR/FatalException;
ForegroundServiceStartNotAllowedException(后台启动限制,API 31+ 收紧)。 - "保活":前台 Service + 双进程互相守护已失效(API 26 JobScheduler/Doze 收紧);合规做法是 WorkManager/前台 Service + 系统白名单申请。
- 源码佐证:
ActivityManagerService/ActiveServices(Android 12+ 位于 com.android.server.am.ActiveServices)负责 Service 记录与 ANR 检查(Service ANR 超时 20s:前台 20s / 后台 200s on Android 11+)。
19. IntentService vs Service?
- IntentService:HandlerThread + 单工作线程,串行处理 intent,处理完自动 stopSelf;已废弃(API 30 Deprecation),官方推荐 WorkManager(可约束网络/电量、可持久化、可观察)。
- Service:默认跑主线程,需自己开线程,不自动停止。
20. BroadcastReceiver 分类与注册方式?有序广播为何能拦截?
- 分类:普通广播(无序、异步并发)、有序广播(按优先级串行,可 abortBroadcast 截断,结果可传递给下一个)、粘性广播(已废弃,API 21+ 弃用,用 LiveData/EventBus 替代)、本地广播(LocalBroadcastManager,已废弃,推荐 LiveData/Flow)。
- 注册:静态(AndroidManifest,隐式广播受限,API 26+ 大部分隐式广播不能静态注册)、动态(registerReceiver/unregisterReceiver,Context 生命周期内)。
- 有序广播拦截:内部链表按 priority 排序逐个 deliver,
BroadcastQueue#processNextBroadcast中 receiver 可设置结果并调用abortBroadcast()(mAborted=true 后终止链)。发送有序广播用sendOrderedBroadcast,可指定 resultReceiver 收最终结果。 - 注意:onReceive 中不能做异步耗时操作,超过 ~10s(前台)/60s(后台)触发 ANR。
21. ContentProvider 的作用?如何自定义?
- 跨进程统一数据访问抽象(增删改查),底层 Binder;也是应用启动入口之一(provider 的 onCreate 在 Application.onCreate 之前执行------可做无感初始化,App Startup 库的原理)。
- 自定义:继承 ContentProvider 实现 onCreate/query/insert/update/delete/getType;AndroidManifest 声明 authorities;
UriMatcher匹配 URI;配合ContentResolver访问;通知数据变更notifyChange(uri, observer),跨进程用 ContentObserver(registerContentObserver,走 Binder)。 - 批量操作:ContentProviderOperation.applyBatch;IPC 大数据用 file descriptor(openFile/ParcelFileDescriptor)。
- 源码佐证:
ActivityManagerService#publishContentProviders、ContentProviderHolder(Binder 代理 stub)。
22. Fragment 生命周期?与 Activity 的关系?
- onAttach → onCreate → onCreateView → onViewCreated → onStart → onResume → onPause → onStop → onDestroyView → onDestroy → onDetach。
- 宿主 Activity 生命周期变化会分发到 Fragment(FragmentManager 驱动);onActivityCreated 已废弃(API 28+),用 onViewCreated + ViewLifecycleOwner。
- 关键:视图生命周期(View 的创建/销毁)与 Fragment 实例生命周期分离,观察 UI 数据用 viewLifecycleOwner 而非 fragment 自身。
23. Fragment 通信方式?
- 同 Activity 内:共享 Activity 作用域 ViewModel(LiveData/Flow 自动解绑,官方推荐)。
- 接口回调:Fragment 定义接口,Activity 实现并转发。
- setTargetFragment + setFragmentResult(TargetFragment 已废弃,API 28+ 后推荐使用
setFragmentResult/setFragmentResultListener,本质是经 FragmentManager 的 Bundle 传递)。 - 父 Fragment 找子:getChildFragmentManager;子找父:getParentFragment。
- 跨 Fragment 直接互相持有引用 → 泄漏 + 耦合,应避免。
24. Handler 原理?
- 三要素:Handler(发送/处理)、MessageQueue(单链表,按 when 时间排序)、Looper(循环取消息;线程局部唯一,sThreadLocal)。
- Looper.prepare() 创建并绑定当前线程;loop() 死循环:MessageQueue.next() 取消息(无消息时 nativePollOnce 挂起,省 CPU),msg.target.dispatchMessage(msg)。
- 消息入队:sendMessage → enqueueMessage(插入链表按触发时间排序,需要唤醒则 nativeWake)。
- 完整链路源码:
Looper.loop()→MessageQueue.next()(idleHandler 机制、同步屏障 async barrier、nativePollOnce底层 Linux epoll)→Handler.dispatchMessage:先 callback(post 的 Runnable),再 mCallback.handleMessage,最后 handleMessage。 - Android 17 相关:Google 在 AOSP 中持续改造 MessageQueue 内部实现(搜索源码可见 lock-free 相关提交,面试可提及"最新版本对 MQ 做了无锁化改造以提升跨线程性能"------AOSP main 分支已有 LockFreeMessageQueue 相关实验性重构,了解即可)。
25. Handler 内存泄漏原因与解决?
- 非静态内部类/匿名类隐式持有外部 Activity 引用;延迟消息在队列中存活时 Activity 无法回收(常见于 sendMessageDelayed 关闭页面后)。
- 解决:静态内部类 Handler + WeakReference;或回调里判空;页面销毁 removeCallbacksAndMessages(null);以及用 viewLifecycleOwner/协程替代。
- 原理:Message.target 强引用 Handler → Handler 强引用 Activity。
26. 为什么子线程不能直接更新 UI?
- UI 非线程安全,Android 采用单线程模型(UI 线程)+ 检查机制。
- 源码:
ViewRootImpl#checkThread():if (mThread != Thread.currentThread()) throw new CalledFromWrongThreadException(...)。所有 UI 操作最终走到 ViewRootImpl(invalidate/requestLayout 后由 Choreographer 调度),统一被检查。 - 为什么不加锁 UI toolkit:加锁会让渲染链路复杂且卡顿;单线程 + Handler 模型更简单高效。
27. View 绘制流程(measure / layout / draw)?
- measure:自上而下。父 View 用 MeasureSpec(size + mode:UNSPECIFIED/EXACTLY/AT_MOST)约束子 View,子 View onMeasure 后 setMeasuredDimension;MeasureSpec 由父 Spec 和子 LayoutParams 组合(getChildMeasureSpec)。
- layout:自上而下确定位置;onLayout 中调用 child.layout,先 layout 后才有 getWidth/getHeight(getMeasuredWidth 是 measure 阶段结果)。
- draw:ViewGroup.draw 依次执行:背景 → onDraw → dispatchDraw(子 View)→ 前景/滚动条 → 默认实现有优化标志位(ViewGroup 默认不重绘背景等)。
- 触发链路:invalidate → 标记脏区 → scheduleTraversals → Choreographer 下一帧 performTraversals(measure→layout→draw 三段都由 ViewRootImpl 的 doTraversal 驱动)。
28. View 事件分发机制?
- 三个方法:dispatchTouchEvent(分派)、onInterceptTouchEvent(仅 ViewGroup,拦截)、onTouchEvent(消费)。
- 流程(DOWN 事件):Activity.dispatchTouchEvent → PhoneWindow/DecorView → ViewGroup.dispatchTouchEvent:若 onInterceptTouchEvent 返回 true 拦截,事件交自身 onTouchEvent;否则遍历子 View 命中测试(子 View 顺序逆序,即最上层优先),递归分发。
- 消费后同一事件序列(DOWN→MOVE→UP)后续事件都交该消费者(mFirstTouchTarget 链表记录);都不消费则回溯给 Activity。
- 优先级:onTouchListener > onTouchEvent > onClickListener(onClick 在 ACTION_UP 后触发)。
- 源码佐证:
ViewGroup#dispatchTouchEvent(命中判断 child 区域、canViewReceivePointerEvents)、View#dispatchTouchEvent(ListenerInfo.mOnTouchListener 先执行)。
29. 滑动冲突如何解决?
- 场景:内外层都可滑动(ScrollView 嵌套 ListView、ViewPager 嵌套 RecyclerView)。
- 外部拦截法:父 View onInterceptTouchEvent 中按方向/条件决定是否拦截(推荐,一次判断,逻辑集中在父)。例:横向滑动距离 > 纵向则拦截(ViewPager 内置实现思路)。
- 内部拦截法:子 View 主动 requestDisallowInterceptTouchEvent(true) 禁止父拦截,处理完再恢复。配合父 onInterceptTouchEvent 处理 ACTION_DOWN 时默认不拦截。
- 现代方案:NestedScrolling 机制(API 21+,RecyclerView/NestedScrollView 原生支持)------子 View 派发前通过 dispatchNestedPreScroll 询问父 View 是否先消费,协作式分发,解决嵌套滑动(CoordinatorLayout 依赖此机制,Behavior 作为父端消费方)。
30. View 与 ViewGroup 区别?
- View:UI 基本单元,无子 View,负责自身绘制与事件处理。
- ViewGroup 继承 View:容器,持有 children 数组,多了 measureChild/布局子 View、onInterceptTouchEvent、dispatchDraw 等;不绘制内容时 onDraw 默认不调用(viewGroupWillNotDraw 标志优化)。
31. 自定义 View 分类与实现?
- 三类:① 继承已有 View 扩展(AppCompatTextView);② 组合已有 View(ConstraintLayout 组合);③ 自绘 View(继承 View 重写 onDraw,如图表、进度条);④ 自绘 ViewGroup(重写 onMeasure/onLayout,如流式布局、下拉刷新)。
- 关键步骤:自定义属性(values/attrs.xml declare-styleable → obtainStyledAttributes 务必 recycle);onMeasure 处理 wrap_content;onDraw 用 Paint/Canvas(硬件加速下避免不支持的操作);onSaveInstanceState/onRestoreInstanceState 保存状态;处理 padding、点击区域(API 29+ 可重写 onResolvePointerIcon 等辅助功能配套)。
- 硬件加速注意:canvas.clipPath 等 API 在低版本 SW 渲染昂贵,可用 View.LAYER_TYPE_SOFTWARE 局部降级。
三、进程与线程
32. Android IPC 方式有哪些?
- Binder(AIDL/Messenger/ContentProvider/四大组件跨进程底层都是 Binder)
- 匿名共享内存 Ashmem / MemoryFile(大数据,如 Bitmap 传递、bundle 中的 Bitmap 默认走 ashmem)
- Socket(网络或 local socket,zygote 就是用 socket 接收启动请求)
- 管道 Pipe(较少)
- 文件共享(并发问题,临时方案)
33. Binder 机制原理?为什么选 Binder 而不是传统 IPC?
- 内核层:Linux 只有 Binder 驱动(不是 Linux 原生 IPC),位于 /dev/binder(现多实例:/dev/binder、/dev/hwbinder(HAL)、/dev/vndbinder(vendor))。
- 模型:Client 进程通过 BinderProxy(BpBinder 代理)发起 transact → 驱动把数据(一次拷贝,sendbuffer 到 target 的映射内存)copy_from_user 到内核,目标进程 Binder 线程池(Binder_1、Binder_2...)收到 BR_TRANSACTION → Stub 子类 onTransact 执行 → 结果原路返回。仅一次数据拷贝(对比管道/消息队列两次、Socket 两次)。
- 句柄/引用计数:驱动管理 binder_node/binder_ref,进程死亡时驱动通知(DeathRecipient/BinderDied,linkToDeath 实现"服务端死亡回调")。
- 为什么 Binder:性能(一次拷贝 vs socket 两次)+ 安全性(UID/PID 在内核层获取,身份可信,传统 IPC 依赖上层协议)+ C/S 易用性(面向对象的跨进程调用,同步语义直观)。缺点:不能跨设备(网络场景还得 Socket)。
- 源码佐证:
frameworks/native/libs/binder/(BpBinder、BBinder、IPCThreadState)、驱动drivers/android/binder.c;Java 层android.os.Binder、BinderProxy(native 方法 transactNative)。
34. AIDL 使用流程?支持的数据类型?
- 流程:创建 .aidl 文件(声明 interface 与方法)→ build 生成同包名的 Stub 子类(服务端继承 Stub 实现 onTransact;客户端 asInterface(IBinder) 拿到代理,跨进程时返回 Stub.Proxy)→ 服务端 Service onBind 返回 Stub 实例 → 客户端 bindService 后 asInterface 使用。方法调用线程:客户端主线程调用,服务端 onTransact 运行在 Binder 线程池(勿做 UI 操作),回调注意线程。
- 支持类型:基本类型(int/long/boolean/double/float/byte/char,除 short)、String、CharSequence、Parcelable、List/Map(元素须为支持类型,定向 in/out/inout)、其他 AIDL 接口、Bundle。
- in/out/inout:定向标签声明数据流向,out/inout 需要客户端用具体实现类(如 CopyOnWriteArrayList)。
35. Parcelable vs Serializable?
- Serializable:Java 原生,反射 + IO,慢且产生大量临时对象(内存抖动);靠 serialVersionUID 兼容;支持任意对象图(含循环引用)。
- Parcelable:Android 定制,序列化到 Parcel(内存 Buffer,直接写入字节),无反射,快数倍(官方称 Serializable 的 10 倍);需实现 describeContents/writeToParcel/CREATOR(Creator 回调创建对象,为什么是 CREATOR 而非构造器:反序列化需带 ClassLoader 调用)。
- 注意:Parcelable 只适合内存传递(进程内/经 Binder 的一次拷贝),持久化到磁盘仍用 Serializable/JSON(Parcel 格式不保证跨版本稳定)。
- Kotlin 简化:
@Parcelize(kotlin-parcelize 插件)自动生成实现。
36. ANR 是什么?条件、定位与避免?
- 定义:应用主线程在超时时间内未响应(Input 事件 5s、广播前台 10s 后台 60s、Service 前台 20s 后台 200s、ContentProvider publish 超时等)。
- 机制:系统侧超时埋点(AMS/ATMS 在事件分发时 post 延迟消息或设置超时)→ 超时触发后收集各进程栈写入 /data/anr/traces.txt(Android 11+ /data/anr/anr_*)。
- 定位:logcat 的 "ANR in xxx" 行(主线程堆栈 + CPU 占用)、traces 文件看主线程是否 blocked/waiting(锁等待、IO、死循环)、结合 systrace/Perfetto 看帧耗时。
- 避免:主线程只做 UI 与轻量逻辑;IO/网络/DB 放子线程(Room 默认子线程);复杂 JSON 解析、Bitmap decode 移出主线程;避免锁竞争在主线程持锁;BroadcastReceiver 内不 sleep。
37. 如何开启多进程?Application 会创建几次?
- 在 AndroidManifest 中对四大组件指定
android:process=":remote"(私有进程,以包名前缀)或完整进程名(全局共享进程,其他应用可加入)。 - 每个进程独立:独立的虚拟机(Dalvik/ART 实例)、独立的 Application、独立的内存空间(静态变量/单例不共享!);进程间通信必须 IPC。
- Application onCreate 每个进程都会执行(次数=进程数),用 processName 区分做按需初始化(获取当前进程名:
ActivityThread.currentProcessName()API 28+,或 ApplicationInfo 遍历)。
四、性能优化
38. 如何检测和解决内存泄漏?常用工具?
- 检测工具:
- LeakCanary:注册 Activity 生命周期回调,onDestroy 后若对象 5s 内未被回收则 dump hprof,Shark 引擎解析生成引用链,通知栏展示泄漏路径。
- Android Studio Profiler:Memory Profiler 手动 dump/record Java/Kotlin allocations,看对象分配数量与持有者。
- MAT(Eclipse Memory Analyzer):分析 hprof,Dominator Tree 看大对象,Path to GC Roots(排除软/弱引用)找引用链。
- dumpsys:
adb shell dumpsys meminfo <pkg>看 Java/native 内存与 Activities/Views 数量是否异常增长。
- 解决:消灭引用链(见 Q10/Q25 场景);大图用 LruCache + 主动 recycle(API 30+ Bitmap 回收交给 GC,但 native 内存仍需注意);WebView 独立进程并销毁前 removeAllViews。
39. OOM 常见原因?Bitmap 如何避免 OOM?
- 常见原因:内存泄漏累积;Bitmap 大图(像素内存 = 宽×高×每像素字节,ARGB_8888 为 4 字节,一张 4000×3000 照片 ≈ 48MB);一次性加载大量资源/对象(List 中大量全尺寸图);内存抖动(循环中临时对象)触发 GC 跟不上。
- Bitmap 对策(BitmapFactory + Options):
- inJustDecodeBounds=true 先读尺寸(不分配像素内存)→ 按控件尺寸计算 inSampleSize(2 的幂,宽高各缩小 1/inSampleSize)→ 二次解码。
- inPreferredConfig:透明度要求不高用 RGB_565(省一半)。
- inBitmap 复用内存(API 19+ 尺寸要求放宽:解码图 ≤ 复用图内存即可),搭配 LruCache 实现滚动复用。
- 大图显示用 BitmapRegionDecoder 局部解码(长图)或 subsample 显示 + 手势放大区域解码。
- 现代方案:直接用 Glide/Coil(Coil 基于协程,Android 13+ 有内置 per-app 语言无关...不,重点是 Coil 轻量);Glide 自动 inSampleSize + BitmapPool 复用。
40. Bitmap 内存占用如何计算?如何压缩?inSampleSize 作用?
- 内存 = width × height × 每像素字节数;ARGB_8888 = 4B,RGB_565/ARGB_4444 = 2B,ALPHA_8 = 1B。注意 density:解码时 inDensity/inTargetDensity 影响缩放后尺寸。
- Android 8.0(API 26)起像素内存放 native 堆(以前放 Java 堆,native 层由 Bitmap.native 对象 + ashmem),Java 堆压力减小,但 native OOM 照样崩;native 内存由 ART GC 联动回收(NativeAllocationRegistry),不用手动 recycle(API 30 recycle 无效并废弃手动调用)。
- 压缩:inSampleSize 采样(有损缩小尺寸);inPreferredConfig 降位深;inScaled;质量压缩(Bitmap.compress(JPEG, quality))只对存储/上传文件有效,不改变解码后内存占用。
- inSampleSize:采样率,1/2 表示宽高各取 1/2、像素数 1/4,最终取最接近 2 的幂且 ≥ 传入值。
41. UI 卡顿原因与检测?
- 原因(16ms/帧 @60Hz,Android 高刷 90/120Hz 则 11/8ms):主线程耗时操作(IO、锁等待、序列化);布局层级深/过度绘制(Overdraw);频繁 requestLayout(布局震荡);onDraw 重活(大图 decode、复杂 Path 软件绘制);GC 暂停(内存抖动);帧回调里 new 大量对象。
- 检测:
- Choreographer.getInstance().postFrameCallback 自测帧间隔(FrameMetrics 回调)。
- FrameMetrics API 24+:Window.addOnFrameMetricsAvailableListener 给出 vsync 各阶段耗时(layout/measure/draw/input)。
- systrace / Perfetto:trace 各线程、看帧丢失(dropped frames)、 binder 调用。
- StrictMode:磁盘/网络读写违规直接闪红框。
- GPU 呈现模式分析(开发者选项柱状图)、过度绘制调试(蓝绿红区)。
- 修复:异步化、减少层级(ConstraintLayout)、ViewStub 懒加载、避免 requestLayout 连锁、硬件加速、LruCache 缓存解码结果、reduce bitmap size。
42. 布局优化手段?
- include(复用布局,可覆写 layout_* 属性需同时指定宽高)、merge(替换 include 根节点,减少一层 ViewGroup)、ViewStub(惰性加载,inflate 时替换)、ConstraintLayout(扁平化层级,相对定位替代嵌套 LinearLayout)。
- 其他:减少 overdraw(背景透明/移除 windowBackground 过渡绘制------启动窗口背景主题绘制后恢复);item 复用(RecyclerView);Hierarchy Viewer(已弃用,用 Layout Inspector);尽量 layout_width 不用 wrap_content 触发两次测量;ConstraintLayout 的 0dp(MATCH_CONSTRAINT) 性能优于 match_parent。
43. 启动优化:冷启动/热启动/温启动区别?
- 冷启动:进程不存在,从 fork 进程开始(zygote fork → Application onCreate → ActivityThread handleBindApplication → 创建 Application → attachBaseContext → onCreate → 第一个 Activity onCreate/onResume → 首帧绘制)。
- 温启动:进程在但 Activity 被回收(如 home 键后进程存活,任务栈重建 Activity)。
- 热启动:进程和 Activity 都在,从 onResume 恢复(最快)。
- 优化手段:
- 减少 Application onCreate 初始化(懒加载、按进程/按需要初始化、App Startup 库聚合 content provider 初始化)。
- 闪屏页主题(windowSplashScreen / 旧版 windowBackground 占位,Android 12+ SplashScreen API 统一启动画面,避免白屏)+ 预加载首屏数据(有反例:过度预加载拖慢启动)。
- 异步初始化(线程池/WorkManager 延迟任务,注意多进程各跑一遍的坑)。
- 类加载优化:避免启动路径上反射;AOT(ART 的 dex2oat 编译为机器码)+ Baseline Profiles(官方库提供基线配置,Android 13+ 可通过 Play 下发,启动提速 15-30%)。
- 布局优化:首帧减少 include 层级、异步 inflate(AsyncLayoutInflater)。
44. APK 体积优化?
- 资源:WebP 替代 PNG(有损 WebP 体积 30-70%);矢量图 VectorDrawable(小图标);资源混淆 AndResGuard / AAPT2 资源链接去重;移除未用资源(shrinkResources true 配合 ProGuard/R8);语言只保留需要的(resConfigs);动态交付:Android App Bundle(Play 上按设备下发,AAB 本身不上传 APK 给用户侧)。
- 代码:R8 全量混淆压缩;移除废弃依赖(support 库替换 androidx 后冗余);So 库:只保留 armeabi-v7a/arm64-v8a(按需),或 ABI 拆分(splits);AGP 的 resource shrinking + code shrinking。
- 其他:Lint 扫描无用资源;大图资源放合适 drawable 密度桶;用 pngquant 等工具预处理。
45. 电量优化与网络优化?
- 电量:Doze 模式(灭屏空闲后延迟 JobScheduler/Alarm 网络访问)与 App Standby Bucket(App 待机存储桶:active/working_set/frequent/rare/restricted,限制后台任务频率,API 28+);AlarmManager setExactAndAllowWhileIdle 受限;避免频繁唤醒(心跳合并、推送代替轮询);JobScheduler/WorkManager 让系统批量调度网络任务;减少 wake lock 持有时间(PowerManager.WakeLock 用完即 release);传感器按需注册注销;定位用 fused location + 精度降档。
- 网络:批量请求、避免轮询(WebSocket/FCM 推送)、缓存(OkHttp Cache-Control、ETag)、图片按尺寸请求(CDN 裁剪参数)、Gzip/Brotli、弱网重试退避(exponential backoff)、监听网络状态延迟非紧急任务(ConnectivityManager.NetworkCallback)、流量省流模式(JobInfo#setRequiresDeviceIdle + setRequiredNetworkType UNMETERED)。
五、架构与设计模式
46. MVC / MVP / MVVM 区别与优缺点?
- MVC(早期 Android 语境):Model 数据层、View 布局、Controller 常为 Activity/Fragment 身兼 View 与 Controller → Activity 臃肿、难以测试。
- MVP:View 与 Model 解耦,Presenter 中间协调(接口契约 IView);优点:职责清晰、可测(Presenter 纯 Java/Kotlin 不依赖 Android 框架);缺点:接口爆炸、Presenter 与 View 生命周期同步复杂、内存泄漏需手动 detach(Presenter 若被 View 持有且异步任务回链 View)。
- MVVM(官方 Jetpack 方案):ViewModel 持有 UI 状态、LiveData/Flow 暴露;View 观察数据自动刷新(声明式/订阅式);生命周期安全(LiveData 感知 Lifecycle);缺点:LiveData 粘性问题(setValue 后新观察者收到旧值,用 SingleLiveEvent/Flow 替代)、DataBinding 编译期生成代码调试用成本、双向绑定滥用。
- 演进:MVP → MVVM(Jetpack 全家桶)→ MVI(单向数据流:State 不可变 + Reducer + Effect,Compose 时代主流,如 Compose + StateFlow + Flow)。
47. Jetpack 常用组件?
- ViewModel:配置变更存活的状态容器(Activity 销毁重建时保留------见 Q49)。
- LiveData:生命周期感知的数据持有类(observe 自动绑定 LifecycleOwner 且在 STARTED 后才下发)。
- Room:SQLite ORM(注解处理器 kapt/ksp 生成实现;配合 Flow 做响应式查询)。
- Navigation:单 Activity 多 Fragment 导航图 + Safe Args 传参 + 深链接。
- WorkManager:保证执行的延迟任务(约束:网络/充电/电量低;链式任务 OneTimeWorkRequest/PeriodicWorkRequest;底层 JobScheduler/AlarmManager+BroadcastReceiver 双实现按 API 选择)。
- Paging 3:分页加载(PagingSource/RemoteMediator)。
- DataStore:SharedPreferences 替代(Proto/KV,基于文件 + Flow)。
- Hilt:DI 框架(Dagger2 之上注解驱动)。
- Compose:声明式 UI 工具包(后续详述)。
48. LiveData 原理?为什么能感知生命周期?
- 核心:observe 时把 Observer 包成 LifecycleBoundObserver(实现 LifecycleEventObserver),存入父类 ObserverWrapper 的 SafeIterableMap,并注册到传入的 LifecycleOwner 的 LifecycleRegistry。
- 生命周期回调:onStateChanged 中,DESTROYED 时自动 removeObserver(防泄漏);STARTED/RESUMED 时 activeStateChanged(true) → dispatchingValue 向 active 观察者推数据;非活跃时不更新(粘性特性来源:onActive 后补发最后值,mLastVersion < mVersion 则 considerNotify)。
- setValue 主线程、postValue 经 ArchTaskExecutor 主线程 Handler 切回;postValue 多次只保留最后一次(覆盖 mPendingData)。
- 感知生命周期本质:LifecycleRegistry 在 Activity/Fragment 生命周期回调时派发 Event,LiveData 的包装观察者订阅这些事件。
49. ViewModel 生命周期?为什么配置变更不销毁?
- 作用域跟随传入的 ViewModelStoreOwner(Activity/Fragment/NavBackStackEntry);Activity finish 或 Fragment 真正销毁时 ViewModelStore.clear() 调 ViewModel.onCleared 销毁。
- 配置变更不销毁的原理:Activity 因旋转销毁重建时,Activity 实例销毁但 ViewModelStore 对象被 NonConfigurationInstances 保留(ActivityThread#handleDestroyActivity 中 retainNonConfigurationInstances 传入新实例,重建时 getLastNonConfigurationInstance 取回);而 onCleared 在真正 finish(isChangingConfigurations=false)时调用。
- 源码佐证:
androidx.lifecycle.ViewModelStore(HashMap<String, ViewModel>)、ComponentActivity#onRetainNonConfigurationInstance(AndroidX 实现,NonConfigurationInstances存 viewModelStore)。 - 注意:ViewModel 不能持有 View/Context(泄漏);跨 Fragment 共享用 activity 作用域。
50. Jetpack Compose 核心思想?声明式 vs 命令式?
- 声明式 UI:用 @Composable 函数描述"界面应该是什么样"(UI = f(state)),状态变化触发重组(recomposition),框架 diff 局部更新;命令式(View 体系)手动 findViewById + setText/setVisibility 同步状态与 UI。
- 核心机制:编译器插件将 @Composable 函数编译为带 Composer 插桩的函数(记住结构:groups/slots 表);
remember { }在重组间缓存状态(memory);derivedStateOf、remember(keys)条件记忆;key()稳定重组边界;LazyColumn虚拟化列表。 - 状态管理:State(mutableStateOf / rememberSaveable 进程死亡恢复)+ hoisting(状态上提)+ unidirectional data flow;ViewModel + Compose 是官方架构。
- 互操作:ComposeView 嵌入传统布局、AndroidView 嵌入 View、 rememberLauncherForActivityResult 等。
- 面试加分:Compose 的重组是"函数重新执行",非整树重绘;写代码避免在重组中做副作用(用 LaunchedEffect/SideEffect/DisposableEffect)。
51. 常见设计模式在 Android 中的应用?
- 单例:Application、系统服务(getSystemService)、Room.databaseBuilder 返回单例;枚举/DCL/volatile 静态内部类。
- Builder:AlertDialog.Builder、NotificationCompat.Builder、Retrofit.Builder、OkHttpClient.Builder。
- 观察者:LiveData(被观察者)/Observer、广播、EventBus、RecyclerView.AdapterDataObserver、Choreographer 回调。
- 责任链:View 事件分发(dispatchTouchEvent 链)、OkHttp 拦截器链(application interceptors → RetryAndFollowUp → Bridge → Cache → Connect → CallServer)。
- 工厂/抽象工厂:LayoutInflater(createView 按 tag 反射创建 View)、BitmapFactory、ValueAnimator.ofXxx。
- 适配器:RecyclerView.Adapter、ListAdapter(DiffUtil)。
- 代理:Retrofit 的动态代理(Proxy.newProxyInstance 生成接口实现)、Binder(BinderProxy/BpBinder 代理模式跨进程)、AOP。
- 策略:RecyclerView.LayoutManager、图片加载的 DiskCache 策略;插值器 TimeInterpolator。
- 享元:Message.obtain(消息池复用)、String 常量池、OkHttp 连接池/线程池、Glide BitmapPool。
- 组合:ViewGroup-View 树(统一对待单个与组合对象,draw/dispatchDraw)。
52. 依赖注入是什么?Dagger2 / Hilt 的原理?
- DI:对象不自己 new 依赖,而由外部容器创建并传入(构造器注入 / 字段注入 / 方法注入)。好处:解耦、实现可替换(单测注入 fake)、生命周期与作用域统一管理。
- Dagger2:编译期注解处理器(apt/kapt/ksp)生成代码,全程零反射;核心是 @Component(依赖图的桥梁与注入入口)、@Module + @Provides(无法加构造器的类,如三方库)、@Inject 构造器(告知 Dagger 如何创建)、@Qualifier(区分同类型实例)、@Scope(@Singleton 双检锁单例;自定义 Scope 绑定组件生命周期,组件销毁则作用域内对象销毁)。
- 生成原理:为每个 Component 生成 DaggerXxxComponent 类,内部以 Provider(javax.inject,惰性创建)串联依赖工厂;依赖图在编译期校验------缺失绑定、循环依赖直接编译报错;字段注入生成 inject(T) 方法直接赋值,无运行时反射(对比 Guice/Spring 的运行时反射+扫描)。
- Hilt:Dagger2 在 Android 上的标准化封装:@HiltAndroidApp 生成 Hilt_Application 基类作为注入入口;@AndroidEntryPoint 标注 Activity/Fragment/View/Service 等,内部生成 Hilt_Xxx 基类完成注入;预定义组件树 SingletonComponent → ActivityRetainedComponent → ActivityComponent → FragmentComponent → ViewComponent,层级即作用域;@HiltViewModel 让 ViewModel 经 ViewModelComponent 注入(自动提供 SavedStateHandle 与 CoroutineScope)。
- 一句话:Dagger 解决"对象图如何创建",Hilt 解决"注入与 Android 生命周期组件的模板化绑定"。
六、网络与存储
53. HTTP 与 HTTPS 区别?HTTPS 加密过程?
- HTTP 明文 80;HTTPS = HTTP over TLS(443),提供机密性、完整性、身份认证。
- TLS 握手(简化):客户端 ClientHello(随机数 + 支持的加密套件/版本)→ 服务端 ServerHello(选定套件 + 证书 + 随机数 + 公钥)→ 客户端校验证书链(CA 信任链、有效期、域名、CRL/OCSP)→ 客户端生成 pre-master(随机数),用服务端公钥加密发送 → 双方用三个随机数计算 session key → 之后对称加密通信(AES-GCM/ChaCha20),消息带 HMAC 防篡改。
- TLS 1.3(主流):0-RTT 恢复、仅 5 个强套件、废除 RSA 密钥交换(向前安全性 PFS,全部 ECDHE);OkHttp 默认支持;Android 10+ 默认 TLS1.3。
- 客户端证书 pinning:OkHttp CertificatePinner 防中间人(配合网络库)。
54. TCP vs UDP?三次握手、四次挥手?
- TCP:面向连接、可靠(序号/确认/重传/流量控制/拥塞控制)、有序、字节流;开销大(头部 20B+)。UDP:无连接、尽力交付、头部 8B;适合直播、游戏、DNS、QUIC 的底子。
- 三次握手:SYN → SYN+ACK → ACK。为什么不是两次:防止失效的连接请求突然又传到服务端造成资源浪费;确认双方收发能力;同步初始序列号。
- 四次挥手(主动关闭方):FIN → ACK → (被动方数据发完)FIN → ACK;主动方进入 TIME_WAIT(2MSL)确保最后一个 ACK 能重传且让旧报文消亡。为什么四次:TCP 半关闭,被动方收到 FIN 后可能还有数据要发,所以 ACK 与 FIN 分开发。
55. OkHttp 拦截器机制?缓存策略?
- Interceptor 链:RealInterceptorChain.proceed 递归调用,每个拦截器可处理 request/response 前后逻辑。应用层拦截器(addInterceptor)一次性看到原始请求;网络拦截器(addNetworkInterceptor)能看到重定向/重试中间过程。
- 内置链:RetryAndFollowUpInterceptor(失败重试、重定向)→ BridgeInterceptor(补 Host/Connection/Gzip 头、响应解压)→ CacheInterceptor(缓存策略核心)→ ConnectInterceptor(建立连接:连接池 Route 选择、TLS 握手、协议协商 HTTP/1.1 或 h2)→ CallServerInterceptor(写请求行/头/体,读响应)。
- 缓存:CacheInterceptor 根据 CacheStrategy:响应带 Cache-Control/Expires/ETag/Last-Modified;请求 Cache-Control: max-age 命中强缓存直接返回;max-stale/max-age 协商;只有 GET 可缓存(默认 OkHttp 实现);ETag/If-None-Match、Last-Modified/If-Modified-Since 走 304 协商缓存;服务端不返回缓存头时可用 FORCE_CACHE/FORCE_NETWORK 控制。
- 连接池:ConnectionPool(默认 5 空闲连接、5 分钟保活),HTTP/2 多路复用同一连接承载多流。
56. Retrofit 原理?
- 运行时动态代理:Retrofit.create(Class) 用 Proxy.newProxyInstance 生成接口实现,invoke 中解析方法注解(@GET/@POST/@Query/@Body/@Path/@Headers/@Multipart)与参数注解 → ServiceMethod 解析(注解 + 参数适配器,缓存到 serviceMethodCache)→ 构建 OkHttp Call(RequestFactory 造 Request)→ 通过 CallAdapter 适配返回类型(Call/Observable/Flowable/suspend 挂起函数------suspend 由 KotlinSuspendCallAdapter 适配为 Continuation)→ Converter(Gson/Moshi/kotlinx.serialization)反序列化。
- 一句话:接口 + 注解描述请求,动态代理生成实现,底层 OkHttp 执行,Converter 做序列化。
57. Glide / Picasso 缓存机制?Glide 缓存策略?
- Picasso:内存缓存(LRU,全尺寸 Bitmap)+ 磁盘缓存(HTTP 缓存协议);下载用 OkHttp。
- Glide 三级缓存:内存 → 活动资源(ActiveResources,弱引用 map,正在使用的图片)→ 内存 LruCache → DiskCache。
- Key 生成:EngineKey(model、签名、宽高、resourceClass、transformation 等)------同一张图按不同尺寸请求会缓存两份,可覆盖 diskCacheStrategy。
- DiskCacheStrategy:RESOURCE(默认,转换后资源)、DATA(原始全尺寸)、ALL、AUTOMATIC(API 30+ 默认,远程 all/本地 none)、NONE;skipMemoryCache。
- 生命周期:with(Activity/Fragment) 绑定页面,自动取消请求、暂停滑动加载(RequestManager Fragment 无 UI 纯生命周期)。
- BitmapPool:LRU 复用解码内存(inBitmap),减少 GC;还有 Target/ViewTarget 尺寸裁剪、RGB_565 降低质量、Thumbnail、transform(centerCrop 等缓存 key 含变换)。
- 面试追问:为什么 Glide 解码比手动省内存?------自动按 Target 尺寸 inSampleSize + BitmapPool 复用 + 双缓存 key。
58. SharedPreferences 优缺点?apply vs commit?MMKV 为什么快?
- SP 原理:键值对 XML 文件,全量加载内存(首次 getSharedPreferences 触发 IO 读整个文件到 Map);写入 commit/apply 先改内存再写文件。问题:加载在 UI 线程(多进程/大文件首读卡顿)、全量写入(一个 key 改动写整个文件)、多进程不可靠(MODE_MULTI_PROCESS 已废弃,无锁)、getXXX 可能阻塞。
- commit:同步写盘(直接落 fs),返回 boolean;apply:内存立即生效,异步 queue 到 QueuedWork 线程写盘,无返回值;崩溃可能丢失 apply 的未落盘修改;apply 会阻塞 onPause?------QueuedWork 等待写盘机制在部分版本导致 ActivityThread handlePauseActivity 等待(Android 8 引入 tryFinishSignals 有优化,Android 11+ 行为持续改善)。
- DataStore:协程 + Flow + 事务写,替代 SP。
- MMKV 快的原因:① mmap 内存映射(用户空间映射文件页,写入即 memcpy,由内核异步刷盘,无系统调用切换、无 read/write 两次拷贝);② 增量更新、protobuf 编码(每个 key 带长度前缀,支持原地修改,不像 XML 全量重写);③ 多进程用文件锁 + CRC 校验。对比 SP:XML 全量解析 + 全量回写。
59. Room 的使用?与 SQLite 关系?
- Room = SQLite 之上的 ORM 抽象层(SQLCipher 可加密),三层:Entity(表,@Entity)、DAO(@Dao 接口/抽象类,@Query/@Insert/@Update/@Delete/@Transaction)、Database(@Database 版本 + entities + exportSchema)。
- 特点:编译期 SQL 校验(注解处理器生成实现,SQL 写错编译报错)、返回类型丰富(LiveData/Flow/PagingSource 自动响应式)、迁移机制(Migration(SQL) 或 destructive fallback)、@Transaction 保证多表操作原子性、关系查询 @Embedded/@Relation、协程 suspend 支持(Room 内部用 TransactionExecutor 线程池)。
- 原理:生成 _Impl 类,把 DAO 方法映射为 SQLite 语句;观察查询通过 InvalidationTracker(触发器 + 表变更通知)驱动 Flow/LiveData 重查。
- 与 SQLite 关系:底层仍是 android.database.sqlite(SQLiteDatabase),Room 只是生成样板代码与线程调度。
七、高级与新技术
60. 热修复原理?
- 类替换方案(Instant Run 系列):修改后类编译为 dex → 下发 → 反射注入到 ClassLoader 数组前面(PathClassLoader 的 dexElements 头插)→ 类加载时先找到新类。Dexposed/AndFix(方法替换,native hook,兼容差)、Nuwa/QZone(插桩规避类校验旧方案,需防止类预校验)。
- Tinker:基于 dexdiff 差分下发全量合成 dex,加载新 dex 作为新 ClassLoader 的 path,所有类隔离重加载(Application 及已加载类不能修),配合 so 库替换与资源替换(AssetManager addAssetPath);无需在加载前后规避校验,稳定性高;缺点:冷启动生效、补丁包大。
- 原理根因:类一旦被加载进虚拟机不可卸载(DexClassLoader 路径先查先得),所以要么新 ClassLoader 全量换(Tinker),要么同 ClassLoader 元素头插(AndFix 类插桩)。
- 注意:Android 14 收紧对动态代码加载的要求(动态加载 dex 需只读文件,见 targetSdk 34 行为变更),热修复需要适配文件只读化。
61. 插件化原理?Hook 点在哪?
- 核心四件事:
- Hook AMS/ATMS:拦截 startActivity 调用,把插件中未注册 Activity 的 Intent 换成宿主中已注册的占坑 Activity(StubActivity)骗过系统校验;在 handleLaunchActivity / 生命周期回调处再换回真实插件 Activity(ActivityThread.mH Handler.Callback 拦截、Instrumentation 替换、或 Android 8+ 用 ActivityLifecycleCallbacks 配合 Instrumentation)。
- 类加载:插件 dex 用 DexClassLoader 加载;插件与宿主类互相可见问题------合并 dexElements 到宿主 PathClassLoader(组件类问题少),或双亲委派破窗(插件优先)+ 公共资源隔离。
- 资源加载:新建 AssetManager,addAssetPath 插件 apk,用反射生成插件 Resources/Theme(包名冲突用 public.xml 固定 id 方案)。
- 四大组件模拟:Service 用代理分发(bind/start 拦截后转给宿主代理 Service 转发到插件 Service);ContentProvider 代理表分发。
- 代表框架:VirtualAPK(占坑 + Hook AMS)、DroidPlugin(完全占坑,系统代理式)、RePlugin(独立进程插件管理)。
- 现状:Android 8/10/12 对系统隐藏 API 与非 SDK 接口反射限制(黑名单/灰名单)使大量 Hook 失效,插件化式微;模块化和热修复(Tinker)承接了大部分需求。
62. 组件化/模块化落地?ARouter 路由原理?
- 拆分:按业务 feature module(独立 application 壳可单独编译运行)+ 基础组件库(common/base、network、image、db)+ 路由桥接层;依赖单向:app → 业务组件 → 基础组件;业务组件之间不直接依赖(通过路由 + 接口下沉到 common)。
- 路由解耦:页面跳转路由(ARouter)、服务发现(接口 + 实现注册,SPI 思想)。
- ARouter 原理:注解处理器(annotationProcessor/kapt/ksp)编译期为 @Route(path) 生成 _ARouterGroupGroupGroupxxx 类(路径 → Class 映射)和 IProvider 实现注册类;运行时首次加载按 group 反射分组加载路由表到 Warehouse(Warehouse.groupsIndex);navigation 时按 path 找 postcard,构建意图跳转或 provider 实例(拦截器链:InterceptorProcessor 可全局降级、登录拦截等)。
- 依赖注入:@Autowired 按 key 自动传参解析(也靠编译期生成)。
- 隔离关键:模块间只暴露接口(在 common 定义)+ 路由地址常量;组件独立编译用 sourceSet 切换 application/library 插件。
63. Gradle 构建流程?如何加速编译?
- 构建流程:初始化(setting.gradle 确定模块)→ 配置(评估所有 build.gradle,生成 TaskGraph)→ 执行(Task 依赖图执行:AAPT2 处理资源 → javac/kotlinc 编译 → dex(D8/R8)→ 打包 zipalign/sign)→ 输出 APK/AAB。Transform API(已废弃,AGP 8 移除)→ Artifacts API / Instrumentation。
- 加速:
- 配置:daemon、parallel build(org.gradle.parallel=true)、configuration on demand、增加堆内存、缓存(build-cache)。
- 增量/按需:kotlin incremental annotation processing(kapt.incremental.apt)、ksp 替代 kapt、避免不必要 transform、flavorDimensions 减少变体(productFlavors × buildTypes 组合爆炸)、资源 shrinkDebugResources 关闭 debug 混淆。
- 工具升级:AGP/Kotlin 新版编译器优化、Configuration Cache(复用配置阶段结果)、Build Analyzer 定位瓶颈 Task、非传递 R 类(non-transitive R classes)减少资源查找。
- CI:远程构建缓存(Gradle Enterprise)、构建扫描。
- ABI/密度分包减少处理资源量;AAR 中拆 so 按 variant 过滤。
64. Kotlin Flow vs LiveData?StateFlow vs SharedFlow?
- LiveData:主线程内建、生命周期感知、粘性、无背压(setValue 丢中间值)、仅 Android 生态;Flow:协程构建的冷流(collect 才执行)、操作符丰富(map/filter/debounce/combine/flatMapLatest、背压、flowOn 调度、retry)、跨平台。
- 对比:LiveData 是"可观察数据容器",Flow 是"异步数据流计算管道";新代码推荐 Flow(lifecycle-livedata-ktx 提供了 Flow.asLiveData 桥)。
- StateFlow(热流):总有当前值(value),新订阅者立即收到当前值(粘性、类似 BehaviorSubject);适合 UI 状态(UiState);conflate 策略(仅保留最新)。
- SharedFlow(热流):可配置 replay(重放缓存数)与 buffer/溢出策略(SUSPEND/DROP_OLDEST/DROP_LATEST);适合一次性事件(Snackbar、导航);MutableSharedFlow 无初始值。
- 使用:ViewModel 暴露 StateFlow;事件流 SharedFlow(replay=0);UI 层 repeatOnLifecycle(STARTED) + collectLatest 安全收集(防后台时更新 UI + 重组时自动重启)。
65. Compose 中重组(Recomposition)是什么?如何避免不必要重组?
- 定义:@Composable 函数在输入(State)变化时重新执行以刷新 UI;Compose 运行时基于 slot table 做记忆化,跳过参数未变的函数(skippable,需参数类型稳定 @Stable/@Immutable)。
- 避免手段:
- remember/rememberSaveable 缓存计算与状态;
- 状态就近存放 + 状态上提(single source of truth);
- 参数传稳定类型(数据类用 @Immutable/List 用 kotlinx immutable 或 SnapshotStateList);
- derivedStateOf 把高频变化降为低频状态;
- key() 控制列表项重组边界;
- 副作用 API 管理非 UI 操作(LaunchedEffect/SideEffect/DisposableEffect/produceState),避免在组合中写副作用;
- Layout Inspector + Compose Compiler Metrics 检查重组范围。
- 进阶:强跳过模式(strong skipping,Compose Compiler 1.5+/2.0 默认开启,参数全稳定才可跳过);@Composable 内读状态的位置决定重组范围(状态读取下沉)。
66. Flutter 与 Compose 区别?跨平台方案对比?
- Compose:Kotlin 声明式 UI,仅 Android(Desktop/Web 在 JetBrains 生态 Compose Multiplatform 有实现);与 Android 系统 API/生命周期深度集成。
- Flutter:Dart + 自渲染引擎(Impeller/Skia),iOS/Android/Web/桌面一套代码;与平台无关的绘制(不转原生控件),一致性高;Dart 语言 + 生态与 Kotlin 不同。
- 对比维度:UI 一致性(Flutter 自绘最强/Compose 仅 Android)、性能(Flutter 自绘无桥接(平台通道除外)/Compose 直接调 View 层,两者都接近原生)、生态/招聘(Kotlin 工程师池远大于 Dart)、包体积(Flutter 引擎大)、混合栈(Flutter 引擎嵌入成本高;Compose 可与 View 互操作)。
- 其他跨平台:React Native(JS + Yoga 布局 + 原生控件桥接,新架构 JSI 去桥)、KMP(Kotlin Multiplatform:共享逻辑层 + 各自 UI------Compose Multiplatform 也在推进共享 UI)、uni-app/Taro(小程序生态)。
- 选型:Android 团队重 UI 一致性 + 已有 Kotlin 资产 → Compose/KMP;小团队全端覆盖 → Flutter/RN。
八、Android 16 / 17(API 36 / 37)新变化速览(面试加分项)
- Android 16(Baklava, API 36,2025-06):通知冷却(Notification Cooldown)、自适应刷新率暴露、照片选择器强化、Health Connect 更新、前台服务类型更严格(dataSync 超时限制)、预测性返回默认启用。
- Android 17(Cinnamon Bun, API 37,2026-06,AOSP 同步开源) :
- 强制大屏自适应:targetSdk 37 应用必须支持大屏 resize(resizableActivity 默认 true、禁止锁定 orientation 的负面清单),桌面窗口化/平板气泡栏(Bubble Bar)成为系统级能力。
- AppFunctions:应用可声明自身能力为"工具"供端上助手(Gemini)发现调用------AI Agent 时代的关键 API。
- 锁屏安全收紧:PIN/密码猜测次数大幅缩减(1 分钟内仅 6 次,20 次后永久锁定)。
- 动态信号监控(Dynamic Signal Monitoring):系统实时检测滥用行为(图标隐藏、无障碍滥用、后台启动),规则云端动态下发。
- 本地网络权限:访问局域网设备需单独声明 NEARBY_WIFI_DEVICES 类新权限模型收紧。
- 动态代码加载(DCL)文件只读:targetSdk 34+ 起动态加载的 dex/so 必须来自只读文件,插件化/热修复框架需适配。
- ART 代际 GC 等性能改进通过 Google Play 系统更新(Project Mainline)下放到 Android 12+ 设备。
- Compose-first 趋势:官方持续引导新 UI 全部 Compose 开发;Android Studio 对 Compose 预览/重构工具链持续投入。
- 面试话术建议:"面试造火箭"层面可提"Android 17 起 targetSdk 37 强制大屏自适应 + AppFunctions 开放端侧 AI 调用,应用架构上应尽早用 Compose 自适应布局 + 声明式导航"。
附:高频追问链路参考答案骨架
Handler 链路
MessageQueue.next() 无消息时 → nativePollOnce(ptr, timeout) → Linux epoll_wait 挂起(不耗 CPU);消息入队 nativeWake 写 eventfd 唤醒 → loop() 取出 dispatchMessage。主线程"不卡死"本质:主线程Looper 的 MessageQueue 用 epoll 阻塞等待,无消息时让出 CPU。
事件分发链路
Activity.dispatchTouchEvent → DecorView(superDispatchTouchEvent) → ViewGroup 链:onInterceptTouchEvent(DOWN 时若子 View 要处理则 requestDisallowIntercept 管理)→ 子 View dispatchTouchEvent → onTouch(Listener)→ onTouchEvent → onClick(UP)。结果沿 mFirstTouchTarget 反向回溯。
View 绘制链路
invalidate → 标记 PFLAG_DIRTY → scheduleTraversals → Choreographer.postCallback vsync 对齐 → performTraversals → measure(自上而下,MeasureSpec 约束)→ layout(位置确定)→ draw(DisplayList/RenderNode 录制,硬件加速下提交 GPU 渲染,Skia/Vulkan/GL)。
Binder 链路
AIDL 接口方法 → BinderProxy.transact → Binder 驱动(binder.c:copy_from_user 一次拷贝 + 目标线程唤醒)→ 服务端 Binder 线程 onTransact → 结果经驱动回写 → 客户端线程从 transact 返回。DeathRecipient 由驱动在 binder_node 主进程死亡时发 BR_DEAD_BINDER。
文档整理时间:2026-09 · 基于 Android 17 (API 37) AOSP 源码。