浅析 Android 的 Context
Context 是 Android App 中用的非常多的一种概念,常被翻译成上下文,这种概念在其他的技术中也有所使用,无意间点了 Context 的源码,那么就来分析分析 Context 在 Android 中到底是什么东西?
先贴段代码
1 | /** |
通过注释可以看出,Android 官方对它的解释,大概可以理解为应用程序环境中全局信息的接口,它整合了许多系统级的服务,可以用来获取应用中的类、资源,以及可以进行应用程序级的调起操作,比如启动 Activity、Service 等等,而且 Context 这个类是 abstract 的,不包含具体的函数实现。
那就意思是能干很多事,反正很牛逼的样子。。。
于是搜了一下 Context 的引用,多的不能再多了
结构分析
Context 类中包含了两类内容:常量、抽象方法定义
由于 Context 本身是 abstract 的,所以它只负责定义需要的操作,具体的实现它并不关心,而直接继承于 Context 的 ContextWrapper 虽然并不是使用 abstract 修饰,但并没有做实际的实现,只是做了简单的包装,以便更好的呈现给 Application、Service 这样的类。我们所能看到的对 Context 做第一层实现的,应该是位于android.app包下的ContextImpl
于是先分析 ContextImpl 中的成员变量(包括静态的、final 的、public 的、private 的),以下源码中是 ContextImpl 的所有成员变量:
1 | // ContextImpl.java |
根据其命名和类型,可以分析出基本的作用:
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 造成的,根据上面对其结构和用途的分析,可以推测一下几点原因:
涉及各种系统、APP 资源,功能过于强大,导致使用的地方太多,在流程和引用关系上造成混乱,比如:异步的网络请求、View 动画等,在流程上处理不当,导致无法释放
杂乱无章的传值,让很多对象都 Hold 一份 Context,开辟了过多无法回收的资源
Drawable 对象、Bitmap 对象回收不及时,甚至与 View 死死绑定
单例的滥用,导致 Context 长期被引用无法释放
解决方案:
没必要传值的时候,尽量使用 Application 的 Context,这样保证 Context 即可以全局使用,又不会创建多份
在与 Activity 等组件耦合的情况下,必须要使用 Activity 的 Context 的时候,考虑使用弱引用,避免循环持有 Context
总结
总得来说,Context 在 Android 中就是对一系列系统服务接口的封装,包括:内部资源、包、类加载、I/O 操作、权限、主线程、IPC 和组件启动等操作的管理。其结构比较简单,但方法很多,涉及的操作也很多,在使用时应当特别注意内存泄露和多线程问题,以免引发冲突或者严重的 bug