Android面试高频题|总被面试官穷追猛打的Activity

零:前言

在Android的世界里,四大组件(Activity、Service、BroadcastReceiver、ContentProvider) 如同搭建应用骨架的「四根支柱」,它们的协作定义了应用的基础行为模式。但许多开发者(甚至有一定经验的人)对它们的理解往往停留在表面:

  • 你以为懂了Activity:背熟了生命周期图,却在屏幕旋转时丢数据;
  • 你以为懂了Service :知道startServicebindService的区别,却说不清「前台服务」与「JobScheduler」的取舍;
  • 你以为懂了BroadcastReceiver:用过动态注册,却对「有序广播」和「粘性广播」的坑视而不见;
  • 你以为懂了ContentProvider :用它跨进程共享数据,却从未深究过Binder的底层通信机制...

接下来本系列将以「高频面试题」为线索,重新解构四大组件。作为本系列的第一篇博客将会先搞定四大组件之首 - Activity

壹、Activity

问题1:Activity的生命周期有哪些?请描述从启动到销毁的完整流程。

(一)正常生命周期流程

  1. 启动阶段

    1. onCreate():首次创建时调用,用于初始化视图和数据。
    2. onStart():Activity可见但未进入前台。
    3. onResume():Activity进入前台,可接收用户输入。
  2. 暂停与停止

    1. onPause():Activity失去焦点(如弹窗覆盖),需释放占用资源(如相机)。
    2. onStop():Activity完全不可见(如跳转到其他Activity)。
  3. 重新回到前台

    1. onRestart():Activity从停止状态重新启动。
    2. onStart()onResume():再次可见并进入前台。
  4. 销毁阶段

    1. onDestroy():Activity被销毁(用户主动退出或系统回收)。

完整流程图

scss 复制代码
onCreate() → onStart() → onResume() → [运行中] → onPause() → onStop() → onDestroy()

(二)异常生命周期流程

  1. 配置变更(如屏幕旋转) Activity会被销毁并重建,触发onSaveInstanceState()保存临时数据,重建后通过onRestoreInstanceState()恢复。
  2. 内存不足回收 后台Activity可能被系统回收,需通过onSaveInstanceState()保存关键数据。

关键方法

  • onSaveInstanceState(Bundle outState):保存临时数据(如文本框内容)
  • onRestoreInstanceState(Bundle savedInstanceState):恢复数据

(三)常见问题

  1. onResume() onPause()的注意事项

    1. onResume():适合启动动画、传感器监听等需要实时交互的操作
    2. onPause():必须轻量化(耗时操作会阻塞下一个Activity启动)
  2. 避免在 onCreate()中执行耗时任务 若初始化耗时(如加载数据库),应使用异步任务或ViewModel延迟加载。

  3. onDestroy()的不确定性 不能依赖onDestroy()释放资源(系统可能直接终止进程),应在onStop()中释放。

问题2: Activity和Fragment的生命周期有哪些关键区别?

  • onAttach(Context context): 当 Fragment 与 Activity 关联时调用(此时可通过 getActivity() 获取宿主 Activity)。通常用于获取 Activity 传递的依赖(如接口回调)。
  • onCreateView(): 创建 Fragment 的视图层次结构时调用,通过 LayoutInflater 解析布局文件
  • onViewCreated():onCreateView() 返回的视图创建完成后调用,适合初始化视图组件(如 findViewById)、设置 RecyclerView 适配器等
  • onActivityCreated() (已废弃):在 AndroidX 中,此方法已被废弃,推荐在 onViewCreated() 中结合 ViewLifecycleOwner 监听生命周期。
  • onDestroyView() :当 Fragment 的视图被销毁时调用(如 Fragment 被移除或替换)。用途:清理与视图相关的资源(如取消异步任务、解除观察者绑定)。
  • onDetach() :当 Fragment 与 Activity 解除关联时调用。释放对宿主 Activity 的引用,避免内存泄漏。

