当前位置: 首页 > news >正文

Android第三次面试总结之activity和线程池篇(补充)

一、线程池高频面试题

1. 为什么 Android 中推荐使用线程池而非手动创建线程?(字节跳动 / 腾讯真题)
  • 核心考点:线程池的优势、资源管理、性能优化
  • 答案要点
    • 复用线程:避免重复创建 / 销毁线程的开销(单个线程创建耗时约 9ms,复用可降低延迟)。
    • 控制并发:通过参数(corePoolSize/maxPoolSize)控制最大并行任务数,防止 OOM(如大量并发网络请求导致内存溢出)。
    • 队列管理:任务队列(如LinkedBlockingQueue)缓冲未执行任务,避免系统资源耗尽。
    • 线程管理:后台线程自动回收,避免内存泄漏(手动创建线程若持有 Activity 引用,易导致 Activity 无法销毁)。
  • 大厂真题延伸
    “如果项目中需要处理 1000 个耗时任务(如文件下载),如何设计线程池参数?”
    :根据任务类型选择策略:
    • 计算密集型:corePoolSize≈CPU 核心数(Android 设备常见 4-8 核);
    • IO 密集型:corePoolSize可适当增大(因 IO 等待时线程可空闲,如2*CPU核心数);
    • 此处下载任务属 IO 密集型,可设corePoolSize=5maxPoolSize=10,队列选LinkedBlockingQueue(有界队列避免内存溢出),拒绝策略用AbortPolicy(抛异常提醒任务处理失败)。
2. ThreadPoolExecutor 的核心参数有哪些?工作流程是什么?
  • 核心参数(必背!)
    public ThreadPoolExecutor(int corePoolSize,       // 核心线程数,空闲时也会保持存活int maximumPoolSize,    // 最大线程数,核心+临时线程总和long keepAliveTime,     // 临时线程空闲时的存活时间TimeUnit unit,          // 时间单位BlockingQueue<Runnable> workQueue, // 任务队列ThreadFactory threadFactory,       // 线程工厂(自定义线程名,方便调试)RejectedExecutionHandler handler   // 拒绝策略
    );
    
  • 工作流程(画图辅助理解)
    1. 任务提交,若线程数<corePoolSize,创建新线程执行;
    2. 若线程数≥corePoolSize,任务入队(根据队列类型,如无界队列LinkedBlockingQueue会一直排队);
    3. 若队列已满且线程数<maximumPoolSize,创建临时线程执行;
    4. 若队列已满且线程数≥maximumPoolSize,触发拒绝策略(4 种策略需背熟:AbortPolicy/CallerRunsPolicy/DiscardPolicy/DiscardOldestPolicy)。
  • 踩坑点
    “为什么不推荐使用 Executors 工具类创建线程池?”
    :Executors 创建的线程池存在隐患:
    • newFixedThreadPool/newSingleThreadExecutor使用无界队列LinkedBlockingQueue,任务大量堆积时可能 OOM;
    • newCachedThreadPoolmaximumPoolSizeInteger.MAX_VALUE,突发大量任务时会创建海量线程,导致 CPU 过载。
      正确做法:直接使用ThreadPoolExecutor自定义参数,根据业务场景配置。
3. 线程池如何实现任务优先级?
  • 核心思路:自定义任务队列,重写offer()/poll()方法实现优先级排序。
  • 代码示例
    // 优先级任务队列(实现Comparator)
    PriorityBlockingQueue<Runnable> priorityQueue = new PriorityBlockingQueue<>(10, (r1, r2) -> {if (r1 instanceof PriorityRunnable && r2 instanceof PriorityRunnable) {return ((PriorityRunnable) r2).getPriority() - ((PriorityRunnable) r2).getPriority(); // 高优先级先执行}return 0;
    });
    // 创建线程池时使用该队列
    ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, priorityQueue
    );
    
  • 面试加分项:结合实际场景(如直播场景中,弹幕渲染任务优先级高于日志上报),说明优先级队列的应用价值。

二、Activity 高频面试题

