Android AIDL 与 Kotlin
实习生面试题笔记

完整题目 · 参考答案 · 个人表现点评 · 复习指南

共 21 题 五大板块 实习生标准 模拟面试

目录导航(点击跳转)

一、Android 基础与 Service

1 Activity 生命周期(启动新 Activity)
问题:从 Activity A 启动 Activity B(标准模式,非透明),生命周期顺序。
参考答案:A.onPause()B.onCreate()B.onStart()B.onResume()A.onStop()
追问:若 B 为半透明,A.onStop()不会调用。
你的回答:正确回答正确。
2 Service 启动方式与停止条件
问题:两种启动方式区别?既 start 又 bind 如何停止?
参考答案:
  • startService:独立生命周期,需 stopService/stopSelf停止。
  • bindService:随绑定组件存活,所有客户端解绑后自动销毁。
  • 同时 start + bind:必须同时调用 stopService和所有 unbindService,顺序不限。
你的回答:正确补充了"先 stop 再 unbind"顺序可互换。
3 Service 的 onUnbind 与 onDestroy 区别
问题:onUnbindonDestroy何时调用?哪个更好?
参考答案:
  • onUnbind:最后一个客户端解绑时调用(无论服务是否还会运行),可返回 true以支持 onRebind
  • onDestroy:服务正常销毁时调用(包括解绑后无 start 标记、或 stopService)。
  • 进程被强杀时两者都不调用。不能依赖它们保存关键数据,应在数据变更时即时持久化。
你的回答:正确正确指出 onUnbind 仅用于主动解绑后的逻辑,onDestroy 更通用但仍有局限。
4 ServiceConnection 回调与进程死亡
问题:服务端进程被杀,客户端哪个方法被调用?如何处理?
参考答案:onServiceDisconnected被调用。应置空服务引用,并可尝试重连。
你的回答:基本正确("onDIsconnection"拼写小误,但意思正确)。

二、AIDL 跨进程通信

5 AIDL 与 Messenger 对比
问题:AIDL 是什么?与 Messenger 相比优势?
参考答案:AIDL 基于 Binder,支持多线程并发 RPC,可传递复杂数据类型。Messenger 串行处理,适合简单消息传递。
你的回答:部分正确缺少对并发和复杂数据的说明。
6 bindService 到 onServiceConnected 完整流程
问题:描述从 bindService 到 onServiceConnected 的过程。
参考答案:
  1. bindService→ AMS(系统进程)。
  2. AMS 创建/找到 Service 进程,调用 onCreateonBind
  3. AMS 将 onBind返回的 IBinder 包装后回传给客户端。
  4. 客户端通过 LoadedApk.ServiceDispatcher调用 ServiceConnection.onServiceConnected
你的回答:不知道(这是短板,需补)。
点评:这是 AIDL 核心流程,建议阅读 ContextImpl.bindServiceCommon源码加深理解。
扩展知识:
  • Binder 通信机制:客户端与服务端通过 Binder 驱动进行跨进程通信,内核维护 Binder 线程池。
  • IBinder 生命周期:服务端返回的 IBinder 在客户端通过 linkToDeath监听死亡状态。
  • Binder 死亡检测:服务进程崩溃时,客户端会收到 onServiceDisconnected回调。
  • 源码路径:frameworks/base/core/java/android/app/ContextImpl.java中的 bindServiceCommon方法。
7 Stub.asInterface 原理
问题:Stub.asInterface(IBinder)如何区分同/跨进程?
参考答案:调用 IBinder.queryLocalInterface,本地 Binder 返回自身 → 直接返回 Stub;跨进程返回 null→ 创建 Proxy 代理。
你的回答:正确
扩展知识:
  • 核心代码逻辑:
    public static IMyService asInterface(IBinder obj) {
        if (obj == null) return null;
        IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (iin != null && iin instanceof IMyService) {
            return (IMyService) iin;  // 同进程:直接返回
        }
        return new MyService.Stub.Proxy(obj);  // 跨进程:创建代理
    }
  • Proxy 工作原理:跨进程时,Proxy 将方法调用打包成 Parcel,通过 Binder 发送到服务端,等待响应后解析返回值。
  • Binder 标识:DESCRIPTOR是接口的唯一标识字符串,用于验证 Binder 对象是否匹配。