问题3:在什么场景下你会选择使用Fragment而不是直接使用多个Activity?

Fragment相较于Activity来说是轻量化组建,Fragment是可以依附在Activity的生命周期

(1)何时选择 Fragment?

  • 界面复用的时候:在不同 Activity 中复用同一界面
  • 底部导航栏和侧边抽屉的这种导航组件时,这种场景需要频繁切换,可以使用Fragment来实现这种轻量化的切换。
  • Fragment 的生命周期与宿主 Activity 绑定,适合管理局部 UI 逻辑

(2)何时选择 Activity?

  • 需要完全独立的界面,不同模块功能的入口
  • 需要处理全局配置(如系统级别的 Intent 过滤)

Fragment 的核心优势:

  1. 轻量高效:适合高频次、局部 UI 更新(如 Navigation 切换、ViewPager2 滑动)。
  2. 模块化与复用性:便于组合复杂界面,支持跨 Activity 复用。
  3. 生命周期可控性:与 Jetpack 组件深度集成,简化状态管理。
  4. 现代化架构支持:单 Activity + 多 Fragment 是 Google 推荐的最佳实践。

传递Bitmap这类大数据的问题在于,如果直接通过Bundle传递,会导致序列化和反序列化的开销,可能引发TransactionTooLargeException异常。所以应该避免直接传递Bitmap对象。

  1. 序列化开销:Bitmap 需要序列化为字节流,消耗 CPU 和内存。
  2. TransactionTooLargeException:Android 的 Binder 事务缓冲区限制为 1MB,传递大对象易崩溃。
  3. 内存泄漏风险:未释放的 Bitmap 可能长期占用内存。

(1)首先想到的是ViewModel,因为ViewModel可以在Fragment之间共享数据,避免直接在导航参数中传递。例如,使用activity范围的ViewModel,这样多个Fragment可以访问同一个ViewModel实例,从而共享Bitmap数据。

(2)使用内存缓存,比如LruCache,这样可以临时存储Bitmap,避免重复加载。同时,需要处理缓存的生命周期,确保不会引起内存泄漏。

(3)使用单例或依赖注入框架(如Hilt)来管理共享的数据,这样数据独立于组件的生命周期,但需要注意内存管理和线程安全。

(4)图片加载库如Glide或Picasso可以处理Bitmap的加载和缓存,避免手动管理

注意:考虑内存泄漏的问题,比如在ViewModel中持有Bitmap的引用,当不再需要时应及时释放

问题5:请解释 Activity 的四种启动模式(Launch Mode)及其使用场景。如果在实际开发中需要实现"点击通知栏消息后跳转到某个特定 Activity,并确保该 Activity 的实例唯一且不重复创建",你会如何设计?

(1)Activity启动模式详解
启动模式 行为特性 生命周期回调 典型应用场景
standard 默认模式,每次启动创建新实例,允许多个实例共存于同一任务栈 完整生命周期(onCreate) 常规页面(如新闻列表页、普通表单页)
singleTop 仅在目标位于栈顶时复用实例(否则新建),复用时会调用onNewIntent onNewIntent 防重复点击场景(如支付按钮跳转结果页、推送通知快速点击)
singleTask 在任务栈中查找匹配实例(根据taskAffinity),若存在则清空其上方所有实例并复用 onNewIntent 应用主页(要求全局唯一)、深层链接入口(如从浏览器跳转回App主流程)
singleInstance 独占一个新任务栈,且该栈中只能存在该Activity onNewIntent 独立功能模块(如系统相机、第三方登录页)
  1. taskAffinity的作用

    1. 默认与包名一致,可通过android:taskAffinity自定义。
    2. singleTask会优先匹配相同taskAffinity的任务栈,若无匹配栈则新建。
    3. 示例:浏览器应用通过不同taskAffinity管理多个网页栈。
  2. singleInstance的特殊性

    1. 完全独占任务栈,其他Activity无法进入该栈。
    2. singleInstance启动其他Activity时,会跳转到其他任务栈(可能触发系统任务管理器的多窗口效果)
  3. allowTaskReparenting的联动

    1. allowTaskReparenting="true"时,Activity可根据taskAffinity动态迁移到前台任务栈(常见于跨应用跳转后返回原应用)
