文章目录
-
-
- [一、基础必问(开场必答 · 送分题)](#一、基础必问(开场必答 · 送分题))
- [二、核心原理(中高级必问 · 难点)](#二、核心原理(中高级必问 · 难点))
- [三、使用场景 & 禁忌(高频实战)](#三、使用场景 & 禁忌(高频实战))
- [四、内存泄漏(大厂最爱 · 加分项)](#四、内存泄漏(大厂最爱 · 加分项))
- 五、源码级深度(阿里/字节/腾讯高级面)
- 六、综合开放题(定级用)
-
一、基础必问(开场必答 · 送分题)
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 :它们都继承自
ContextThemeWrapper(ContextWrapper的子类),是 Context 的具体使用场景。 - 关系总结:装饰模式。Wrapper 持有真实的 Impl 对象,调用时转发给 Impl 执行。
3. 一个 App 里有多少种 Context?分别有多少个实例?
-
标准答案 :
主要分为三种类型,实例数量计算公式为:- Application Context:1 个(全局单例)。
- Service Context:Service 的数量 = 1 个/个 Service 实例。
- 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 内部。
- 不能直接用 this :BroadcastReceiver (onReceive 返回后对象即销毁)、AsyncTask (匿名内部类持有 Activity 引用)、普通 Java 类(无上下文环境)。
二、核心原理(中高级必问 · 难点)
6. Context 的创建流程是什么?
- 标准答案 :
- Application :在
ActivityThread的handleBindApplication中,通过LoadedApk创建ContextImpl,然后创建 Application 并 attach。 - Activity :通过
ActivityThread的performLaunchActivity创建 ContextImpl,再创建 Activity 并 attach 上下文。 - 核心逻辑 :由
ActivityThread统一管理,通过LoadedApk获取类加载器,实例化ContextImpl,并调用attachBaseContext完成关联。
- Application :在
7. ContextImpl 是什么?它和 ContextWrapper 的关系?
- 标准答案 :
- ContextImpl :Context 的真正实体 ,所有的方法实现(如
startActivity、getContentResolver)都在这里。 - 关系 :代理模式 。ContextWrapper 是包装类,它内部有一个
mBase成员变量(类型 Context),通常指向 ContextImpl 的实例。所有 Wrapper 的方法调用最终都会转发给 mBase (Impl) 来执行。
- ContextImpl :Context 的真正实体 ,所有的方法实现(如
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 :
- 需要全局单例对象(如 ImageLoader、网络库)。
- 不需要 UI 交互的操作(如发送广播、操作数据库)。
- 生命周期长于当前组件(如静态变量引用)。
- 必须用 Activity Context :
- 显示 Dialog / PopupWindow(必须需要 Activity 的 Token)。
- 加载布局资源 (Inflate):涉及主题样式。
- 启动 Activity(虽然 Application 也能启,但需要设置 Flag,规范做法是用 Activity)。
- 资源加载(涉及主题 Theme)。
- 用 Application Context :
12. 弹 Toast、启动 Activity、创建 Dialog、获取资源、SharedPreferences 分别用哪种 Context?为什么?
- 标准答案 :
- Toast :Application 或 Activity 均可(Application 更推荐,避免内存泄漏)。
- 启动 Activity :Activity 最佳。Application 启动需设置
NEW_TASKFlag,可能导致任务栈异常。 - 创建 Dialog :必须是 Activity。Application 没有 WindowToken,无法弹出。
- 获取资源 :Application 或 Activity 均可。如果是动态主题资源需用 Activity。
- SharedPreferences :Application 或 Activity 均可。
13. 为什么不能用 Application Context 启动非标准 launchMode 的 Activity?
- 标准答案 :
因为 Application Context 没有任务栈(Task Stack)。- 非标准启动模式(如
singleTask、singleInstance)的 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 导致内存泄漏?
- 标准答案 :
- 看引用链:检查是否有长生命周期对象(如 Static 变量、Thread、单例、系统服务)短生命周期对象(Activity/Fragment)。
- 看内部类:检查是否使用了非静态内部类、匿名内部类。
- 看生命周期:检查 Handler 是否移除了回调,AsyncTask 是否在销毁时取消。
- 工具检测 :使用 Android Studio Profiler 或 LeakCanary 进行检测。
五、源码级深度(阿里/字节/腾讯高级面)
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 与 进程 绑定。每个进程有自己独立的ActivityThread、LoadedApk和ContextImpl实例。
虽然 Application 是单例,但它是 进程内单例 。
如果应用开启了多进程模式(Android:process),每个进程都会有一个独立的 Application 对象和 Context 树,进程之间无法直接共享 Context,必须通过 Binder 或序列化方式通信。
25. 如何自己构造一个 Context?有什么风险?
-
标准答案 :
可以通过ContextThemeWrapper手动构造。
代码示例:javaContextThemeWrapper wrapper = new ContextThemeWrapper(this, R.style.MyTheme); LayoutInflater inflater = LayoutInflater.from(wrapper);风险 :
- 配置同步问题:手动创建的 Context 无法自动响应系统配置变更(如黑夜模式、语言)。
- 组件依赖:不能用于启动需要特定宿主 Context 的组件(如 Activity)。
- 资源混淆:容易导致资源 ID 查找错误。
六、综合开放题(定级用)
26. 项目中你因为 Context 踩过什么坑?怎么解决的?
- 答题思路(话术) :
- 案例:比如在 Fragment 中使用 getActivity() 导致空指针,或者在静态工具类中传了 Activity Context 导致内存泄漏。
- 解决 :
- 空指针:通过
isAdded()判断 Fragment 生命周期,或使用WeakReference持有 Activity。 - 泄漏:统一使用
getApplicationContext()传入单例,或者使用 Lifecycle 管理。
- 空指针:通过
- 总结:强调"生命周期匹配"原则。
27. 一个第三方库要求传入 Context,你怎么设计才安全、不泄漏、兼容所有场景?
- 答题思路(话术) :
- 优先 Application :文档说明必须传入 Application Context(
getApplicationContext())。 - 使用 Lifecycle :如果库需要生命周期回调,要求传入 LifecycleOwner,通过
registerActivityLifecycleCallbacks管理,而不是强持 Context。 - 弱引用 :如果必须持有,使用
WeakReference。 - 隔离 Context:库内部不直接使用传入的 Context 启动组件,而是通过 Intent 跳转,由上层 App 处理。
- 优先 Application :文档说明必须传入 Application Context(
28. Jetpack 里 ViewModel、Lifecycle 是如何规避 Context 泄漏的?
- 答题思路(话术) :
- ViewModel :存储在
ViewModelStore中,生命周期跨越 Activity 旋转,且不直接持有 View 或 Activity 引用。 - Lifecycle :采用观察者模式。组件(如 Activity)销毁时,会主动通知 LifecycleObserver,解除注册,避免引用滞留。
- 核心:打破了强引用链,实现了自动解耦。
- ViewModel :存储在
29. Jetpack Compose 里的 LocalContext 是什么原理?
- 答题思路(话术) :
LocalContext是 Composition Local(组合本地化)。
它通过 CompositionContext 在 Compose 树中向下传递。- 原理:类似于 React 的 Context,提供了一种在组件树中自上而下传递数据的方式,无需手动层层传递参数。
- 获取 :
LocalContext.current。 - 作用:在 Compose 环境中获取当前的 Context 环境,通常用于兼容传统 View 组件或系统资源。
30. 如何做一个全局安全的 Context 提供者?
- 答题思路(话术) :
不建议搞全局静态 Context。如果必须提供,推荐写法如下:-
Application 初始化:在 Application onCreate 中保存 Application Context。
-
单例封装 :
javapublic class AppContextProvider { private static Context sAppContext; public static void init(Context context) { sAppContext = context.getApplicationContext(); } public static Context get() { return sAppContext; } }
-