8 AIDL 线程模型与 ANR
问题:UI 线程调用远程耗时方法会怎样?如何解决?回调运行在哪个线程?
参考答案:会卡死 UI,导致 ANR。应切到后台线程(协程/线程池)。回调运行在客户端的 Binder 线程池,不能直接操作 UI,需 runOnUiThread
你的回答:正确
9 大 Bitmap 传输问题与优化
问题:传递超过 1MB 的 Bitmap 会怎样?如何优化?
参考答案:
  • 抛出 TransactionTooLargeException(Binder 缓冲区 ~1MB)。
  • 优化:保存到文件传递 URI、使用 SharedMemory、降低分辨率。
  • 切片传递不可行(仍受单次 1MB 限制)。
你的回答:错误原因对,但优化方案"切片"错误。应改为文件/共享内存。
扩展知识:
  • Binder 缓冲区限制:默认大小为 1MB(Process.BINDER_VM_SIZE),可通过系统属性调整,但不建议依赖。
  • SharedMemory 方案:API 26+ 可用,通过 MemoryFileSharedMemory创建共享内存区域。
    // 创建共享内存
    val shm = SharedMemory.create("BitmapShm", bitmapSize)
    // 写入数据
    val buffer = shm.mapReadWrite()
    bitmap.copyPixelsToBuffer(buffer)
    // 传递文件描述符
    val fd = shm.fileDescriptor
  • FileProvider 方案:将 Bitmap 保存为文件,通过 FileProvider.getUriForFile()生成 Content URI 传递。
  • 压缩策略:使用 Bitmap.compress()降低质量或尺寸,推荐 WebP 格式(更高压缩率)。
10 RemoteCallbackList 原理
问题:RemoteCallbackList 如何自动清理死亡回调?
参考答案:内部使用 ArrayMap<IBinder, IInterface>,通过 IBinder.linkToDeath注册死亡通知,客户端死亡时自动移除。
你的回答:正确(线程安全、自动清理)。
扩展知识:
  • 核心机制:每个注册的回调都通过 linkToDeath绑定死亡监听,当客户端进程死亡时,Binder 驱动通知服务端,触发 DeathRecipient.binderDied()回调。
  • 遍历安全:提供 beginBroadcast()/ finishBroadcast()配对方法,保证遍历期间的线程安全。
    // 安全遍历回调
    final int count = callbackList.beginBroadcast();
    try {
        for (int i = 0; i < count; i++) {
            IMyCallback callback = callbackList.getBroadcastItem(i);
            if (callback != null) {
                callback.onEvent(data);
            }
        }
    } finally {
        callbackList.finishBroadcast();
    }
  • 对比 CopyOnWriteArrayList:普通集合无法处理跨进程场景,而 RemoteCallbackList 专门设计用于管理跨进程回调。
11 死锁场景(服务端同步块内调回调)
问题:服务端 synchronized方法内调用客户端回调,客户端回调中又调用服务端同步方法 → 死锁。如何避免?
参考答案:在调用回调前释放锁,或避免回调中反向调用同步方法,或使用 oneway回调。
你的回答:不知道(进阶知识,实习生可暂缓)。
12 oneway 关键字
问题:oneway 作用、限制、场景?
参考答案:
  • 异步调用,客户端不等待返回。
  • 限制:void 方法,不能抛异常,参数不能 out/inout。
  • 场景:日志上报、通知等无需结果的调用。
你的回答:不知道
扩展知识:
  • AIDL 声明示例:
    // IMyService.aidl
    interface IMyService {
        // 同步调用(默认)
        void syncMethod();
        
        // 异步调用
        oneway void asyncMethod(String data);
    }
  • 底层机制:oneway 方法通过 IBinder.FLAG_ONEWAY标记发送,Binder 驱动收到后立即返回,不等待服务端处理完成。
  • 注意事项:oneway 方法虽然异步,但服务端仍是串行处理(除非使用多线程),适合"fire and forget"场景。
  • 性能影响:减少客户端阻塞时间,提高响应性,但无法获取返回值或异常信息。