1. Activity 生命周期完整流程是什么?哪些场景会触发各个回调?
  • 关键回调触发场景
    • onCreate():Activity 创建时调用(初始化 UI、数据),系统异常重建时也会调用(需结合onSaveInstanceState恢复数据)。
    • onStart()onResume():Activity 可见 / 可交互时调用(区别:onStart表示可见但可能在后台,onResume表示位于前台)。
    • onPause():Activity 失去焦点时调用(如跳转到新 Activity,需在此处停止动画、释放 CPU 资源,但避免耗时操作,否则影响新 Activity 启动)。
    • onStop():Activity 完全不可见时调用(可执行较耗时操作,如保存数据,但需注意若用户快速返回,可能跳过onStop直接调用onRestart)。
    • onDestroy():Activity 销毁时调用(释放所有资源,避免内存泄漏,如取消网络请求、解绑 Listener)。
  • 大厂真题
    “横竖屏切换时,Activity 的生命周期如何变化?如何避免数据丢失?”
    1. 默认情况下,屏幕旋转会导致 Activity 销毁重建,生命周期走:onPause()onStop()onDestroy()onCreate()onStart()onResume()
    2. 避免数据丢失方案:
      • 重写onSaveInstanceState(Bundle)保存临时数据(系统自动调用,在onStop()前),onRestoreInstanceState(Bundle)onCreate(Bundle)中恢复;
      • 在 Manifest 中给 Activity 添加android:configChanges="orientation|screenSize",阻止重建,此时仅调用onConfigurationChanged()
2. Activity 启动模式有哪些?应用场景是什么?
  • 四大启动模式(必背!)
    模式核心特性典型场景
    standard每次启动都创建新实例,默认模式,多实例共存(栈内可存在多个相同 Activity)普通页面(如商品详情页)
    singleTop若栈顶已有实例,不再创建,调用onNewIntent()消息通知页(避免重复打开顶部)
    singleTask栈内唯一实例,启动时清除栈中该 Activity 以上的所有 Activity(单任务栈)主页(如微信首页,需清空其他页面)
    singleInstance全局唯一实例,独占一个任务栈,其他 Activity 需跳转至该栈调用来电界面(独立于其他应用)
  • 进阶问题
    “如何理解任务栈(Task)和启动模式的关系?singleTask为什么会清空栈顶?”
    • 任务栈是 Activity 的容器,遵循 “后进先出”,每个应用默认有一个主任务栈;
    • singleTask要求实例在栈内唯一,若目标栈中存在该 Activity,会清除其上方的所有 Activity,确保其成为栈顶(类似 “回到主页并清空中间页面”)。
      “启动模式和IntentFlag(如FLAG_ACTIVITY_NEW_TASK)如何配合使用?”
      FLAG_ACTIVITY_NEW_TASK相当于singleTask模式,优先级高于清单文件配置,二者同时存在时以IntentFlag为准。
3. Activity A 启动 Activity B,如何回传数据?异常情况下(如 B 被系统杀死)如何保证数据不丢失?
  • 常规方法
    • A 通过startActivityForResult(Intent, requestCode)启动 B;
    • B 通过setResult(resultCode, data)回传数据,A 在onActivityResult()中处理;
    • 注意:Android 10 + 建议使用ActivityResultContract替代(更简洁安全,避免内存泄漏)。
  • 异常场景处理
    • 若 B 在后台被系统杀死(内存不足),系统会保存其onSaveInstanceState数据,重建后onActivityResult()仍会回调,但需在 B 的onCreate()中恢复必要数据,确保setResult()正常调用。
  • 大厂真题延伸
    “如果 A 启动 B 后,B 又启动 C,C 如何直接返回数据给 A?”
    1. 传统方法:C→B→A 逐层回传(繁琐);
    2. 优化方案:使用事件总线(如 EventBus、LiveData),C 发送事件,A 订阅并处理(需注意解耦和内存泄漏,Activity 销毁时取消订阅)。

 面试扩展:

一、线程池深度补充

1. 线程池中的线程为什么不会无限创建?核心源码解析
  • 源码关键逻辑ThreadPoolExecutor.execute(Runnable command)):
    public void execute(Runnable command) {if (command == null) throw new NullPointerException();int c = workerCountOf(ctl.get());// 步骤1:若当前线程数 < 核心线程数,创建核心线程执行任务if (c < corePoolSize) {if (addWorker(command, true)) return;c = workerCountOf(ctl.get());}// 步骤2:若线程数 >= 核心线程数,将任务加入队列if (isRunning(c) && workQueue.offer(command)) {int recheck = workerCountOf(ctl.get());// 再次检查线程池状态(防止加入队列后线程池已关闭)if (!isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false); // 确保至少有一个核心线程存在(allowCoreThreadTimeOut=false时)}// 步骤3:队列已满,创建非核心线程(直到maxPoolSize),失败则触发拒绝策略else if (!addWorker(command, false))reject(command);
    }
    
  • 核心控制逻辑
    • 通过ctl变量(32 位整数,高 3 位表示状态,低 29 位表示线程数)原子性控制线程创建和状态转换;
    • addWorker方法会检查线程数是否超过maximumPoolSize,确保线程数不超过上限。
