2026年面试1

41 阅读27分钟

一、星纪魅族

1、Android中创建线程:

在Android中创建线程主要有三种方式:

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口,实现run方法
  3. 使用Kotlin协程(Coroutines)——严格来说协程不是线程,但可以用于异步任务

方式一:继承Thread类

class MyThread : Thread() {
    override fun run() {
        // 执行耗时操作
    }
}

// 启动线程
MyThread().start()

方式二:实现Runnable接口

class MyRunnable : Runnable {
    override fun run() {
        // 执行耗时操作
    }
}

// 启动线程
val myRunnable = MyRunnable()
Thread(myRunnable).start()

方式三:使用Kotlin协程(需要添加依赖)

// 在ViewModel或Activity等中使用
viewModelScope.launch {
    // 在后台执行耗时操作
    val result = withContext(Dispatchers.IO) {
        // 执行耗时操作,比如网络请求
        return@withContext "结果"
    }
    // 回到主线程更新UI
    textView.text = result
}

注意事项:

  1. 一个线程只能启动一次,即start()方法只能调用一次,否则会抛出IllegalThreadStateException。
  2. 线程的执行顺序不一定与启动顺序一致,这取决于操作系统的调度。
  3. 在Android中,子线程不能直接更新UI,必须通过Handler等机制将更新操作切换到主线程。

2、java中创建线程过程:

// Thread.java 核心代码分析
public synchronized void start() {
    // 1. 检查线程状态
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    
    // 2. 将线程加入线程组
    group.add(this);
    
    // 3. 调用native方法创建底层线程
    boolean started = false;
    try {
        start0();  // Native方法调用
        started = true;
    } finally {
        // 4. 如果启动失败,从线程组移除
        if (!started) {
            group.threadStartFailed(this);
        }
    }
}

// Native方法(由JVM实现)
private native void start0();

3、Android中创建线程过程:

Android基于Linux内核,线程创建过程如下:

1. 应用层(Java/Kotlin)
   ↓
2. ART/Dalvik虚拟机(JNI调用)
   ↓
3. Bionic C库(pthread_create)
   ↓
4. Linux内核(clone系统调用)
   ↓
5. 内核创建task_struct结构体
   ↓
6. 返回线程ID给应用层
public class ThreadDemo {
    public static void main(String[] args) {
        // 完整创建过程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 线程执行体
                System.out.println("Thread running");
            }
        });
        
        // 设置线程属性
        thread.setName("MyThread");
        thread.setPriority(Thread.NORM_PRIORITY);
        
        // 启动线程(关键步骤)
        thread.start();
    }
}
NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATED

创建过程对应状态变化:
1. new Thread() → NEW状态
2. thread.start() → RUNNABLE状态(可运行)
3. 操作系统调度 → RUNNING状态(实际运行)
4. 执行完毕 → TERMINATED状态

4、使用Executors工厂类创建## (了解,但不推荐)

Executors类提供了几种创建线程池的静态方法:

// 1. 固定线程数量的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

// 2. 单个线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

// 3. 可缓存的线程池,线程数量动态变化
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

// 4. 定时任务线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);

但是,在Android中,由于设备资源有限,不推荐使用Executors创建线程池,因为:

  • FixedThreadPoolSingleThreadExecutor使用无界队列LinkedBlockingQueue,可能导致内存溢出。
  • CachedThreadPoolScheduledThreadPool允许创建无限数量的线程,也可能导致内存溢出。

5、使用ThreadPoolExecutor构造函数创建(推荐)

通过ThreadPoolExecutor构造函数,我们可以自定义线程池的各个参数,从而更好地控制线程池的行为。构造函数如下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
参数说明:
  • corePoolSize:核心线程数,即使线程空闲也不会被销毁,除非设置allowCoreThreadTimeOut
  • maximumPoolSize:最大线程数,当工作队列满了之后,线程池可以创建的最大线程数。
  • keepAliveTime:非核心线程空闲时的存活时间,超过这个时间就会被销毁。
  • unit:存活时间的单位。
  • workQueue:工作队列,用于保存等待执行的任务。
  • threadFactory:线程工厂,用于创建新线程,可以设置线程名称、优先级等。
  • handler:拒绝策略,当线程池无法执行新任务时的处理策略。
int corePoolSize = 2;
int maximumPoolSize = 5;
long keepAliveTime = 60L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
ThreadFactory threadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r, "MyThread #" + mCount.getAndIncrement());
        thread.setPriority(Thread.NORM_PRIORITY);
        return thread;
    }
};
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();

ExecutorService executor = new ThreadPoolExecutor(
    corePoolSize,
    maximumPoolSize,
    keepAliveTime,
    unit,
    workQueue,
    threadFactory,
    handler
);

6、线程池的工作流程

  1. 当提交一个新任务时,如果当前线程数小于核心线程数,则创建新线程执行任务。
  2. 如果当前线程数达到核心线程数,则将任务放入工作队列。
  3. 如果工作队列已满,且当前线程数小于最大线程数,则创建新线程执行任务。
  4. 如果当前线程数达到最大线程数,且工作队列已满,则执行拒绝策略。