(2)通知栏跳转场景的优化设计

需求分析

  • 核心目标:无论用户从通知栏点击多少次,目标Activity(如消息详情页)始终保持唯一实例,且不重复创建。

  • 潜在问题

    • 用户可能从不同入口(如主页、其他页面)触发通知跳转。
    • 需要处理返回栈逻辑,避免回退时出现空白页面。

实现方案

  • launchMode="singleTask":确保全局唯一性,复用实例时清除上方页面。
  • taskAffinity:隔离消息相关Activity到独立任务栈,避免影响主流程。
  • FLAG_ACTIVITY_NEW_TASK:强制在新任务栈中启动(配合taskAffinity)。
  • FLAG_ACTIVITY_CLEAR_TOP:若目标Activity已存在,清除其上方所有实例。
ini 复制代码
<activity
    android:name=".MessageDetailActivity"
    android:launchMode="singleTask"
    android:taskAffinity=".MessageTask" />
    
val intent = Intent(context, MessageDetailActivity::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
    putExtra("message_id", messageId)}
    val pendingIntent = PendingIntent.getActivity(
    context,
    requestCode,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

在目标Activity中处理数据更新

必须重写 onNewIntent:处理复用实例时的数据更新,避免UI状态陈旧。

kotlin 复制代码
override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    val messageId = intent?.getStringExtra("message_id")
    // 根据新的messageId刷新UI
    loadMessageDetails(messageId)
}

问题6:taskAffinity有什么作用,可以在哪些场景用到它?

  1. 定义
  • taskAffinity 是 Activity 的一个属性(可通过 AndroidManifest.xml 或代码设置),用于指定该 Activity "倾向于"归属于哪个任务栈
  • 默认情况下,所有 Activity 的 taskAffinity 继承自应用的包名(<manifest package>)。例如,包名为 com.example.app,则默认 taskAffinity="com.example.app"
  1. 核心规则
  • 任务栈匹配优先级 :当启动一个 Activity 时,系统会优先寻找与其 taskAffinity 相同的任务栈。
  • 与启动模式联动singleTasksingleInstance 启动模式的行为高度依赖 taskAffinity
  1. taskAffinity 的实践案例

案例1:浏览器应用的多标签页管理

  • 需求:每个浏览器标签页独立运行,且在最近任务列表中显示为独立卡片。
  • 实现:taskAffinity + singleTask
ini 复制代码
<!-- 每个 WebActivity 设置不同的 taskAffinity -->
<activity
    android:name=".WebActivity"
    android:taskAffinity=".WebTask_${uniqueId}"  <!-- 动态生成唯一ID -->
    android:launchMode="singleTask" />
    
// 启动新标签页时指定唯一 taskAffinity
val intent = Intent(this, WebActivity::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK
    putExtra("task_affinity", "com.example.app.WebTask_${System.currentTimeMillis()}")
}
startActivity(intent)
  • 效果:

    • 每个 WebActivity 在独立任务栈中运行。

    • 用户通过最近任务列表可快速切换不同标签页。

案例2:跨应用跳转后返回原任务栈

  • 需求:从 App A 跳转到 App B 的支付页,支付完成后返回 App A 的订单详情页,而非 App B 的主页。
  • 实现taskAffinity + allowTaskReparenting

allowTaskReparenting="true":当某个任务栈进入前台时,Activity 会从后台任务栈迁移到与其 taskAffinity匹配的前台栈。

xml 复制代码
<!-- App A 的订单详情页 -->
<activity
    android:name=".OrderDetailActivity"
    android:taskAffinity=".OrderTask"
    android:allowTaskReparenting="true" /
