JVM原理与内存管理
在Android领域,虽然主要运行在Dalvik/ART虚拟机上,但对JVM原理的理解是评估开发者底层功底的重要标准。高级面试常考察类加载机制、GC算法及内存泄漏排查。
① 类加载机制
- 加载 (Loading):通过类的全限定名获取二进制字节流。
- 验证 (Verification):确保字节流符合当前虚拟机规范。
- 准备 (Preparation):为类变量分配内存并设置默认初始值。
- 解析 (Resolution):将符号引用替换为直接引用。
- 初始化 (Initialization):执行类构造器<clinit>()方法。
② GC算法对比
- 标记-清除 (Mark-Sweep):产生内存碎片。
- 标记-复制 (Mark-Copy):适合年轻代,无碎片,空间利用率50%。
- 标记-整理 (Mark-Compact):适合老年代,无碎片,移动对象成本高。
- 分代收集:结合上述算法,年轻代用复制,老年代用整理。
ART与Dalvik的区别
| 特性 | Dalvik | ART (Android Runtime) |
|---|---|---|
| 执行方式 | JIT (Just-In-Time) | AOT (Ahead-Of-Time) / JIT混合 |
| 安装时优化 | 否 | 是 (dex2oat) |
| 启动速度 | 较慢 | 较快 |
| 运行速度 | 一般 | 更快,预编译代码 |
| 存储空间 | 较小 | 较大 (预编译占用空间) |
多线程与并发编程
高级开发必须精通Java并发包(JUC)及Android主线程机制。面试官通常关注线程池参数配置、死锁排查及协程原理。
ThreadPoolExecutor 核心参数解析
面试高频考点:如何合理配置线程池?
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程空闲存活时间
TimeUnit unit, // 时间单位
BlockingQueue workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
- ⚡ 核心线程数:CPU密集型设为N+1,IO密集型设为2N+1。
- ⚡ 任务队列:ArrayBlockingQueue(有界), LinkedBlockingQueue(无界,慎用), SynchronousQueue(直接移交)。
- ⚡ 拒绝策略:AbortPolicy(抛异常), CallerRunsPolicy(调用者运行), DiscardPolicy(丢弃), DiscardOldestPolicy(丢弃最老)。
常见线程安全工具类
- 【ReentrantLock】:可重入锁,支持公平锁,配合Condition实现精确唤醒。
- 【ReadWriteLock】:读写锁,读共享写独占,适合读多写少场景。
- 【ConcurrentHashMap】:JDK7分段锁,JDK8 CAS+synchronized,保证线程安全且高性能。
- 【AtomicInteger】:基于CAS无锁编程,避免上下文切换开销。
- 【ThreadLocal】:线程封闭,解决线程安全问题,但需注意内存泄漏(使用弱引用)。
Kotlin协程原理
协程是轻量级线程,由编译器实现状态机。
- ⚡ 挂起 (Suspend):非阻塞挂起,释放CPU资源,恢复时从断点继续。
- ⚡ Dispatcher:
- IO:线程池大小动态调整(64N)。
- Main:主线程,用于UI更新。
- Default:CPU密集型,固定线程数(N+1)。
- ⚡ Structured Concurrency:结构化并发,父协程取消子协程,子协程异常影响父协程,便于资源管理。
架构设计与组件化
从MVC到MVP再到MVVM,最终演进到MVI及组件化架构。高级工程师需具备拆解业务、降低耦合、提升复用率的能力。
所有模块耦合在一个APK中。优点:开发简单,调试方便。缺点:代码耦合严重,编译速度慢,无法独立迭代,团队协同困难。
按功能拆分模块(如:user, home, cart)。模块间通过接口通信,避免直接依赖。优点:代码隔离,可独立编译测试。缺点:模块间耦合仍需管理,路由设计复杂。
模块可独立运行(Debug模式),也可组装运行(Release模式)。使用ARouter进行页面跳转和数据传递。优点:高内聚低耦合,支持多套UI(如国内/海外版),提升编译速度。
通过动态下发代码实现功能更新,无需发版。技术难点:类加载机制(DexClassLoader)、资源加载、兼容性问题。代表方案:VirtualAPK, Tinker。
路由设计核心
ARouter 工作原理
- 【编译期】:通过注解处理器(Annotation Processor)扫描@Route注解,生成路由映射表(InterceptRoute)。将路径、类名、权重等信息写入JSON或Java文件。
- 【运行期】:ARouter初始化时加载映射表。post()方法根据路径查找对应Class,通过反射实例化Activity或Fragment。
- 【拦截器】:支持登录检查、埋点统计等拦截逻辑,链式调用。
性能优化实战
性能优化是高级工程师的核心竞争力。涵盖启动速度、UI流畅度、内存泄漏、包体积及网络优化。
启动优化
- ⚡ 测量:使用Systrace, Perfetto, TraceView。
- ⚡ 策略:
- 延迟初始化:非关键逻辑延后到主线程空闲时执行。
- 多线程初始化:将初始化任务分发到子线程。
- Application瘦身:减少Application onCreate()中的逻辑。
- 静态变量优化:避免在静态块中执行耗时操作。
UI流畅度
- ⚡ 测量:Choreographer, Layout Inspector。
- ⚡ 策略:
- 布局优化:使用ConstraintLayout,减少层级,使用include/merge。
- 绘制优化:避免过度绘制,使用硬件加速。
- 列表优化:RecyclerView复用,DiffUtil计算差异。
- 动画优化:避免在Animation中执行耗时操作。
内存优化
- ⚡ 泄漏排查:LeakCanary, MAT, Android Studio Profiler。
- ⚡ 策略:
- Context使用:避免Activity Context长期持有。
- 监听器注销: onDestroy()中移除监听。
- 弱引用:Handler, Bitmap使用弱引用或软引用。
- 图片压缩:使用WebP,按需加载,LruCache。
高频问答深度解析
Q1: 什么是AIDL?它与Binder机制的关系?
A:AIDL (Android Interface Definition Language) 是Android定义的接口定义语言,用于生成跨进程通信(IPC)的代码。Binder是Android中一种跨进程通信机制,是AIDL的底层实现。AIDL简化了Binder的开发复杂度,开发者只需编写接口文件,编译器自动生成Stub类。Binder基于C/S架构,服务端实现Stub,客户端通过Proxy调用,数据通过Parcel序列化。
Q2: Handler内存泄漏的原因及解决方案?
A: 原因:Handler持有外部Activity的引用,Message在消息队列中等待处理时,会持有Handler的引用,进而持有Activity,导致Activity无法回收。 解决方案: 1. 将Handler声明为静态内部类,持有Activity的弱引用(WeakReference)。 2. 在Activity的onDestroy()中移除回调:handler.removeCallbacksAndMessages(null)。 3. 使用LifecycleOwner,在生命周期结束时自动清理。
Q3: 如何设计一个高可用的崩溃监控系统?
A: 1. 数据采集:重写UncaughtExceptionHandler,捕获非预期异常;使用try-catch捕获预期异常;监控ANR(通过TraceFile);监控内存泄漏(Crashlytics/LeakCanary集成)。 2. 数据上报:异步上报,避免阻塞主线程;批量上报,减少网络请求;断点续传,确保数据不丢失。 3. 数据处理:堆栈混淆还原;异常去重;用户行为记录;实时报警。 4. 技术选型:自建(成本高)或第三方(Bugly, Firebase Crashlytics)。