写文章,其实是一个梳理思维,学习成长的过程,有意的可以一块学习,互相交流。谢谢大家!有错误或者意见还请指出共同进步。
今天在用 LeakCanary 检测内存泄露过程中,发现 ConnectivityManager 报出内存泄露的警告(context),这不是系统 Framework 的网络管理模块吗,系统服务还能泄露了。。。
通过在不同的测试手机上测试,发现此问题只在 android6.0 以上才有。5.0 或 4.4 的手机中并无此问题。大家都知道,使用这些系统服务会传入一个上下文环境,也就是我们所说的 context,在 android 的源码中,这些服务的对象是通过单例创建的,但是在 6.0 以上,它会持有我们传入的 context 的引用。所以,如果你传入的是 activity,当你的 activity 销毁时,它却持有这个 activity 的引用,就会造成内存泄露。在 5.1 的源码中,ConnectivityManager 实现为单例但不持有 Context 的引用,在 5.0 有以下版本 ConnectivityManager 既不为单例,也不持有 Context 的引用。本文就 6.0 以上说明一下。
在解释问题前,先说下 context。都知道,Context 是维持 Android 程序中各组件能够正常工作的一个核心功能类。他的继承关系如下图:
Context 的继承结构如图,它的直系子类有两个,一个是 ContextWrapper,一个是 ContextImpl。ContextWrapper 是上下文功能的封装类,而 ContextImpl 则是上下文功能的实现类。而 ContextWrapper 又有三个直接的子类,ContextThemeWrapper、Service 和 Application。其中,ContextThemeWrapper 是一个带主题的封装类,而它有一个直接子类就是 Activity。
通过上面我们可以看出,我们说的 context 就三种,activity,service,application。这三个类虽然分别各种承担着不同的作用,但它们都属于 Context 的一种,而它们具体 Context 的功能则是由 ContextImpl 类去实现的。
PS:如果您想看 ContextImpl 的源码,你会发现从 AS 看 context 的继承结构并没有 ContextImpl,这时你需要去 sdk 中 / android-sdk/sources/android-25/android/app/ContextImpl.java 去查找观看。
从上面可以看到,Activity 根本上是从 ContextWrapper 继承而来的,跟进源码,ContextWrapper 中持有一个 mBase 实例
- public
- class
- ContextWrapper
- extends
- Context
- {
- Context mBase;
- public ContextWrapper(Context base) {
- mBase = base;
- }
这个实例指向一个 ContextImpl 对象,我们可以看 ContextWrapper 中的 attachBaseContext 方法。
- protected void attachBaseContext(Context base) {
- if (mBase != null) {
- throw new IllegalStateException("Base context already set");
- }
- mBase = base;
- }
这个方法中传入了一个 base 参数,并把这个参数赋值给了 mBase 对象。而 attachBaseContext() 方法其实是由系统来调用的,它会把 ContextImpl 对象作为参数传递到 attachBaseContext() 方法当中,从而赋值给 mBase 对象,之后 ContextWrapper 中的所有方法其实都是通过这种委托的机制交由 ContextImpl 去具体实现的。
同时 ContextImpl 对象持有一个 OuterContext 对象,对于 Activity 来说,这个 OuterContext 就是 Activity 对象。
也就是构造中的那个 this。
- private ContextImpl(ContextImpl container, ActivityThread mainThread,
- LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
- Display display, Configuration overrideConfiguration, int createDisplayWithId) {
- mOuterContext = this;
- // If creator didn't specify which storage to use, use the default
- // location for application.
- if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE
- | Context.CONTEXT_DEVICE_PROTECTED_STORAGE)) == 0) {
- final File dataDir = packageInfo.getDataDirFile();
- if (Objects.equals(dataDir, packageInfo.getCredentialProtectedDataDirFile())) {
- flags |= Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
- } else if (Objects.equals(dataDir, packageInfo.getDeviceProtectedDataDirFile())) {
- flags |= Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
- }
- }
- mMainThread = mainThread;
- mActivityToken = activityToken;
- mFlags = flags;
- ......
所以您通过 context 调用 getSystemService 时最终会调用到 ContextImpl 的 getSystemService 方法。而这个 getSystemService 是通过 ContextImpl 的 SystemServiceRegistry 来完成。
代码流程如下。
第一步:
- public Object getSystemService(String name) {
- return SystemServiceRegistry.getSystemService(this, name);
- }
第二步:
- public static Object getSystemService(ContextImpl ctx, String name) {
- ServiceFetcher < ?>fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
- }
第三步:SystemServiceRegistry 提供 ConnectivityManager 的实例。
- registerService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class, new StaticOuterContextServiceFetcher<ConnectivityManager>() {
- @Override
- public ConnectivityManager createService(Context context) {
- IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
- return new ConnectivityManager(context, service); }});
- static abstract
- class
- StaticOuterContextServiceFetcher
- <
- T
- >
- implements
- ServiceFetcher
- <
- T
- > {
- private T mCachedInstance;
- @Override
- public final T getService(ContextImpl ctx) {
- if (mCachedInstance == null) {
- mCachedInstance = createService(ctx.getOuterContext());
- }
- }
- }
- public abstract T createService(Context applicationContext); }
Context 在 ConnectivityManager 创建时传入,这个 Context 在 StaticOuterContextServiceFetcher 中由 ContextImpl 对象转换为 OuterContext,如果我们的 context 是 Activity 对象,ConnectivityManager 的单实例就持有了 Activity 的实例引用。这样即使 Activity 退出后仍然无法释放,导致内存泄漏。在源码中,public abstract T createService(Context applicationContext); },人家提示的非常明显了,这个上下文环境,人家想要的是一个 Application 中提供的 context。
获取系统服务 getSystemService 时使用我们在自定义 Application 中提供的 context。
对于其他的系统服务,有些传入 activty 的引用也是不会造成内存泄露的,所以这方面还是就实际而言,有问题解决,看人家源码的提示,没问题就没问题喽,安心的写其他代码。
来源: http://blog.csdn.net/say_from_wen/article/details/76680064