浅析 Android 的 Context

Context 是 Android App 中用的非常多的一种概念,常被翻译成上下文,这种概念在其他的技术中也有所使用,无意间点了 Context 的源码,那么就来分析分析 Context 在 Android 中到底是什么东西?

先贴段代码

1
2
3
4
5
6
7
8
9
/**
* Interface to global information about an application environment. This is
* an abstract class whose implementation is provided by
* the Android system. It
* allows access to application-specific resources and classes, as well as
* up-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {

通过注释可以看出,Android 官方对它的解释,大概可以理解为应用程序环境中全局信息的接口,它整合了许多系统级的服务,可以用来获取应用中的类、资源,以及可以进行应用程序级的调起操作,比如启动 Activity、Service 等等,而且 Context 这个类是 abstract 的,不包含具体的函数实现。

那就意思是能干很多事,反正很牛逼的样子。。。

于是搜了一下 Context 的引用,多的不能再多了

Context 的引用

结构分析

Context 类中包含了两类内容:常量抽象方法定义

由于 Context 本身是 abstract 的,所以它只负责定义需要的操作,具体的实现它并不关心,而直接继承于 Context 的 ContextWrapper 虽然并不是使用 abstract 修饰,但并没有做实际的实现,只是做了简单的包装,以便更好的呈现给 Application、Service 这样的类。我们所能看到的对 Context 做第一层实现的,应该是位于android.app包下的ContextImpl

Context 的继承关系

于是先分析 ContextImpl 中的成员变量(包括静态的、final 的、public 的、private 的),以下源码中是 ContextImpl 的所有成员变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// ContextImpl.java

/**
* Map from package name, to preference name, to cached preferences.
*/
@GuardedBy("ContextImpl.class")
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;

/**
* Map from preference name to generated path.
*/
@GuardedBy("ContextImpl.class")
private ArrayMap<String, File> mSharedPrefsPaths;

final @NonNull ActivityThread mMainThread;
final @NonNull LoadedApk mPackageInfo;
private @Nullable ClassLoader mClassLoader;

private final @Nullable IBinder mActivityToken;

private final @Nullable UserHandle mUser;

private final ApplicationContentResolver mContentResolver;

private final String mBasePackageName;
private final String mOpPackageName;

private final @NonNull ResourcesManager mResourcesManager;
private @NonNull Resources mResources;
private @Nullable Display mDisplay; // may be null if default display

private final int mFlags;

private Context mOuterContext;
private int mThemeResource = 0;
private Resources.Theme mTheme = null;
private PackageManager mPackageManager;
private Context mReceiverRestrictedContext = null;

// The name of the split this Context is representing. May be null.
private @Nullable String mSplitName = null;

private final Object mSync = new Object();

@GuardedBy("mSync")
private File mDatabasesDir;
@GuardedBy("mSync")
private File mPreferencesDir;
@GuardedBy("mSync")
private File mFilesDir;
@GuardedBy("mSync")
private File mNoBackupFilesDir;
@GuardedBy("mSync")
private File mCacheDir;
@GuardedBy("mSync")
private File mCodeCacheDir;

// The system service cache for the system services that are cached per-ContextImpl.
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();

根据其命名和类型,可以分析出基本的作用:

sSharedPrefsCache:用于管理 SharedPreference 的缓存信息

mSharedPrefsPaths:用于保存 SharedPreference 文件存储路径

mMainThread:用于操作 Activity 等组件的主线程

mPackageInfo:当前加载的 APK 的 PackageInfo,包括包名、版本号等

mClassLoader:用于加载 Dex 类的类加载器,类似其他 Java 项目中的 ClassLoader,Android 中做插件化的时候会用到

mActivityToken:一个 Binder,可以用于 IPC 通信

mUser:用于管理用户数据,配合 ContentResovler 来使用

mContentResolver:应用程序级的 ContentResolver,用来在不同进程间进行数据交换

mBasePackageName:基本包名

mOpPackageName:好像又是个包名。。。

mResourcesManager:系统资源服务,可以用来获取图片、字符串等资源,用的很多

mResources:系统资源,如主题、颜色定义等

mDisplay:系统显示器/屏幕信息,比如获取分辨率、PPI

mFlags:标志位,用来区分 Context 的类别

mOuterContext:外部使用的 Context

mThemeResource:主题资源 Id

mTheme:APP 使用的主题

mPackageManager:包管理器,可以用来获取安装的 APP 的相关信息

mReceiverRestrictedContext:应该是用于 BroadcastReceiver 的 Context

mSplitName:推测也是用于区别 Context 的
mSync:用于设置同步功能,用来保证文件操作的线程安全

mDatabasesDir:用于存储数据库文件的目录路径

mPreferencesDir:用于存储 SharedPreference 文件的目录路径

mFilesDir:用于存储一般文件的目录路径

mNoBackupFilesDir:用于存储不用于自动备份文件的目录路径

mCacheDir:用于存储缓存文件的目录路径

mCodeCacheDir:用于存储代码缓存文件(主要用于加载 Dex)的目录路径

其中,@GuardedBy 注解用来处理多线程的保护问题,来指明在多线程环境中,该对象被哪个加了同步锁的对象来保护

在分析了以上成员变量的用途以后,ContextImpl 基本职责也就明确了,剩下的成员方法和静态方法,都是围绕这些成员展开的操作,也就是一些我们熟悉的 Context 可以进行的操作,比如:startActivity、startService、getExternalCacheDir、getDatabasesDir、getMainLooper、getResources、getTheme、checkPermission 等等

不过,除了这些成员方法,有几个静态方法比较重要:

createSystemContext、createSystemUiContext、createAppContext、createActivityContext

虽然没有看到直接对这几个方法的引用,但拿 Activity 的启动流程来举例,ActivityManagerService(AMS)使用 zygote 进程先启动一个 ActivityThread,在进行 attach 的时候,会调用 ContextImpl 的静态方法,来创建所需要的 Context,同样,其他组件 Application、Service 等类中的 Context,也是一样的道理,所以,我们在 App 中使用的各类 Context,其实归根结底是由 ContextImpl 创建出来的,可见 ContextImpl 的重要!

Context 的内存泄露

众所周知,Android 中的内存泄露,很多数情况都是 Context 造成的,根据上面对其结构和用途的分析,可以推测一下几点原因:

  1. 涉及各种系统、APP 资源,功能过于强大,导致使用的地方太多,在流程和引用关系上造成混乱,比如:异步的网络请求、View 动画等,在流程上处理不当,导致无法释放

  2. 杂乱无章的传值,让很多对象都 Hold 一份 Context,开辟了过多无法回收的资源

  3. Drawable 对象、Bitmap 对象回收不及时,甚至与 View 死死绑定

  4. 单例的滥用,导致 Context 长期被引用无法释放

解决方案:

  1. 没必要传值的时候,尽量使用 Application 的 Context,这样保证 Context 即可以全局使用,又不会创建多份

  2. 在与 Activity 等组件耦合的情况下,必须要使用 Activity 的 Context 的时候,考虑使用弱引用,避免循环持有 Context

总结

总得来说,Context 在 Android 中就是对一系列系统服务接口的封装,包括:内部资源、包、类加载、I/O 操作、权限、主线程、IPC 和组件启动等操作的管理。其结构比较简单,但方法很多,涉及的操作也很多,在使用时应当特别注意内存泄露和多线程问题,以免引发冲突或者严重的 bug