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主线程机制。面试官通常关注线程池参数配置、死锁排查及协程原理。

线程池实战
线程安全
Kotlin协程

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及组件化架构。高级工程师需具备拆解业务、降低耦合、提升复用率的能力。

阶段一:单体架构
Monolith App

所有模块耦合在一个APK中。优点:开发简单,调试方便。缺点:代码耦合严重,编译速度慢,无法独立迭代,团队协同困难。

阶段二:模块化
Modularization

按功能拆分模块(如:user, home, cart)。模块间通过接口通信,避免直接依赖。优点:代码隔离,可独立编译测试。缺点:模块间耦合仍需管理,路由设计复杂。

阶段三:组件化
Componentization

模块可独立运行(Debug模式),也可组装运行(Release模式)。使用ARouter进行页面跳转和数据传递。优点:高内聚低耦合,支持多套UI(如国内/海外版),提升编译速度。

阶段四:插件化/动态化
Plugin/Dynamic

通过动态下发代码实现功能更新,无需发版。技术难点:类加载机制(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)。