2. 如何监控线程池的运行状态?大厂性能优化必问题
  • 关键监控指标
    方法含义应用场景
    getActiveCount()当前正在执行任务的线程数判断线程池是否繁忙(如扩容依据)
    getCompletedTaskCount()已完成的任务总数统计任务处理效率
    getQueue().size()等待执行的任务数预警队列堆积(如超过阈值时扩容)
    getLargestPoolSize()线程池曾创建的最大线程数分析峰值负载,优化maxPoolSize
  • 实战方案
    // 定时打印线程池状态(建议在后台线程执行)
    ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
    monitor.scheduleAtFixedRate(() -> {log.d("Thread pool status: " +"active=" + executor.getActiveCount() +", queueSize=" + executor.getQueue().size() +", completed=" + executor.getCompletedTaskCount());
    }, 0, 5, TimeUnit.SECONDS);
    
  • 大厂真题
    “如果发现线程池队列持续堆积,如何排查和解决?”
    1. 检查任务处理耗时是否过长(可能存在耗时操作未优化);
    2. 确认corePoolSize是否过小(IO 密集型任务可适当增大);
    3. 若使用有界队列,可临时增大队列容量或调整maxPoolSize
    4. 考虑任务优先级,拆分高优先级任务到独立线程池。
3. WorkManager vs 线程池:Android 后台任务如何选择?
  • 核心区别
    特性线程池(ThreadPoolExecutor)WorkManager
    生命周期管理依赖宿主(如 Activity)手动关闭系统级管理,支持跨进程、跨重启执行
    持久化任务不支持(任务丢失后需手动重启)支持(通过WorkRequest配置持久化)
    约束条件无(需自行处理网络、电量等限制)支持网络状态、充电状态等约束
    适用场景短期、实时性任务(如网络请求)长期后台任务(如日志上报、定时同步)
  • 大厂最佳实践
    • 前台实时任务(如界面数据加载):使用线程池 + Activity 生命周期绑定;
    • 后台异步任务(如下载、定时任务):优先使用 WorkManager(避免后台服务被系统杀死,兼容 Android 12 + 前台服务限制)。

二、Activity 深度补充(Framework 层原理 + 实战坑点)

1. Activity 启动流程
  • 极简流程图
    用户操作 → Launcher发送启动Intent → AMS(ActivityManagerService)查询目标Activity信息 →  
    若目标进程未启动,创建ProcessRecord → 创建ActivityThread → 创建Activity实例 →  
    依次调用Activity的生命周期回调(onCreate→onStart→onResume)  
    
  • 面试高频问题
    “Activity 启动时,AMS 如何处理任务栈?singleInstance模式为什么独占一个任务栈?”
    • AMS 维护一个任务栈列表,每个任务栈有独立的 Task ID;
    • singleInstance模式要求 Activity 在全局唯一,因此 AMS 会为其创建新的任务栈,确保其他 Activity 无法进入该栈,实现 “独占”。
2. onSaveInstanceState vs onRestoreInstanceState:必坑细节
  • 调用时机
    • onSaveInstanceState非用户主动销毁时调用(如系统内存不足、配置变更),用户按 Back 键不会调用(因 Activity 是主动销毁,无需保存);
    • onRestoreInstanceState:在onStart()之后、onResume()之前调用,仅当 Activity 有保存的 InstanceState 时触发
  • 数据丢失场景
    “如果在 onSaveInstanceState 中保存了 EditText 内容,为什么旋转屏幕后内容还在,但用户按 Back 键退出再进入就丢失了?”
    • 旋转屏幕属于配置变更,系统自动保存并恢复InstanceState
    • 按 Back 键退出时 Activity 被主动销毁,onSaveInstanceState不会调用,因此未保存数据,再次进入时需通过onCreate的参数或ViewModel恢复。
  • 最佳实践
    • 临时 UI 状态(如 EditText 内容、列表滚动位置):用onSaveInstanceState
    • 持久化数据(如用户登录状态、网络请求结果):用ViewModel(不随 Activity 销毁而销毁)或SharedPreferences