注意事项
  1. 线程池的关闭:使用shutdown()shutdownNow()方法关闭线程池,否则可能导致内存泄漏。
  2. 线程池的监控:可以通过ThreadPoolExecutor提供的方法(如getActiveCount())监控线程池状态。
  3. 避免内存泄漏:在Activity或Fragment中使用线程池时,注意在组件销毁时取消任务,避免持有组件引用。
// 通用线程池配置模板
fun createThreadPool(
    name: String,
    coreSize: Int = 2,
    maxSize: Int = 8,
    queueSize: Int = 128
): ThreadPoolExecutor {
    return ThreadPoolExecutor(
        coreSize, maxSize, 30L, TimeUnit.SECONDS,
        LinkedBlockingQueue(queueSize),
        ThreadFactory { r ->
            Thread(r, "$name-${System.currentTimeMillis()}")
                .apply { priority = Thread.NORM_PRIORITY }
        },
        ThreadPoolExecutor.CallerRunsPolicy()
    ).apply {
        allowCoreThreadTimeOut(true)  // 允许回收核心线程
    }
}

7、ThreadPoolExecutor 的拒绝策略有多少种?

hreadPoolExecutor 中内置了四种拒绝策略,它们都是实现了RejectedExecutionHandler接口的内部类。此外,我们也可以自定义拒绝策略。

下面是四种内置拒绝策略的详细说明:

  1. AbortPolicy(默认策略) :当线程池无法处理新任务时,会抛出RejectedExecutionException异常。
  2. CallerRunsPolicy(调用者运行策略):当线程池无法处理新任务时,会由调用者所在的线程来执行该任务。
  3. DiscardPolicy(丢弃策略):当线程池无法处理新任务时,会直接丢弃该任务,不会有任何通知。
  4. DiscardOldestPolicy(丢弃最老策略):当线程池无法处理新任务时,会丢弃队列中最老的任务(即队列头部的任务),然后尝试重新提交当前任务。

8、handler 中view.postDelayed(Runnable { },100) 怎么做的延时的,总结一下

在Android中,View.postDelayed(Runnable, long) 实际上是通过Handler实现的延时执行。因为每个View都关联了一个Handler(具体来说是ViewRootImpl中的Handler),当我们调用View.postDelayed()时,它会将Runnable任务发送到关联的Handler的消息队列中,并设置延迟时间。

具体步骤总结:

  1. 获取Handler:View在attach到窗口时,会关联一个ViewRootImpl,而ViewRootImpl中有一个Handler(实际上是主线程的Handler,因为ViewRootImpl是在主线程创建的)。所以View.postDelayed()实际上是通过ViewRootImpl中的Handler来执行的。
  2. 发送延迟消息:当我们调用View.postDelayed(runnable, delayMillis)时,它会将这个runnable包装成一个Message(或者直接使用Handler的postDelayed方法),然后发送到Handler的消息队列中,并设置延迟时间。
  3. 消息队列处理:Handler所在线程(通常是主线程)的Looper会不断从消息队列中取出消息,当到达指定的延迟时间时,就会处理这个消息,执行我们传入的Runnable。
  4. 执行Runnable:当消息被处理时,会调用Runnable的run方法,从而执行我们传入的代码。

在View类中,postDelayed()方法最终会调用到:

public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    } else {
        // 如果View还没有attach到窗口,使用ViewRootImpl的Handler,但是ViewRootImpl可能为空,所以会使用一个备用的主线程Handler
        // 这个备用的Handler是通过RunQueue实现的,它会在View被attach到窗口时,将任务转移到正确的Handler
        ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
        return true;
    }
}

其中,AttachInfo是View内部的一个静态类,它保存了View附加到窗口时的一些信息,包括一个Handler(mHandler)。这个Handler实际上是ViewRootImpl中的Handler,也就是主线程的Handler。

因此,View.postDelayed()本质上就是通过主线程的Handler来延迟执行一个Runnable。

总结:View.postDelayed(Runnable, long) 是通过将Runnable发送到主线程的Handler的消息队列中,并设置延迟时间来实现延时的。当延迟时间到达后,主线程的Looper会从消息队列中取出并执行这个Runnable。

延时执行流程图

View.postDelayed(runnable, 100)
          ↓
Handler.postDelayed(msg, 100)
          ↓
sendMessageDelayed(msg, 100)
          ↓
sendMessageAtTime(msg, currentTime + 100)
          ↓
MessageQueue.enqueueMessage(msg, when)
          ↓
消息按 when 时间插入队列(时间排序)
          ↓
MessageQueue.next() 循环检查
          ↓
如果 now < msg.when,计算等待时间
          ↓
nativePollOnce(ptr, timeout) 阻塞
          ↓
时间到达 → 返回消息给 Handler
          ↓
Handler.dispatchMessage(msg)
          ↓
执行 runnable.run()