kotlin 复制代码
// App B 的支付页完成支付后
val intent = Intent(this, OrderDetailActivity::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent) 

效果

  • allowTaskReparenting="true" 使得 OrderDetailActivity 会从 App B 的任务栈迁移到 App A 的 .OrderTask 栈。
  • 用户返回时直接回到 App A 的订单详情页

问题7:谈谈Activity的onSaveInstanceState()和onRestoreInstanceState()的作用及调用时机

1. onSaveInstanceState(Bundle outState) 用于保存Activity的临时状态数据(如用户输入、滚动位置),以便在Activity被异常销毁后重建时恢复这些数据。

触发条件:

  • 系统主动回收:当Activity内存不足或者配置发生变更(比如旋转屏幕、语言切换)被销毁时。
  • onStop()之前调用,但不一定在 onPause()之后
scss 复制代码
onPause() → onSaveInstanceState() → onStop() → onDestroy()。

2. onRestoreInstanceState(Bundle savedInstanceState):

用于从Bundle中恢复之前保存的临时数据,通常在Activity被系统重建后调用。

触发条件:

Activity被系统重建:例如屏幕旋转后恢复。

调用时机 :在onStart()之后、onResume()之前调用。

scss 复制代码
onCreate() → onStart() → onRestoreInstanceState() → onResume()

3.与 onCreate() 的数据恢复对比

方法 Bundle参数是否可能为null 适用场景
onCreate(Bundle) 是(首次创建时为null) 通用初始化,需检查Bundle是否为null。
onRestoreInstanceState() 否(Bundle一定非null) 专用恢复数据,无需判空,代码更简洁。

问题8:如何通过Intent在Activity之间传递数据?Bundle的大小限制是多少?

1.通过 Intent putExtra() 方法直接传递基本类型数据

kotlin 复制代码
// 在源Activity中
val intent = Intent(this, TargetActivity::class.java)
intent.putExtra("key_string", "Hello, World!")
intent.putExtra("key_int", 123)
intent.putExtra("key_boolean", true)
startActivity(intent)

// 在目标Activity的onCreate()中
val stringValue = intent.getStringExtra("key_string")
val intValue = intent.getIntExtra("key_int", 0) // 第二个参数为默认值
val booleanValue = intent.getBooleanExtra("key_boolean", false)

2.复杂对象需实现 Parcelable Serializable 接口以支持序列化。

kotlin 复制代码
// 定义数据类
data class User(val name: String, val age: Int) : Parcelable

// 发送数据
val user = User("Alice", 30)
intent.putExtra("key_user", user)

// 接收数据
val receivedUser = intent.getParcelableExtra<User>("key_user")

3.Bundle的大小限制

  • Binder事务缓冲区限制 :Android通过Binder机制传输数据,其事务缓冲区大小约为1MB(不同版本可能略有差异)。
  • 实际可用空间:由于系统占用部分缓冲区,实际可传递数据应远小于1MB
  • 超过限制:会 抛出TransactionTooLargeException异常,导致应用崩溃。
  • 避免传递大数据:如图片、长文本等,可以改用以下方式:

(1)全局单例或ViewModel:在内存中共享数据。

(2)持久化存储:将数据保存到数据库或文件,传递标识符(如文件路径)。

(3)ContentProvider:跨进程大数据共享。

(4)EventBus或LiveData:组件间通信。

4.最佳实践

(1)传递最小必要数据: 传递用户ID而非整个用户对象,目标Activity再通过ID查询数据。

(2)使用ViewModel共享数据

csharp 复制代码
// 在源Activity中
val viewModel: SharedViewModel by viewModels()
viewModel.setUser(user)

// 在目标Activity中
val viewModel: SharedViewModel by viewModels()
val user = viewModel.getUser()

(3)处理配置变更: 使用onSaveInstanceState()保存临时数据,结合ViewModel保留复杂状态。