3. Activity 异常销毁(系统回收)后的恢复策略
  • 三大恢复方案对比
    方案数据类型生命周期感知优缺点
    onSaveInstanceState临时 UI 状态(可序列化)仅处理配置变更 / 内存回收简单易用,但数据需可序列化,且 Back 键不触发
    ViewModel与界面相关的业务数据感知 Activity/Fragment 生命周期不随配置变更销毁,需配合 LiveData 更新 UI
    SavedStateHandle键值对数据(Jetpack)替代传统 InstanceState类型安全,支持更复杂数据结构(如 List)
  • 代码示例(ViewModel + SavedStateHandle)
    class MyViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {private val _userData = savedStateHandle.getLiveData<String>("user_data")val userData: LiveData<String> get() = _userDatafun saveData(data: String) {savedStateHandle["user_data"] = data // 自动保存,Activity重建时恢复}
    }
    

三、最新大厂真题实战

1. 字节跳动:如何设计一个线程池,让高优先级任务优先执行,同时避免低优先级任务饿死?
  • 解题思路
    1. 使用PriorityBlockingQueue作为任务队列,自定义任务优先级比较器;
    2. 限制高优先级任务的并发数,避免低优先级任务长期无法执行(“公平性策略”)。
  • 关键代码
    // 自定义优先级任务
    class PriorityRunnable(private val priority: Int, private val runnable: Runnable) implements Runnable, Comparable<PriorityRunnable> {@Overridepublic void run() { runnable.run(); }@Overridepublic int compareTo(PriorityRunnable o) {return o.priority - this.priority; // 数值越大优先级越高}
    }// 线程池配置
    ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 8, 30, TimeUnit.SECONDS,new PriorityBlockingQueue<>(),(r) -> new Thread(r, "PriorityThread-" + counter.incrementAndGet())
    );
    
  • 进阶回答
    补充 “饥饿解决方案”:设置最低执行配额(如每执行 N 个高优先级任务,必须执行 1 个低优先级任务),或使用公平队列(如按提交顺序混合执行)。
2. 美团:Activity A 启动 Activity B 时,B 的主题设置为透明(android:theme="@android:style/Theme.Translucent"),A 的生命周期如何变化?
  • 核心考点:Activity 可见性对生命周期的影响
  • 答案
    • 若 B 为透明主题,A 未完全遮挡,A 只会调用onPause()(保持部分可见),不会调用onStop()
    • 若 B 为非透明主题(完全覆盖 A),A 会调用onPause()onStop()
  • 延伸问题
    “如何判断一个 Activity 是否在前台可见?”
    • 通过isResumed()(前台可交互)或重写ActivityLifecycleCallbacks监控所有 Activity 的onResume/onPause
    • 注意:透明 Activity 可能让底层 Activity 处于 “可见但非焦点” 状态,需结合getWindowVisibility()判断。

相关文章:

  • 软件架构之旅(6):浅析ATAM 在软件技术架构评估中的应用
  • Webug4.0通关笔记12- 第17关 文件上传之前端拦截(3种方法)
  • OpenHarmony平台驱动开发(一),ADC
  • 人工智能(AI)未来会产生意识吗?
  • Flink基础整理
  • Python速成系列二
  • React hooks详解
  • 新能源实验室电磁兼容设计优化方案论述
  • HTML02:网页基本信息
  • 苍穹外卖部署到云服务器使用Docker
  • 软考 系统架构设计师系列知识点之杂项集萃(52)
  • 观察者模式(Observer Pattern)详解
  • 自由学习记录(58)
  • n8n工作流自动化平台的实操:利用本地嵌入模型,完成文件内容的向量化及入库
  • 从 0 到 1:使用 Jetpack Compose 和智能自动化实现高效 Android UI 开发
  • 2025 年如何使用 Pycharm、Vscode 进行树莓派 Respberry Pi Pico 编程开发详细教程(更新中)
  • HTML学习笔记(7)
  • PHP的include和require
  • 基于STM32的心电图监测系统设计
  • 【前端】【面试】在 Vue-React 的迁移重构工作中,从状态管理角度来看,Vuex 迁移到 Redux 最大的挑战是什么,你是怎么应对的?
  • 五一假期,新任杭州市委书记刘非到嘉兴南湖瞻仰红船
  • 商务部:外贸优品中华行活动采购意向超167亿元
  • 党旗下的青春|赵天益:少年确定志向,把最好的时光奉献给戏剧事业
  • 2024年境内酒店住宿行业指标同比下滑:酒店行业传统增长模式面临挑战
  • 燕子矶:物流网络中的闪亮节点|劳动者的书信②
  • 向左繁华都市,向右和美乡村,嘉兴如何打造城乡融合发展样本