View.postDelayed() 的延时机制核心:

  1. 基于 Handler 消息队列:所有 postDelayed 最终都通过 Handler 实现
  2. 时间排序:消息队列按执行时间 (when) 排序
  3. 精准等待:使用 nativePollOnce 在 Native 层精确等待指定时间
  4. 时间基准:使用 SystemClock.uptimeMillis(),不受系统时间修改影响
  5. 线程关联View.postDelayed() 最终在主线程执行

9、CoroutineContext 是什么?

一个保存协程执行环境键值对集合,决定了协程如何执行。

CoroutineContext由以下几个主要元素构成:

  1. Job:控制协程的生命周期,例如启动和取消协程。

  2. CoroutineDispatcher:决定协程在哪个线程或线程池上运行。常见的Dispatcher有:

    • Dispatchers.Main:在主线程(通常是UI线程)上运行。
    • Dispatchers.IO:适用于I/O密集型任务。
    • Dispatchers.Default:适用于CPU密集型任务。
    • Dispatchers.Unconfined:不限制在特定线程上。
  3. CoroutineName:为协程指定一个名称,便于调试和日志记录。

  4. CoroutineExceptionHandler:处理协程中未捕获的异常。

这些元素可以通过加号(+)操作符组合在一起,形成一个上下文。当协程被创建时,它会从父协程继承上下文,并可以覆盖或添加新的元素。

例如,可以这样组合一个上下文:

val context = Dispatchers.Main + CoroutineName("MyCoroutine") + CoroutineExceptionHandler { _, exception ->
    println("Caught $exception")
}

CoroutineContext支持以下操作:

  • 组合:使用+操作符将多个上下文元素组合在一起。例如:Job() + Dispatchers.Main
  • 获取元素:通过Key来获取对应的元素。例如:context[CoroutineName]
  • 移除元素:使用minusKey函数移除指定Key的元素。

一句话总结:CoroutineContext是协程的"配置中心",管理线程、异常、生命周期等执行环境。

10、CoroutineContext 元素讲解?

1. Job

Job是协程的句柄,用于管理协程的生命 周期。Job可以有一个父Job,父Job取消时,所有子Job也会被取消。Job有三种状态:isActive、isCompleted、isCancelled。

2. CoroutineDispatcher

调度器决定了协程在哪个线程或线程池上运行。常见的调度器有:

  • Dispatchers.Default:用于CPU密集型任务,使用共享的线程池。
  • Dispatchers.IO:用于IO密集型任务,如网络请求、文件操作等。
  • Dispatchers.Main:在主线程上执行,用于更新UI(在Android中)。
  • Dispatchers.Unconfined:不限制在特定线程,但一般不推荐使用。

3. CoroutineName

为协程指定一个名称,方便调试和日志记录。可以通过coroutineContext[CoroutineName]来获取当前协程的名称。

4. CoroutineExceptionHandler

用于处理协程中未捕获的异常。注意:只有在根协程中设置的异常处理器才会被调用,子协程的异常会向上传播。

import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

fun main() = runBlocking {
    // 创建一个上下文,包含Job、Dispatcher、CoroutineName和ExceptionHandler
    val context: CoroutineContext = Job() + Dispatchers.Default + CoroutineName("MyCoroutine") + CoroutineExceptionHandler { _, exception ->
        println("Caught $exception")
    }

    // 使用这个上下文启动一个协程
    val job = launch(context) {
        println("Running in ${Thread.currentThread().name}, name: ${coroutineContext[CoroutineName]?.name}")
        delay(100)
        throw RuntimeException("Test exception")
    }

    job.join()
}

11、Job 是什么?它与协程有什么关系?

// Job 是协程的句柄,代表一个可取消的后台任务
val job = launch {
    // 协程体
}

// 关系:每个协程都会返回一个 Job 对象
// 协程是执行逻辑,Job 是管理这个执行的生命周期


public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

12、Job 的几种状态?

// 主要状态:
// 1. New (新建) - 创建但未启动
// 2. Active (活跃) - 启动后运行中
// 3. Completing (完成中) - 执行完毕,等待子协程
// 4. Cancelling (取消中) - 正在取消
// 5. Cancelled (已取消) - 已取消
// 6. Completed (已完成) - 已正常完成

13、如何取消一个 Job?

val job = launch {
    repeat(1000) { i ->
        delay(500)
        println("job: $i")
    }
}

// 方式1:直接取消
job.cancel()

// 方式2:取消并指定原因
job.cancel(CancellationException("用户取消"))

// 方式3:取消整个作用域
coroutineScope.cancel()

14、cancel() 和 cancelAndJoin() 的区别?

// cancel(): 异步取消,立即返回
job.cancel()

// cancelAndJoin(): 取消并等待协程结束
job.cancelAndJoin()  // 等价于 job.cancel() + job.join()

15、Job 的父子关系有什么作用?

// 父 Job 取消时,所有子 Job 都会被取消
val parentJob = launch {
    launch {  // 子 Job 1
        delay(1000)
        println("Child 1")
    }
    
    launch {  // 子 Job 2
        delay(2000)
        println("Child 2")
    }
}

