ConnectivityManager导致的内存泄漏

2,593 阅读4分钟

前言

最近在查看线上上报上来的卡顿数据,发生卡顿时长较久的的卡顿都是发生在内存不足的时候,基本上系统可用内存都只有几百k或者1、2M,于是就怀疑我们应用存在内存泄漏。拿了手上刷了Android 12的Pixel 3开始排查

奇怪的引用链

按照我们应用之前的尿性,内存泄漏经常是在接力房这个页面。跑了一遍进房 → 上麦 → 回到首页的场景,demp出内存情况,果然有泄漏

截屏2021-10-12 下午7.35.02 (1).png

看了这5个泄漏点,其实都是跟RoomLiveGameActivity相关,接着看下是谁还在持有RoomLiveGameActivity

截屏2021-10-12 下午7.37.01.png

纳尼,ConnectivityManager竟然持有了RoomLiveGameActivity而导致内存泄漏。没办法,又要开始苦逼的看源码了。

分析

一般我们在获取ConnectivityManager都是这样获取的

Activity.java

ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

而Activity是继承于ContextWrapper,最终调用到ContextWrapper的getSystemService方法

ContextWrapper.java

@Override
public Object getSystemService(String name) {
    // 这里的mBase是ContextImpl,后面会讲到
    return mBase.getSystemService(name);
}

ContextImpl.java

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

所有需要用到的系统服务,都是从SystemServiceRegistry获取的,然后通过各个Manager调用binder接口与系统通信,我们看看SystemServiceRegistry的实现:

SystemServiceRegistry.java

public static Object getSystemService(ContextImpl ctx, String name) {
    // ServiceFetcher是一个接口,通过服务名匹配相应的Fetcher,获取对应的Manager
    final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    ...
    final Object ret = fetcher.getService(ctx);
    ...
    return ret;
}
 
static {
    // 在static块中,初始化了CONNECTIVITY_SERVICE对应的Fetcher
    ConnectivityFrameworkInitializer.registerServiceWrappers();
    ...
}

ConnectivityFrameworkInitializer.java

public static void registerServiceWrappers() {
    // 这里又调回SystemServiceRegistry的静态方法,注册CONNECTIVITY_SERVICE对应的服务
    // 最后一个参数是一个lambda表达式,它是一个ContextAwareServiceProducerWithBinder接口
    SystemServiceRegistry.registerContextAwareService(
                Context.CONNECTIVITY_SERVICE,
                ConnectivityManager.class,
                (context, serviceBinder) -> {
                    IConnectivityManager icm = IConnectivityManager.Stub.asInterface(serviceBinder);
                    return new ConnectivityManager(context, icm);
                }
    );
}
 
public interface ContextAwareServiceProducerWithBinder<TServiceClass> {
    
    @NonNull
    TServiceClass createService(@NonNull Context context, @NonNull IBinder serviceBinder);
}

SystemServiceRegistry.java

public static <TServiceClass> void registerContextAwareService(
        @NonNull String serviceName, @NonNull Class<TServiceClass> serviceWrapperClass,
        @NonNull ContextAwareServiceProducerWithBinder<TServiceClass> serviceProducer) {
 
    // 可以看到CONNECTIVITY_SERVICE对应的是CachedServiceFetcher,而CachedServiceFetcher的createService又调用了ContextAwareServiceProducerWithBinder的createService方法(就是上面的lambda表达式)
    // context是从ContextImpl获取出来的OuterContext,这个很重要(敲黑板),后面会讲到
    registerService(serviceName, serviceWrapperClass,
            new CachedServiceFetcher<TServiceClass>() {
                @Override
                public TServiceClass createService(ContextImpl ctx)
                        throws ServiceNotFoundException {
                    // 这里传一个ContextImpl的OuterContext给ConnectivityManager做初始化用
                    return serviceProducer.createService(
                            ctx.getOuterContext(),
                            ServiceManager.getServiceOrThrow(serviceName));
                }});
}
 
 
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
  
    @Override
    public final T getService(ContextImpl ctx) {
        // 这里先检查有没有相应的缓存,有的话直接返回
        final Object[] cache = ctx.mServiceCache;
        T service = (T) cache[mCacheIndex];
        if (service != null) {
            return service;
        }
 
        // 没有找到缓存的话,通过createService创建
        ...
        T service = null;
        @ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;
        try {
            service = createService(ctx);
            newState = ContextImpl.STATE_READY;
        } catch (ServiceNotFoundException e) {
            onServiceNotFound(e);
        }
        return service;
    }
 
    public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}

