【Android面试】Context专题

文章目录

      • [一、基础必问(开场必答 · 送分题)](#一、基础必问(开场必答 · 送分题))
      • [二、核心原理(中高级必问 · 难点)](#二、核心原理(中高级必问 · 难点))
      • [三、使用场景 & 禁忌(高频实战)](#三、使用场景 & 禁忌(高频实战))
      • [四、内存泄漏(大厂最爱 · 加分项)](#四、内存泄漏(大厂最爱 · 加分项))
      • 五、源码级深度(阿里/字节/腾讯高级面)
      • 六、综合开放题(定级用)

一、基础必问(开场必答 · 送分题)

1. 什么是 Context?它的作用是什么?

  • 标准答案
    Context 直译为"上下文",是一个抽象类 ,其具体实现是 ContextImpl。它是应用全局功能的访问接口,充当了应用核心组件(Activity、Service 等)与系统资源(LayoutInflater、PackageManager、ContentResolver 等)之间的桥梁。
    • 核心作用:获取应用资源(字符串、图片、布局)、启动组件(Activity/Service)、发送广播/注册接收器、操作数据库(SharedPreferences/ContentProvider)、创建 View(LayoutInflater)。

2. Context、Activity、Application、ContextWrapper、ContextImpl 之间是什么关系?

  • 标准答案
    • Context:抽象基类,定义了通用的接口和常量。
    • ContextImpl具体实现类,真正执行业务逻辑。
    • ContextWrapper :包装类,持有一个 ContextImpl 的引用,用于装饰或代理 Context 的行为。
    • Application/Activity/Service :它们都继承自 ContextThemeWrapperContextWrapper 的子类),是 Context 的具体使用场景
    • 关系总结:装饰模式。Wrapper 持有真实的 Impl 对象,调用时转发给 Impl 执行。

3. 一个 App 里有多少种 Context?分别有多少个实例?

  • 标准答案
    主要分为三种类型,实例数量计算公式为:

    1. Application Context:1 个(全局单例)。
    2. Service Context:Service 的数量 = 1 个/个 Service 实例。
    3. Activity Context:Activity 的数量 = 1 个/个 Activity 实例(非配置变更情况下)。
    • 总数公式:App 总 Context 数 = 1 (Application) + Service 数量 + Activity 数量。

4. Application、Activity、Service 这三种 Context 有什么区别?

  • 标准答案
    • 生命周期不同
      • Application:伴随整个 App 进程生命周期。
      • Activity:伴随 Activity 的生命周期(可见/销毁)。
      • Service:伴随 Service 的生命周期。
    • 功能限制不同
      • Application/Service 无法启动 Dialog(无 Token)、无法加载主题资源(无 Theme)。
      • Activity 是顶级上下文,能处理 UI 交互、主题、窗口。

5. 哪些地方能获取 Context?哪些不能直接用 this?

  • 标准答案
    • 能获取:Activity、Service、Application、ContextWrapper 内部。
    • 不能直接用 thisBroadcastReceiver (onReceive 返回后对象即销毁)、AsyncTask (匿名内部类持有 Activity 引用)、普通 Java 类(无上下文环境)。

二、核心原理(中高级必问 · 难点)

6. Context 的创建流程是什么?

  • 标准答案
    • Application :在 ActivityThreadhandleBindApplication 中,通过 LoadedApk 创建 ContextImpl,然后创建 Application 并 attach。
    • Activity :通过 ActivityThreadperformLaunchActivity 创建 ContextImpl,再创建 Activity 并 attach 上下文。
    • 核心逻辑 :由 ActivityThread 统一管理,通过 LoadedApk 获取类加载器,实例化 ContextImpl,并调用 attachBaseContext 完成关联。

7. ContextImpl 是什么?它和 ContextWrapper 的关系?

  • 标准答案
    • ContextImpl :Context 的真正实体 ,所有的方法实现(如 startActivitygetContentResolver)都在这里。
    • 关系代理模式 。ContextWrapper 是包装类,它内部有一个 mBase 成员变量(类型 Context),通常指向 ContextImpl 的实例。所有 Wrapper 的方法调用最终都会转发给 mBase (Impl) 来执行。

8. 为什么 Activity 可以作为 Context,又能作为 View 容器?

  • 标准答案
    因为 Activity 继承树是:ContextThemeWrapper -> ContextWrapper -> Context
    它实现了 LayoutInflater.Factory2 接口。当加载布局时,Activity 会回调 onCreateView 自动给 View 注入 Context。同时,Activity 持有 Window 对象,Window 管理着整个窗口视图,因此 Activity 天然具备作为 View 容器的能力。

9. getApplicationContext() 和 getApplication() 区别?

  • 标准答案
    • 返回类型getApplication() 返回的是 Application 对象(或其子类)。getApplicationContext() 返回的是 Context 对象。
    • 用途getApplication() 用于获取全局应用对象,常用于持有全局变量或生命周期回调。getApplicationContext() 是获取 Context 的一种方式,通常用于需要 Context 但不涉及 UI 操作的场景。
    • 生命周期:两者生命周期一致,都是全局。

10. getBaseContext() 是什么?什么时候用?

  • 标准答案
    getBaseContext() 返回的是 ContextWrapper 中持有的 底层真实 Context (通常是 ContextImpl)。
    • 使用场景 :极少直接使用。通常在自定义 Wrapper 或者需要绕过包装层、直接操作系统服务时使用。面试回答应强调:一般不要直接使用,以免破坏 Context 的装饰链。

三、使用场景 & 禁忌(高频实战)

11. 什么时候用 Application Context,什么时候必须用 Activity Context?

  • 标准答案
    • 用 Application Context
      1. 需要全局单例对象(如 ImageLoader、网络库)。
      2. 不需要 UI 交互的操作(如发送广播、操作数据库)。
      3. 生命周期长于当前组件(如静态变量引用)。
    • 必须用 Activity Context
      1. 显示 Dialog / PopupWindow(必须需要 Activity 的 Token)。
      2. 加载布局资源 (Inflate):涉及主题样式。
      3. 启动 Activity(虽然 Application 也能启,但需要设置 Flag,规范做法是用 Activity)。
      4. 资源加载(涉及主题 Theme)。

12. 弹 Toast、启动 Activity、创建 Dialog、获取资源、SharedPreferences 分别用哪种 Context?为什么?

  • 标准答案
    • ToastApplicationActivity 均可(Application 更推荐,避免内存泄漏)。
    • 启动 ActivityActivity 最佳。Application 启动需设置 NEW_TASK Flag,可能导致任务栈异常。
    • 创建 Dialog必须是 Activity。Application 没有 WindowToken,无法弹出。
    • 获取资源ApplicationActivity 均可。如果是动态主题资源需用 Activity。
    • SharedPreferencesApplicationActivity 均可。

13. 为什么不能用 Application Context 启动非标准 launchMode 的 Activity?

  • 标准答案
    因为 Application Context 没有任务栈(Task Stack)。
    • 非标准启动模式(如 singleTasksingleInstance)的 Activity 通常需要存在于特定的任务栈中。
    • 使用 Application Context 启动时,系统会为该 Activity 创建一个新的任务栈(除非设置 Flag),这破坏了应用的正常返回栈逻辑,导致按返回键无法回到原界面。

14. 为什么不能用 Application Context 弹 Dialog?

  • 标准答案
    Dialog 的显示依赖于 WindowToken 。Activity 拥有 WindowToken,而 Application Context 只是一个应用级的上下文,不具备窗口令牌(WindowToken),系统无法将 Dialog 附着到屏幕上,会抛出 WindowManager$BadTokenException

15. 为什么不能用 Application Context 去 inflate 某些带主题的布局?

  • 标准答案
    布局加载(Inflate)涉及资源解析。带主题的布局(如 @style/MyDialog)依赖于 Activity 的主题(Theme)。Application Context 只有默认的系统主题,没有应用自定义主题,会导致样式加载失败或属性读取错误。

四、内存泄漏(大厂最爱 · 加分项)

16. 单例模式中为什么不能直接传 Activity Context?会发生什么?

  • 标准答案
    单例的生命周期长于 Activity(全局单例)。
    • 如果传入 Activity Context,单例就会持有 Activity 的引用。当 Activity 销毁时,由于单例仍持有引用,GC 无法回收 Activity,导致内存泄漏
    • 后果:Activity 及其持有的 View、资源等对象无法被销毁,严重时导致 OOM(内存溢出)。

17. 静态变量持有 Context 为什么会泄漏?

  • 标准答案
    静态变量的生命周期是整个进程,与 Application 同生共死。
    如果 Context(如 Activity)被静态变量引用,只要进程未退出,Activity 就无法被回收。即使屏幕旋转(配置变更)导致旧 Activity 销毁,新 Activity 重建时也不会释放旧实例,造成严重泄漏。

18. AsyncTask / Handler / 匿名内部类持有 Context 为什么会泄漏?

  • 标准答案
    Java 中,非静态内部类/匿名内部类 会隐式持有外部类(Activity)的引用。
    • Handler:如果发送了延迟消息,消息队列(MessageQueue)会持有 Handler,进而持有 Activity。延迟期间 Activity 销毁会导致泄漏。
    • AsyncTask:后台任务未结束时,持有 Activity 引用。
    • 解决 :使用 Static 内部类 + WeakReference (弱引用) 持有 Context。

19. 如何安全地在单例里使用 Context?

  • 标准答案
    必须使用 Application Context
    代码示例:

    java 复制代码
    // 在 Application 或 初始化时获取
    context = getApplicationContext(); 
    // 或者 Application 级别 Context

    原因:Application Context 是全局单例,生命周期与单例一致,不会导致泄漏。

20. 如何判断一段代码是否会因为 Context 导致内存泄漏?

  • 标准答案
    1. 看引用链:检查是否有长生命周期对象(如 Static 变量、Thread、单例、系统服务)短生命周期对象(Activity/Fragment)。
    2. 看内部类:检查是否使用了非静态内部类、匿名内部类。
    3. 看生命周期:检查 Handler 是否移除了回调,AsyncTask 是否在销毁时取消。
    4. 工具检测 :使用 Android Studio ProfilerLeakCanary 进行检测。

五、源码级深度(阿里/字节/腾讯高级面)

21. Context 的 getResources()、getTheme()、startActivity() 最终是谁实现的?

  • 标准答案
    最终都是由 ContextImpl 实现的。
    • getResources() -> ContextImpl.getResources()
    • getTheme() -> ContextImpl.getTheme()
    • startActivity() -> ContextImpl.startActivity()
      所有的调用都是 Wrapper 转发给 Impl 后,由 Impl 与系统服务(如 ActivityManagerService)进行 Binder 通信完成的。

22. ActivityThread、LoadedApk、Context 之间的关系?

  • 标准答案
    • ActivityThread:应用的主线程管理类,入口,管理四大组件生命周期。
    • LoadedApk:代表一个已加载的 APK 文件,缓存了 Application、类加载器、资源等信息。
    • 关系ActivityThread 持有 LoadedApk 的引用。LoadedApk 负责创建 ContextImpl。组件(Activity/Service)的 Context 是由 ActivityThread 通过 LoadedApk 辅助创建的。

23. 为什么不同 Context 获取的 Resources 可能不一样?

  • 标准答案
    因为每个 Context 都有自己的 Configuration (配置)和 Theme
    • Activity Context 的 Resources 会响应屏幕旋转、语言切换等配置变更。
    • Application Context 的 Resources 通常是初始配置。
    • 因此,Application Context 获取的资源可能无法适配 UI 组件的动态配置变化。

24. Context 是如何实现"跨进程不共享"的?

  • 标准答案
    Context 与 进程 绑定。每个进程有自己独立的 ActivityThreadLoadedApkContextImpl 实例。
    虽然 Application 是单例,但它是 进程内单例
    如果应用开启了多进程模式(Android:process),每个进程都会有一个独立的 Application 对象和 Context 树,进程之间无法直接共享 Context,必须通过 Binder 或序列化方式通信。

25. 如何自己构造一个 Context?有什么风险?

  • 标准答案
    可以通过 ContextThemeWrapper 手动构造。
    代码示例:

    java 复制代码
    ContextThemeWrapper wrapper = new ContextThemeWrapper(this, R.style.MyTheme);
    LayoutInflater inflater = LayoutInflater.from(wrapper);

    风险

    1. 配置同步问题:手动创建的 Context 无法自动响应系统配置变更(如黑夜模式、语言)。
    2. 组件依赖:不能用于启动需要特定宿主 Context 的组件(如 Activity)。
    3. 资源混淆:容易导致资源 ID 查找错误。

六、综合开放题(定级用)

26. 项目中你因为 Context 踩过什么坑?怎么解决的?

  • 答题思路(话术)
    • 案例:比如在 Fragment 中使用 getActivity() 导致空指针,或者在静态工具类中传了 Activity Context 导致内存泄漏。
    • 解决
      1. 空指针:通过 isAdded() 判断 Fragment 生命周期,或使用 WeakReference 持有 Activity。
      2. 泄漏:统一使用 getApplicationContext() 传入单例,或者使用 Lifecycle 管理。
    • 总结:强调"生命周期匹配"原则。

27. 一个第三方库要求传入 Context,你怎么设计才安全、不泄漏、兼容所有场景?

  • 答题思路(话术)
    1. 优先 Application :文档说明必须传入 Application Context(getApplicationContext())。
    2. 使用 Lifecycle :如果库需要生命周期回调,要求传入 LifecycleOwner,通过 registerActivityLifecycleCallbacks 管理,而不是强持 Context。
    3. 弱引用 :如果必须持有,使用 WeakReference
    4. 隔离 Context:库内部不直接使用传入的 Context 启动组件,而是通过 Intent 跳转,由上层 App 处理。

28. Jetpack 里 ViewModel、Lifecycle 是如何规避 Context 泄漏的?

  • 答题思路(话术)
    • ViewModel :存储在 ViewModelStore 中,生命周期跨越 Activity 旋转,且不直接持有 View 或 Activity 引用。
    • Lifecycle :采用观察者模式。组件(如 Activity)销毁时,会主动通知 LifecycleObserver,解除注册,避免引用滞留。
    • 核心:打破了强引用链,实现了自动解耦。

29. Jetpack Compose 里的 LocalContext 是什么原理?

  • 答题思路(话术)
    LocalContext 是 Composition Local(组合本地化)。
    它通过 CompositionContext 在 Compose 树中向下传递。
    • 原理:类似于 React 的 Context,提供了一种在组件树中自上而下传递数据的方式,无需手动层层传递参数。
    • 获取LocalContext.current
    • 作用:在 Compose 环境中获取当前的 Context 环境,通常用于兼容传统 View 组件或系统资源。

30. 如何做一个全局安全的 Context 提供者?

  • 答题思路(话术)
    不建议搞全局静态 Context。如果必须提供,推荐写法如下:
    1. Application 初始化:在 Application onCreate 中保存 Application Context。

    2. 单例封装

      java 复制代码
      public class AppContextProvider {
          private static Context sAppContext;
          public static void init(Context context) {
              sAppContext = context.getApplicationContext();
          }
          public static Context get() {
              return sAppContext;
          }
      }
相关推荐
江湖十年10 小时前
Go 并发控制:sync.Pool 详解
后端·面试·go
rainbow72424410 小时前
AI人才简历评估选型:技术面试、代码评审与项目复盘的综合运用方案
人工智能·面试·职场和发展
张张123y10 小时前
RAG从0到1学习:技术架构、项目实践与面试指南
人工智能·python·学习·面试·架构·langchain·transformer
努力学算法的蒟蒻12 小时前
day115(3.17)——leetcode面试经典150
面试·职场和发展
Baihai_IDP12 小时前
OpenClaw 架构详解 · 第一部分:控制平面、会话管理与事件循环
人工智能·面试·llm
张李浩13 小时前
Leetcode 15三题之和
算法·leetcode·职场和发展
三少爷的鞋13 小时前
从 MVVM 到 MVI:为什么说 MVVM 的 UI 状态像“网”,而 MVI 像“一条线”?
android
蜡台14 小时前
Flutter 安装配置
android·java·flutter·环境变量
阿乐艾官14 小时前
【HBase列式存储数据库】
android·数据库·hbase