// 取消父 Job,所有子 Job 都会取消
parentJob.cancel()

16、Job 和 SupervisorJob 的区别?

// Job: 子协程失败会传播给父协程
val job = Job()
launch(job) {
    launch {
        throw Exception()  // 会取消整个 job 作用域
    }
    launch {
        delay(1000)
        println("不会执行")  // 也被取消
    }
}

// SupervisorJob: 子协程失败不会影响其他子协程
val supervisorJob = SupervisorJob()
launch(supervisorJob) {
    launch {
        throw Exception()  // 只影响自己
    }
    launch {
        delay(1000)
        println("正常执行")  // 不受影响
    }
}

17、coroutineScope 和 supervisorScope 的区别?

// coroutineScope: 子协程失败会传播
coroutineScope {
    launch {
        throw Exception()  // 会取消整个作用域
    }
    launch {
        delay(1000)
        println("不会执行")
    }
}

// supervisorScope: 子协程独立
supervisorScope {
    launch {
        throw Exception()  // 只影响自己
    }
    launch {
        delay(1000)
        println("正常执行")  // 不受影响
    }
}

18、Job 的 join() 和 await() 有什么区别?

// join(): Job 的方法,等待完成(无返回值)
val job = launch { delay(1000) }
job.join()  // 等待 job 完成

// await(): Deferred 的方法,等待并获取结果
val deferred = async { "result" }
val result = deferred.await()  // 获取返回值 "result"

19、如何实现 Job 的依赖关系?

// 使用 join() 实现依赖
val job1 = launch { delay(1000) }
val job2 = launch {
    job1.join()  // 等待 job1 完成
    println("job1 完成后执行")
}

// 或者使用 async 组合结果
val result1 = async { doTask1() }
val result2 = async { doTask2() }
val finalResult = result1.await() + result2.await()

20、如何在 ViewModel 中正确处理 Job?

class MyViewModel : ViewModel() {
    private val jobs = mutableListOf<Job>()
    
    fun fetchData() {
        // 方式1:使用 viewModelScope(推荐)
        viewModelScope.launch {
            // 自动管理生命周期
        }
        
        // 方式2:手动管理
        val job = CoroutineScope(Dispatchers.IO).launch {
            // 需要手动管理
        }
        jobs.add(job)
    }
    
    override fun onCleared() {
        super.onCleared()
        jobs.forEach { it.cancel() }
    }
}

21、如何避免 Job 的内存泄漏?

// 反例:Activity 被销毁但 Job 还在运行
class MyActivity : AppCompatActivity() {
    private var job: Job? = null
    
    override fun onCreate() {
        job = GlobalScope.launch {  // ❌ 使用 GlobalScope
            while (isActive) {
                delay(1000)
                updateUI()  // 可能访问已销毁的 Activity
            }
        }
    }
    
    // 正确做法
    override fun onCreate() {
        // 使用 lifecycleScope
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                while (isActive) {
                    delay(1000)
                    updateUI()  // 只在活跃状态执行
                }
            }
        }
    }
}

22、为什么有时候 cancel() 不能立即取消协程?

launch {
    var i = 0
    while (i < 5) {
        // ❌ 错误:没有检查取消状态
        Thread.sleep(1000)  // 阻塞线程,无法响应取消
        // ✅ 正确:使用 delay()
        delay(1000)  // 可响应取消
        i++
    }
}

// 协程取消是协作式的,需要在代码中检查取消状态

23、withContext 返回的 Job 是什么?

val result = withContext(Dispatchers.IO) {
    // 这个 block 会立即执行
    "result"
}
// withContext 不是启动新协程,而是切换上下文执行代码
// 它返回的是代码块的执行结果,不是一个可管理的 Job

24、必须掌握的核心点:

  1. Job 是协程的句柄,用于管理生命周期
  2. 结构化并发:父子 Job 的生命周期绑定
  3. 取消是协作式的:需要检查 isActive 或使用可取消的挂起函数
  4. 两种 Job 类型:普通 Job 和 SupervisorJob
  5. 异常处理差异launch 异常会取消父 Job,async 异常需要 await() 时捕获

25、什么是AIDL?

  • AIDL是Android接口定义语言,用于定义跨进程通信的接口。
  • 它允许不同进程中的对象进行方法调用和数据交换。

26、为什么要使用AIDL?什么场景下使用?

  • 当需要跨进程通信(IPC)时,尤其是多个应用共享服务或一个应用需要为多个应用提供服务时。
  • 场景:音乐播放器服务、推送服务、多个应用共享数据等。

27、AIDL支持哪些数据类型?

  • 基本数据类型(int, long, char, boolean, double等)
  • String和CharSequence
  • List(其中的元素必须是AIDL支持的类型)
  • Map(其中的元素必须是AIDL支持的类型)
  • 其他AIDL接口
  • 实现了Parcelable的对象