ConnectivityManager.java

public class ConnectivityManager {
    private static ConnectivityManager sInstance;
     
    public ConnectivityManager(Context context, IConnectivityManager service) {
        mContext = Objects.requireNonNull(context, "missing context");
        mService = Objects.requireNonNull(service, "missing IConnectivityManager");
 
        // 初始化的时候,把自己赋值给静态变量sInstance
        sInstance = this;
    }
}

总结一下获取ConnectivityManager的调用流程:

  1. 通过getSystemService获取ConnectivityManager
  2. 最终会调用到ContextImpl的getSystemService方法
  3. 接着通过SystemServiceRegistry的getSystemService方法
  4. SystemServiceRegistry内部通过CachedServiceFetcher获取
  5. CachedServiceFetcher又通过ContextAwareServiceProducerWithBinder创建ConnectivityManager返回给调用者

前面讲到,ContextImpl中的OuterContext很重要,要知道这个OuterContext是什么,得看看Activity的启动流程:

ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    // 这里创建一个ContextImpl
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    java.lang.ClassLoader cl = appContext.getClassLoader();
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
 
    ...
    if (activity != null) {
        // 这里可以看到OuterContext其实就是Activity
        appContext.setOuterContext(activity);
        // 上面讲到ContextWrapper中的mBase就是在attach方法被赋值的
        activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken, r.shareableActivityToken);
 
    }
 
    ...
}

至此,内存泄漏的原因找到了。在ConnectivityManager的构造方法中可以看到,有个静态变量持有自己,并且持有了传进来的context,这个context是ContextImpl的OuterContext。

如果调用getSystemService的context是Activity的话,那么ContextImpl的OuterContext就是Activity。

如果调用getSystemService的context是Application的话,那么ContextImpl的OuterContext就是Application。

所以解决这个内存泄漏的方法也很简单,就是用Application的context去获取ConnectivityManager就能保证不发生内存泄漏,代码这里就不贴了。

结束了吗?

上面讲的全是Android 12的源码,但是在别的版本又是不一样的情况,先说说Android 7到Andoid 11的版本:

SystemServiceRegistry.java

static {
    // 在static块中,去初始化CONNECTIVITY_SERVICE对应的Fetcher
    registerService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class,
                new StaticApplicationContextServiceFetcher<ConnectivityManager>() {
            @Override
            public ConnectivityManager createService(Context context) throws ServiceNotFoundException {
                IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
                IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
                return new ConnectivityManager(context, service);
            }});
}
 
 
static abstract class StaticApplicationContextServiceFetcher<T> implements ServiceFetcher<T> {
 
    private T mCachedInstance;
 
    @Override
    public final T getService(ContextImpl ctx) {
        synchronized (StaticApplicationContextServiceFetcher.this) {
            if (mCachedInstance == null) {
                // 可以看到,已经帮我们去获取ApplicationContext了,所以在Android 7到11的版本是不存在泄漏的情况。
                Context appContext = ctx.getApplicationContext();
                try {
                    mCachedInstance = createService(appContext != null ? appContext : ctx);
                } catch (ServiceNotFoundException e) {
                    onServiceNotFound(e);
                }
            }
            return mCachedInstance;
        }
    }
}

我们再看看Android 6:

SystemServiceRegistry.java

static {
    // 在static块中,去初始化CONNECTIVITY_SERVICE对应的Fetcher
    registerService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class,
                new StaticApplicationContextServiceFetcher<ConnectivityManager>() {
            @Override
            public ConnectivityManager createService(Context context) throws ServiceNotFoundException {
                IBinder b = ServiceManager.getServiceOrThrow(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) {
        synchronized (StaticOuterContextServiceFetcher.this) {
            if (mCachedInstance == null) {
                mCachedInstance = createService(ctx.getOuterContext());
            }
            return mCachedInstance;
        }
    }
}

可以看到调用createService方法时,传的还是ContextImpl的OuterContext,所以在Android 6的版本,是存在泄漏的情况的可能。

总结

在需要调用系统服务的场景,能用applicationContext还是尽量用,避免出现类似的情况。

写完了,下篇见。