问题9:如何实现Activity与Fragment之间的通信?

  1. 定义通信接口
  • 实现方法: 在Fragment中定义通信接口,由Activity实现该接口
  • 优点:解耦,Fragment不依赖具体Activity。
  • 缺点:需手动管理接口绑定与解绑。
  1. ViewModel共享数据(推荐用于数据驱动场景)
  • 实现方法: 创建共享ViewModel,Activity与Fragment中访问ViewModel
  • 优点:生命周期安全,数据持久化,支持多Fragment共享。
  • 缺点:需引入AndroidX依赖。
  1. 直接引用Activity(简单但高耦合)
  • 实现方法: 在Fragment中获取Activity引用
  • 优点:实现简单。
  • 缺点:高耦合,Fragment无法复用。
  1. EventBus(第三方库,慎用)
  • 实现方法: 添加依赖, Fragment发送事件,Activity订阅事件
  • 优点:跨组件通信灵活。
  • 缺点:需引入第三方库,易导致内存泄漏和代码混乱。
  1. Fragment Result API(AndroidX推荐方式)
  • 优点:官方推荐,生命周期安全。
  • 缺点:仅支持单向通信,适合简单场景。
  1. 最佳实践
  • 简单数据传递 :优先使用 Fragment Result API
  • 数据共享与状态管理:选择 ViewModel

问题10:如何避免Activity的内存泄漏

常见内存泄漏场景与解决方案

(1)静态变量持有Activity引用

场景:单例类或静态变量直接或间接持有Activity的Context

csharp 复制代码
object Singleton {var context: Context? = null // 错误:可能持有Activity的引用}

解决方案

  • 使用Application Context代替Activity Context
  • 若必须使用Activity引用,使用弱引用(WeakReference):
kotlin 复制代码
class Singleton {
    private var activityRef: WeakReference<Activity>? = null
    
    fun setActivity(activity: Activity) {
        activityRef = WeakReference(activity)
    }
}

(2)非静态内部类或匿名内部类

场景:Handler、Runnable等内部类隐式持有Activity引用。

kotlin 复制代码
class MyActivity : Activity() {
    private val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            // 隐式持有外部Activity引用
        }
    }
}

解决方案:静态内部类 + 弱引用

kotlin 复制代码
private class SafeHandler(activity: MyActivity) : Handler(Looper.getMainLooper()) {
    private val activityRef = WeakReference(activity)
    
    override fun handleMessage(msg: Message) {
        val activity = activityRef.get() ?: return
        // 处理消息
    }
}

onDestroy()中移除回调

kotlin 复制代码
override fun onDestroy() {
    handler.removeCallbacksAndMessages(null)
    super.onDestroy()
}

(3) 未正确注销监听器或回调

场景:注册广播、事件总线、监听器后未及时注销

kotlin 复制代码
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    LocalBroadcastManager.getInstance(this).registerReceiver(receiver, intentFilter)
}

解决方案:onDestroy()中反注册:

kotlin 复制代码
override fun onDestroy() {
    LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
    super.onDestroy()
}

(4)异步任务未取消

场景:AsyncTask、RxJava、Coroutine等异步任务未随Activity销毁终止。

kotlin 复制代码
private var job: Job? = null

fun startTask() {
    job = CoroutineScope(Dispatchers.IO).launch {
        // 耗时操作
        withContext(Dispatchers.Main) {
            updateUI() // 若Activity已销毁,此处可能崩溃
        }
    }
}