28、AIDL的实现原理是什么?

  • AIDL通过Binder机制实现。编译时,AIDL文件会生成一个Java接口,其中包含一个Stub类(继承Binder)和一个Proxy类(代理类)。
  • 服务端实现Stub类,客户端通过Proxy类调用方法,实际调用通过Binder驱动传递到服务端。

29、Binder机制是什么?

  • Binder是Android中的一种跨进程通信机制,它使用内存共享的方式,效率高于传统IPC(如管道、消息队列等)。
  • Binder驱动在内核空间,负责进程间数据的交换。

30、AIDL中的Stub和Proxy是什么?

  • Stub:服务端的抽象类,继承Binder,并实现AIDL接口。它负责接收客户端调用,并将数据解析后调用服务端的具体实现。
  • Proxy:客户端的代理类,也实现AIDL接口。它负责将方法调用和数据打包,通过Binder发送给服务端。

31、如何创建一个AIDL服务?

步骤:

  1. 创建AIDL文件,定义接口。
  2. 实现Service,在onBind方法中返回Stub的实现。
  3. 在AndroidManifest.xml中声明Service,并设置exported属性为true(如果允许其他应用调用)。
  4. 客户端绑定服务,将返回的Binder对象转换为AIDL接口。

32、AIDL中如何进行权限验证?

  • 在Service的onBind方法中,可以通过调用Binder.getCallingUid()Binder.getCallingPid()来获取调用方的UID和PID,然后进行验证。
  • 也可以在Service的onTransact方法中验证,或者使用自定义权限。

33、AIDL中如何传递自定义对象?

  1. 自定义对象必须实现Parcelable接口。
  2. 为这个对象创建一个AIDL文件(如MyObject.aidl),并声明为parcelable。
  3. 在需要使用的AIDL接口文件中导入这个对象。

34、AIDL中的oneway关键字有什么作用?

  • 使用oneway修饰方法时,表示该方法为异步调用,客户端不会阻塞等待结果。
  • 注意:oneway方法不能有返回值,且不能抛出异常(除了RemoteException)。

35、AIDL中客户端调用服务端方法时,如何确保线程安全?

  • 服务端方法默认是在Binder线程池中执行的,所以服务端实现需要考虑多线程问题。
  • 如果服务端方法需要更新UI,则需要切换到主线程(使用Handler等)。

36、AIDL的客户端和服务端在同一个进程和不同进程有什么区别?

  • 同一个进程:AIDL接口调用不会经过Binder驱动,直接调用服务端实现。
  • 不同进程:调用会经过Binder驱动,涉及序列化和反序列化,因此效率较低。

37、AIDL中如何处理回调?

  • 在AIDL中定义回调接口,客户端实现该接口,并将回调对象通过AIDL接口注册到服务端。
  • 注意:回调是在服务端进程调用的,所以客户端接收回调的方法是在Binder线程池中,更新UI需要切换到主线程。

38、AIDL传输大量数据时如何优化?

  • 使用Parcelable时,避免传递过大的数据,可以考虑使用文件描述符(如ParcelFileDescriptor)传递文件,或者使用共享内存(Ashmem)。
  • 也可以将数据分块传输,或者使用更高效的数据格式。

39、AIDL服务端如何同时支持多个客户端?

  • 服务端默认就是多线程的,Binder线程池会处理多个客户端的请求。但需要注意服务端方法的线程安全。
  • 如果需要区分不同客户端,可以在接口中增加标识,或者使用回调时携带客户端信息。

40、AIDL与Messenger、ContentProvider的区别?

  • AIDL:功能强大,支持复杂的数据类型和同步/异步调用,但实现稍复杂。
  • Messenger:基于AIDL封装,只支持串行消息处理,使用简单,但功能有限。
  • ContentProvider:主要用于数据共享,提供CRUD接口,底层也是Binder。

41、AIDL调用失败的可能原因有哪些?

  • 服务端未在AndroidManifest.xml中正确声明。
  • 客户端和服务端的AIDL接口不一致(版本问题)。
  • 权限不足,没有声明或申请所需权限。
  • 服务端进程被杀掉,导致Binder死亡。

42、如何监控AIDL的调用性能?

  • 可以使用Traceview或Systrace工具分析调用耗时。
  • 在代码中记录关键点的时间戳,特别是跨进程调用的次数和耗时。

43、为什么需要 AIDL?哪些场景下使用?

需要跨进程通信的场景:

  1. 不同应用之间共享服务(如音乐播放器服务)
  2. 系统服务与应用的通信
  3. 多进程应用中的进程间通信
  4. 需要长时间运行的后台服务

44、AIDL 的工作流程是什么?

  1. 定义AIDL接口文件(.aidl)
  2. 编译生成Java接口文件
  3. 服务端实现Stub类
  4. 客户端通过Proxy调用
  5. Binder驱动进行进程间通信

45、Binder 机制在 AIDL 中起什么作用?

// Binder是Android跨进程通信的核心
// 1. Binder驱动在内核空间传递数据
// 2. IBinder接口是跨进程调用的桥梁
// 3. Parcel用于数据序列化/反序列化

