浅析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