解决方案: 使用生命周期感知的协程(lifecycleScope

kotlin 复制代码
lifecycleScope.launch {
    // 自动在onDestroy时取消
}

// 取消任务
override fun onDestroy() {
    job?.cancel()
    super.onDestroy()
}

(5) 资源未释放

场景:文件流、数据库连接、传感器服务等未关闭。

kotlin 复制代码
private lateinit var sensorManager: SensorManager
private lateinit var sensorListener: SensorEventListener

override fun onCreate(savedInstanceState: Bundle?) {
    sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
    sensorManager.registerListener(sensorListener, ...)
}

解决方法 :在onDestroy()中释放资源:

kotlin 复制代码
override fun onDestroy() {
    sensorManager.unregisterListener(sensorListener)
    super.onDestroy()
}
避免内存泄漏的黄金法则
  1. 及时释放资源 :在onDestroy()中取消任务、反注册监听器、关闭资源。
  2. 避免强引用:优先使用弱引用或生命周期感知的组件。
  3. 最小化Context使用 :尽量使用Application Context代替Activity Context
  4. 工具辅助:定期使用LeakCanary和Profiler检测潜在问题。

问题11: 解释Activity的任务栈(Task)和返回栈(Back Stack)管理机制。

  1. 任务栈(Task)

    1. 定义:一组Activity的集合,按启动顺序排列,形成一个任务栈。

    2. 特点

      • 每个任务栈有一个唯一的ID(Task ID)。
      • 任务栈可以跨应用(如从应用A跳转到应用B的某个Activity)。
      • 任务栈中的Activity可以来自不同应用(如浏览器打开多个标签页)。
  2. 返回栈(Back Stack)

    1. 定义:用户通过返回键(Back)导航的Activity栈,是任务栈的子集。

    2. 特点

      • 返回栈遵循"后进先出"(LIFO)原则。
      • 用户按返回键时,当前Activity出栈,前一个Activity恢复显示。
  3. 任务栈与返回栈的关系

  • 任务栈是物理概念,返回栈是逻辑概念。
  • 返回栈是任务栈中用户可见的部分。
  • 一个应用可以有多个任务栈(如通过FLAG_ACTIVITY_NEW_TASK启动新栈)。
  • 每个任务栈有独立的返回栈。
  1. 任务栈的管理机制

(1)创建新任务栈 : 通过Intent.FLAG_ACTIVITY_NEW_TASK启动Activity时,若目标Activity的taskAffinity与当前任务栈不同,则创建新任务栈。

ini 复制代码
val intent = Intent(this, TargetActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
  • 切换任务栈 : 通过ActivityManagermoveTaskToFront()方法将任务栈切换到前台。

(2)任务栈的销毁

  • 用户主动退出:按返回键直到栈中所有Activity出栈。
  • 系统回收:内存不足时,后台任务栈可能被系统销毁。

(3)返回栈的入栈与出栈

  • 入栈:启动新Activity时,当前Activity入栈。
  • 出栈:按返回键时,当前Activity出栈,前一个Activity恢复。

(4)返回栈的调整

  • FLAG_ACTIVITY_CLEAR_TOP:若目标Activity已在栈中,则清除其上方所有Activity并复用。
  • FLAG_ACTIVITY_REORDER_TO_FRONT:若目标Activity已在栈中,则将其移到栈顶
ini 复制代码
val intent = Intent(this, TargetActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT 或者 intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP 
startActivity(intent)
相关推荐
a3158238062 分钟前
SnapdragonCamera骁龙相机源码解析
android·数码相机·framework·高通
IT乐手17 分钟前
adb logcat 写文件乱码的解决方案
android·python
yuanbenshidiaos44 分钟前
stm32面试
stm32·嵌入式硬件·面试
欧雷殿1 小时前
再谈愚蠢的「八股文」面试
前端·人工智能·面试
xiaoduyyy1 小时前
【Android】View动画—XML动画、帧动画
android·xml
掘了1 小时前
分布式系统中如何保证崩溃一致性?
分布式·后端·面试
weixin_454102461 小时前
cordova android12+升级一些配置注意事项
android·前端·cordova
IT程序媛-桃子2 小时前
【网安面经合集】42 道高频 Web 安全面试题全解析(附原理+防御+思路)
运维·网络·安全·面试
我的div丢了肿么办2 小时前
vue3第二次传递数据方法无法获取到最新的值
前端·面试·github
兰亭序咖啡2 小时前
学透Spring Boot — 007. 加载外部配置
android·java·spring boot