46、AIDL 中的 in、out、inout 参数有什么区别?

// in: 参数从客户端流向服务端(默认)
void method1(in User user);  // 客户端→服务端

// out: 参数从服务端流向客户端
void method2(out User user); // 服务端→客户端

// inout: 双向流通
void method3(inout User user); // 客户端↔服务端

// 注意:基本类型只能使用in,因为out和inout需要能接收返回值

47、ARouter 怎么实现进程间通信的

当发生跨进程跳转时,ARouter不会让调用方直接操作目标进程的组件,而是引入了一个运行在目标进程的跨进程服务(IPCService) 作为代理。整个流程如下图所示:

deepseek_mermaid_20260120_c5544a.png

48、ARouter与标准AIDL的对比

你可以将ARouter的IPCService理解为一个专门为路由功能定制的、内置的AIDL服务。它与标准AIDL对比如下:

特性ARouter跨进程通信标准AIDL
抽象层级。开发者只需关心路由路径,无需直接操作Binder
。需定义AIDL接口,手动处理ServiceConnection和序列化。
使用场景特定。专为组件化架构中的跨进程页面跳转和服务获取设计
通用。适用于任意自定义的跨进程方法调用。
耦合性。调用方只依赖路由路径,不依赖目标组件的具体类。。调用方需要依赖AIDL接口的包。
便捷性。和单进程跳转使用相同的ARouter.getInstance().build(path).navigation()API。。需要完成绑定服务、转换接口、调用方法等一系列步骤。

49、ARouter总结

简单来说,ARouter通过在每个需要提供跨进程访问的进程中部署一个统一的“路由代理服务”(IPCService) ,将复杂的原生IPC细节隐藏起来。对于开发者,跨进程跳转与同进程跳转的代码几乎一致,框架在底层自动完成了进程间请求的转发和在本进程内的路由执行

注:ARouter的跨进程功能主要服务于其组件化路由的核心目标,并非一个通用的IPC框架。对于复杂的自定义跨进程交互,仍需使用AIDL等其他方案

50、Android 开发中,怎么做内存优化的

deepseek_mermaid_20260120_945e1f.png

内存泄漏 vs. 内存溢出 (OOM)

347ac557d01b5357ebd7f968a056cc30.png

52、系统性优化方案与实战代码

可以从以下三个层面入手,并举例说明:

1.代码与数据层面

  • 使用合适数据结构SparseArrayArrayMap替代HashMap可节省内存
  • 避免内存抖动:不在onDraw或循环中创建对象,可使用对象池复用
  • 处理Bitmap:这是OOM主因,务必使用BitmapFactory.Options采样 (inSampleSize),选择RGB_565等低内存配置,并通过Bitmap.recycle()及时回收

  • 注意集合清理:及时清除集合内无用的对象引用。
// Bitmap采样加载示例
public static Bitmap decodeSampledBitmap(Resources res, int resId, int reqWidth, int reqHeight) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true; // 先只读尺寸
    BitmapFactory.decodeResource(res, resId, options);
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 计算采样率
    options.inJustDecodeBounds = false;
    options.inPreferredConfig = Bitmap.Config.RGB_565; // 使用更省内存的配置
    return BitmapFactory.decodeResource(res, resId, options);
}

2.资源与架构层面

  • 及时释放资源:在onDestroy()中关闭Cursor、文件流,注销BroadcastReceiver
  • 优化UI与资源:使用ViewStub延迟加载,压缩图片为WebP格式,移除未用资源
  • 使用内存缓存:用LruCache缓存Bitmap等对象,并设置合理上限
  • 架构预防泄漏
  • 使用Application Context代替Activity Context
  • 将内部类(如Handler)改为静态内部类,并使用WeakReference弱引用持有外部类(如Activity)
  • 使用ViewModel + LiveData,它们与Activity/Fragment生命周期自动关联,能有效避免泄漏。
  • 使用Lifecycle组件确保异步操作在页面销毁时自动取消。
// 使用静态内部类 + 弱引用的Handler示例
private static class SafeHandler extends Handler {
    private final WeakReference<MyActivity> mActivityRef;
    SafeHandler(MyActivity activity) {
        mActivityRef = new WeakReference<>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        MyActivity activity = mActivityRef.get();
        if (activity != null && !activity.isFinishing()) {
            // 安全地更新UI
        }
    }
}

3.进程与系统层面ApplicationComponentCallbacks2中实现onTrimMemory(),根据系统内存级别释放缓存等资源

  • 对于多进程应用,将内存消耗大的功能(如后台播放)放到独立进程,系统可单独回收。

53、监控、排查工具链

开发期/实验室

  • Android Studio Profiler:核心工具,用于实时监控内存曲线、发现抖动、手动抓取堆快照(Hprof)并进行分配追踪
  • LeakCanary:集成到调试版本,自动检测并报告内存泄漏,给出引用链,非常直观
  • 线上监控(Buly):可以搭建自动化上报系统,在OOM发生或内存使用异常时,采集Hprof文件并上报,用于分析线上真实问题。