13 自定义 Parcelable 传递步骤
问题:自定义 Book 类作为 AIDL 参数需几步?
参考答案:
  1. 实现 Parcelable接口。
  2. 在 aidl 目录下创建 Book.aidl,内容 parcelable Book;(包名与 Java 类一致)。
  3. 在主 AIDL 中导入 import ...Book;
  4. 若不写 Book.aidl编译会报错。
你的回答:错误前半对,但认为"不写 .aidl 不会报错"是错误的。
14 AIDL 性能优化
问题:高频调用 AIDL 的优化手段?
参考答案:
  • 批量操作,减少跨进程次数。
  • 使用 oneway
  • 避免大数据传输。
  • 服务端方法轻量化,耗时操作异步化。
你的回答:部分正确提到"限频",思路较单一。

三、Java 基础(实习生要求)

15 final / finally / finalize 区别
问题:三者的区别?
参考答案:
  • final:修饰类/方法/变量(不可变)。
  • finally:异常处理必执行块。
  • finalize:GC 前回调(已废弃,JDK 9 标记为 @Deprecated)。
你的回答:不知道
扩展知识:
  • final 的三种用法:
    • final class:不可被继承(如 String
    • final method:不可被重写
    • final variable:引用不可变(但对象内容可变)
  • finally 的执行时机:无论 try 块是否抛出异常、是否有 return,finally 都会执行(除非调用 System.exit())。
  • finalize 的问题:执行时机不确定、可能导致对象复活、性能开销大,JDK 9 后推荐使用 CleanerAPI 或 try-with-resources。
  • 经典面试题:try 中有 return,finally 中也有 return,最终返回值是?答案:finally 的返回值会覆盖 try 的返回值。
16 ArrayList vs LinkedList
问题:区别与使用场景?
参考答案:
  • ArrayList:数组实现,随机访问快 O(1),增删慢 O(n)(除非尾部)。
  • LinkedList:双向链表,增删快 O(1)(两端),随机访问慢 O(n)。
你的回答:不知道
扩展知识:
  • 底层实现对比:
    操作 ArrayList LinkedList
    随机访问 O(1) O(n)
    头部插入/删除 O(n) O(1)
    尾部插入/删除 O(1)(扩容时O(n)) O(1)
    内存占用 连续空间,有预留 每个节点有前后指针
  • 使用场景建议:
    • 优先选 ArrayList:大多数场景(查询多、增删少)
    • LinkedList:频繁在两端操作(队列/栈场景)
  • ArrayList 扩容机制:默认初始容量 10,扩容时增长 1.5 倍(oldCapacity + (oldCapacity >>1)),通过 ensureCapacity()预分配可优化性能。

四、Kotlin 基础

17 val / var 区别
问题:val变量真的不可变吗?
参考答案:val引用不可变,但指向的对象内容可修改(若对象可变)。
你的回答:基本正确但"直接修改地址存的值"表述易误解,已澄清。
18 data class 自动生成方法
问题:data class 生成了哪些方法?
参考答案:equalshashCodetoStringcomponentNcopy不生成 get/set(Kotlin 属性自带)。
你的回答:错误(误以为生成 get/set)。
扩展知识:
  • data class 示例与生成的方法:
    data class User(val name: String, val age: Int)
    
    // 自动生成:
    // equals(other: Any?): Boolean
    // hashCode(): Int  
    // toString(): String → "User(name=John, age=30)"
    // component1(): String → name
    // component2(): Int → age
    // copy(name: String = this.name, age: Int = this.age): User
  • 解构声明:利用 componentN 方法实现解构:
    val user = User("John", 30)
    val (name, age) = user  // 解构
    println("$name is $age years old")
  • copy 方法用途:创建对象副本并修改部分属性:
    val updatedUser = user.copy(age = 31)  // name 不变,age 改为 31
  • 注意事项:data class 必须至少有一个主构造函数参数;参数默认是 val(不可变),如需可变使用 var;继承时需注意 equals/hashCode 的一致性。
19 空安全与安全调用
问题:val name: String? = null,调用 name.length会怎样?如何安全获取长度?
参考答案:编译报错,需使用 name?.lengthname?.length ?: 0
你的回答:正确,还给出了 .let{}写法。
20 lateinit vs by lazy
问题:区别与使用场景。
参考答案:
  • lateinit:可变属性,延迟赋值,适用于 DI 或非空变量晚些初始化(如 @Inject lateinit var)。
  • by lazy:只读属性,首次访问时初始化,适用于一次性昂贵计算。
你的回答:未回答(时间不足)。
扩展知识:
  • 对比表格:
    特性 lateinit by lazy
    可变性 var(可变) val(只读)
    初始化时机 任意时刻手动赋值 首次访问时自动初始化
    线程安全 需手动保证 默认线程安全(可配置)
    适用类型 非空引用类型 任意类型
  • lateinit 使用示例:
    class MyActivity : Activity() {
        lateinit var presenter: MyPresenter  // 延迟初始化
        
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            presenter = MyPresenter()  // 手动赋值
        }
    }
  • by lazy 使用示例:
    class MyClass {
        val expensiveData: String by lazy {
            // 首次访问时执行,结果缓存
            fetchDataFromNetwork()  // 耗时操作
        }
    }
  • lazy 线程模式:
    • LazyThreadSafetyMode.SYNCHRONIZED:默认,线程安全
    • LazyThreadSafetyMode.PUBLICATION:允许多线程同时初始化
    • LazyThreadSafetyMode.NONE:非线程安全,性能最好
21 协程 launch vs async
问题:区别?
参考答案:
  • launch:返回 Job,不携带结果,用于执行无需返回值的任务。
  • async:返回 Deferred,可通过 await获取结果,用于并发计算。
你的回答:未回答
扩展知识:
  • 对比表格:
    特性 launch async
    返回值 Job(无结果) Deferred<T>(有结果)
    获取结果 await()
    适用场景 执行副作用(IO、更新UI) 并发计算,合并结果
    惰性执行 立即执行 立即执行(可用 lazy)
  • launch 使用示例:
    // 启动协程执行副作用
    val job = lifecycleScope.launch {
        val data = fetchData()  // 挂起函数
        updateUI(data)          // 更新UI
    }
    
    // 取消协程
    job.cancel()
  • async 使用示例(并发计算):
    // 并发执行两个任务
    lifecycleScope.launch {
        val deferred1 = async { fetchData1() }
        val deferred2 = async { fetchData2() }
        
        // 等待两个任务完成并合并结果
        val result1 = deferred1.await()
        val result2 = deferred2.await()
        
        combineResults(result1, result2)
    }
  • 注意事项:
    • async默认立即执行,如需惰性执行使用 async(start = CoroutineStart.LAZY)
    • await()会挂起当前协程,直到结果返回
    • 使用 coroutineScopesupervisorScope管理并发子协程

五、总体评价(针对实习生面试)

优势

  • 对 AIDL 的基本用法(Stub/Proxy、线程问题、RemoteCallbackList)理解较好。
  • Service 生命周期基础扎实。
  • Kotlin 空安全掌握不错。

短板

  • 缺少 bindServiceonServiceConnected的系统流程知识(第6题)。
  • 部分优化方案错误(大 Bitmap 切片传递,第9题)。
  • Java 集合、final/finally/finalize基础缺失(第15-16题)。
  • Kotlin data class 自动生成方法混淆(第18题)。

复习建议

AIDL 部分可以拿到及格偏上,但 Java/Kotlin 基础薄弱会影响整体评分。
建议针对短板刷题 + 读源码(ContextImpl等),一个月后可再次模拟面试。

重点补课清单: ① 第6题 bindService 系统流程 ② 第9题大Bitmap优化方案修正 ③ Java基础:final/finally/finalize、集合框架 ④ Kotlin:data class、lateinit vs lazy、协程基础。