54、Android 中 lifecycleScope、 GlobalScope 、viewModelScope和CoroutineScope他们有什么区别,使用场景是什么?

为了让你一目了然,我将它们的核心区别和使用场景总结为下表:

特性lifecycleScopeviewModelScopeGlobalScope自定义 CoroutineScope
绑定对象LifecycleOwner (Activity, Fragment)ViewModel (应用进程)自定义 (任何对象)
自动取消时机Lifecycle 被销毁时 (DESTROYED)ViewModelonCleared()永不自动取消手动控制 (调用 cancel())
内存安全安全安全⚠️ 极不安全 (易内存泄漏)取决于实现
结构化并发支持支持不支持支持
主要使用场景UI 相关的生命周期感知的短任务ViewModel 内部的业务逻辑与数据准备几乎不推荐,仅限整个应用进程存活期间执行的任务需要精细控制生命周期的复杂场景

55、lifecycleScope

  • 本质:与 ActivityFragmentLifecycle 绑定。是每个 LifecycleOwner 的扩展属性。

  • 关键点:当 Lifecycle 进入 DESTROYED 状态时,其下的所有协程会自动取消。这完美解决了在 Activity 销毁后更新 UI 导致的崩溃问题。

// 在 Activity/Fragment 中发起一个与UI生命周期绑定的请求
class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 当 Fragment 视图销毁时,此协程自动取消
        lifecycleScope.launch {
            val data = withContext(Dispatchers.IO) { repository.fetchData() }
            updateUI(data) // 只有生命周期活跃时才会执行到这里
        }
    }
}

56、viewModelScope

  • 本质:与 ViewModel 的生命周期绑定。是每个 ViewModel 的扩展属性。
  • 关键点:当 ViewModel 被清除(例如因配置更改或 Activity 正常结束)时,其 onCleared() 方法被调用,所有协程自动取消。这是执行业务逻辑准备数据的首选作用域。
  • 典型场景
class MyViewModel(private val repo: MyRepository) : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState

    fun loadData() {
        // 数据加载任务与 ViewModel 生命周期绑定
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val data = repo.fetchData() // 可能是网络或数据库操作
                _uiState.value = UiState.Success(data)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message)
            }
        }
    }
    // 当 ViewModel 被清除时,`loadData` 中启动的协程会被自动取消,避免资源浪费。
}

57、GlobalScope

  • 极度不推荐常规使用
  • 本质:一个与应用进程同生命周期的全局作用域。

58、自定义 CoroutineScope

  • 本质:通过 CoroutineScope(context) 工厂函数创建,生命周期完全由开发者控制。

  • 关键点:用于那些无法直接使用 lifecycleScopeviewModelScope,但又需要精细化生命周期管理的场景。

  • 典型场景

    • 在自定义 ViewPresenter 中管理一组相关任务。
    • 需要手动提前取消一组任务(例如用户离开某个页面标签页)。
    • 实现一个独立的、可重启的任务管理器。
class MyCustomController {
    // 1. 创建一个自定义作用域,使用 SupervisorJob 使子协程失败互不影响
    private val customJob = SupervisorJob()
    private val customScope = CoroutineScope(Dispatchers.Main + customJob)

    fun startWork() {
        customScope.launch {
            // 执行一系列任务
        }
    }

    fun stopAllWork() {
        // 2. 手动取消所有在此作用域内启动的协程
        customJob.cancel()
    }

    fun onDestroy() {
        stopAllWork()
        // 可选:也可重新创建 job 和 scope 以复用 controller
    }
}

59、Scope选择策略与最佳实践

  1. 默认首选 viewModelScope:在 ViewModel 中处理所有业务逻辑和数据准备。这是 Android 架构组件的最佳拍档。
  2. UI 操作使用 lifecycleScope:在 Activity/Fragment 中直接执行与 UI 生命周期紧密相关的轻量级操作(例如 View 的动画、点击防抖),或配合 repeatOnLifecycle 安全地收集 Flow
  3. 尽量避免 GlobalScope:将它视为一个“代码异味”。如果你的逻辑需要长时间运行,请考虑使用 WorkManager 或绑定到自定义 Service 的作用域。
  4. 在需要精确控制时自定义:当内置作用域无法满足你复杂的生命周期管理需求时,就创建一个自定义作用域,并务必记得在合适的时机手动取消

简单来说,记住一个核心原则:让你的协程生命周期与负责它的组件的生命周期保持一致。这样,当组件销毁时,所有相关任务都能自动清理,这是避免内存泄漏和异常的关键。

60、ViewModel 是什么?解决了什么问题?

核心答案ViewModel 是一个生命周期感知的,用于存储和管理 UI 相关数据的类。它的设计目的是:

  • 解决数据持久化问题:在屏幕旋转等配置更改导致 Activity 重建时,ViewModel 中的数据会被保留,无需重新从网络或数据库加载。
  • 实现关注点分离:将数据持有和业务逻辑Activity/Fragment (UI 控制器) 中剥离出来,让后者专注于处理 UI 和用户交互。
  • 实现数据共享:在同一个 Activity 的多个 Fragment 间轻松共享数据。

61、ViewModel 的生命周期是怎样的?

  • ViewModel 的生命周期范围从首次创建(通常在 ActivityonCreate 中获取)一直到关联的 Activity 最终 finishFragment 最终 detached
  • 配置更改(如旋转)导致的 Activity 短暂销毁重建过程中,ViewModel 不会销毁。它会在新的 Activity 实例中重用。
  • 只有当 Activity 真正结束(按返回键退出)时,系统才会调用 ViewModelonCleared() 方法进行清理。

62、ViewModel 如何实现数据在配置更改后依然存活?

原理简述ViewModel 对象并非由 Activity 直接持有,而是由 ViewModelStore(通过 ViewModelProvider)在 Activity 的非配置状态(onRetainNonConfigurationInstance 机制,现由 ViewModelStore 内部管理)中保存并传递。

面试回答重点ViewModel 的生命周期由内部的 ViewModelStore 管理。配置更改时,Activity 对象虽然被销毁,但 ViewModelStore 及其持有的 ViewModel 实例会被系统保留,并关联到新创建的 Activity 实例上。

63、ViewModel 与 onSaveInstanceState 的区别

面试官常让你对比这两者,下表清晰地说明了它们的适用场景:

特性ViewModelonSaveInstanceState (Bundle)
数据持久性在配置更改时存活,进程被杀死后消失在配置更改和进程被杀死后重建时均可存活
存储位置内存序列化到磁盘
数据类型支持复杂、大型对象(如列表、LiveData)只适合存储小型、可序列化的简单数据(如ID、状态标志)
设计目的管理UI所需的数据保存瞬时的UI状态(如滚动位置、临时勾选状态)

结论:两者是互补关系。

  • ViewModel 保存准备和恢复成本高的数据(如用户列表)。
  • onSaveInstanceState 保存轻量的UI状态,以便在应用被系统杀死后能精确恢复界面。

64、ViewModel 与 LiveData / Flow 的协作

这是现代 Android 开发的标配模式。

class MyViewModel : ViewModel() {
    // 1. 使用 LiveData(状态容器)
    private val _uiState = MutableLiveData<UiState>()
    val uiState: LiveData<UiState> = _uiState

    // 2. 使用 StateFlow / SharedFlow(更现代的协程流)
    private val _uiStateFlow = MutableStateFlow<UiState>(UiState.Loading)
    val uiStateFlow: StateFlow<UiState> = _uiStateFlow

    fun loadData() {
        viewModelScope.launch { // 3. 使用 viewModelScope 管理协程
            _uiState.value = UiState.Loading
            try {
                val result = repository.fetchData()
                _uiState.value = UiState.Success(result)
                _uiStateFlow.value = UiState.Success(result)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message)
                _uiStateFlow.value = UiState.Error(e.message)
            }
        }
    }
}

// 在 Activity/Fragment 中观察(以 Flow 为例)
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiStateFlow.collect { uiState ->
            // 安全地更新 UI(只在 STARTED 及以上状态接收)
            updateUI(uiState)
        }
    }
}

面试要点

  • viewModelScope 的作用:确保所有协程在 ViewModel 销毁时自动取消,避免内存泄漏。
  • 观察模式的好处UI 订阅 ViewModel 的数据,数据变化自动驱动 UI 更新,实现单向数据流。

65、常见陷阱与注意事项

1. 不要向 ViewModel 传递 Context

问题:如果传入 ActivityContextViewModel 存活期间会一直持有该引用,导致 Activity 无法被回收(内存泄漏)。
正确做法:如果需要 Context,应使用 AndroidViewModel(它持有 ApplicationContext)。

2.Fragment 间共享 ViewModel 的作用域

  • 同一 Activity 内共享:使用 by activityViewModels()
  • 同一导航图内共享:使用 by navGraphViewModels(graphId)
    原理:它们使用相同的 ViewModelStoreOwner 来获取 ViewModel 实例。

3.SavedStateHandle:让 ViewModel 支持进程死亡恢复

kotlin

class MyViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    // 将数据保存在 SavedStateHandle 中,进程被杀死后也能恢复
    var userId: String by mutableStateOf(
        savedStateHandle.get<String>("userId") ?: ""
    )
        private set

    fun saveUserId(id: String) {
        userId = id
        savedStateHandle["userId"] = id // 保存到 Bundle
    }
}

可能的高阶追问

  • ViewModelonCleared() 方法里应该做什么?” (清理资源,取消自定义的监听或协程)。
  • ViewModel 是单例吗?” (不是传统单例,它的作用域由 ViewModelStoreOwner 决定,在同一个 Owner 下是单例)。
  • “能直接在新线程中修改 LiveDatavalue 吗?” (不能,需用 postValue)。