preface

Recently, I checked the data reported online and found that the time of the delay occurred immediately and for a long time was when the memory was insufficient. Basically, the available memory of the system was only a few hundred K or 1 or 2M, so I suspected that there was a memory leak in our application. Grab your Android 12 Pixel 3 and start checking

Strange chain of references

According to our application prior to urination, memory leaks are often in relay room on this page. After running through the scene of entering the room → going on the mic → returning to the home page, DEMp showed the memory condition and sure enough there was a leak

Looking at these five leaks, which are actually related to RoomLiveGameActivity, let’s see who is still holding RoomLiveGameActivity

Nani, ConnectivityManager actually holds RoomLiveGameActivity causing a memory leak. No way, and began to helpless pain to see the source code.

Analysis of the

This is how we usually get ConnectivityManager

Activity.java

ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
Copy the code

The Activity inherits from ContextWrapper and finally calls ContextWrapper’s getSystemService method

ContextWrapper.java

@override public Object getSystemService(String name) {// mBase is ContextImpl, Return mbase.getSystemService (name); }Copy the code

ContextImpl.java

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

All required system services are fetched from SystemServiceRegistry, and then communicated with the system by invoking binder interfaces with each Manager.

SystemServiceRegistry.java

Public static Object getSystemService(ContextImpl CTX, String name) {ServiceFetcher (ContextImpl CTX, String name) {ServiceFetcher (ContextImpl CTX, String name); Obtain the corresponding Manager Final ServiceFetcher<? > fetcher = SYSTEM_SERVICE_FETCHERS.get(name); . final Object ret = fetcher.getService(ctx); . return ret; } the static {/ / in the static block, initialize the corresponding Fetcher CONNECTIVITY_SERVICE ConnectivityFrameworkInitializer. RegisterServiceWrappers (); . }Copy the code

ConnectivityFrameworkInitializer.java

Public static void registerServiceWrappers() {// Return SystemServiceRegistry static method, // The last argument is a lambda expression, It is a ContextAwareServiceProducerWithBinder interface 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); }Copy the code

SystemServiceRegistry.java

public static <TServiceClass> void registerContextAwareService( @NonNull String serviceName, @NonNull Class<TServiceClass> serviceWrapperClass, @NonNull ContextAwareServiceProducerWithBinder<TServiceClass> serviceProducer) { // CONNECTIVITY_SERVICE corresponds to CachedServiceFetcher, Call again and CachedServiceFetcher createService ContextAwareServiceProducerWithBinder createService method (that is, the lambda expressions above) / / The context is the OuterContext that gets out of ContextImpl, that's important (type on the blackboard), RegisterService (serviceName, serviceWrapperClass, new CachedServiceFetcher<TServiceClass>() { @Override public TServiceClass createService(ContextImpl ctx) throws ServiceNotFoundException {// Pass a ContextImpl OuterContext to ConnectivityManager to initialize the 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; } // Create a cache using createService if not found... 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; }Copy the code

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 = this; sInstance = this; }}Copy the code

To summarize the call flow for getting ConnectivityManager:

  1. Get the ConnectivityManager from getSystemService
  2. The getSystemService method of ContextImpl is eventually called
  3. Next, through the getSystemService stry method
  4. SystemServiceRegistry is obtained internally from CachedServiceFetcher
  5. CachedServiceFetcher create ConnectivityManager through ContextAwareServiceProducerWithBinder again returned to the caller

ContextImpl: ContextImpl: ContextImpl: ContextImpl: ContextImpl: ContextImpl: ContextImpl: ContextImpl: ContextImpl

ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... / / here to create a ContextImpl ContextImpl appContext = createBaseContextForActivity (r); Activity activity = null; java.lang.ClassLoader cl = appContext.getClassLoader(); activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent); . if (activity ! Can see OuterContext = null) {/ / here is actually the Activity appContext. SetOuterContext (Activity); Attach (appContext, this, getInstrumentation(), r.token, r.I.dent, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback, r.assistToken, r.shareableActivityToken); }... }Copy the code

At this point, the cause of the memory leak is found. As you can see in the constructor of ConnectivityManager, a static variable holds itself and holds the context passed in. This context is ContextImpl’s OuterContext.

If the context for calling getSystemService is an Activity, the ContextImpl OuterContext is an Activity.

ContextImpl’s OuterContext is Application if the context used to call getSystemService is Application.

So the solution to this memory leak is very simple, just use the Application context to get the ConnectivityManager to ensure that there is no memory leak, the code is not attached here.

Is it over?

Android 7 to Andoid 11 Android 7 to Andoid 11

SystemServiceRegistry.java

Static {// In the static block, To initialize the corresponding Fetcher CONNECTIVITY_SERVICE 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) {// You can see that the ApplicationContext has been obtained for us, so there is no leak in Android versions 7 through 11. Context appContext = ctx.getApplicationContext(); try { mCachedInstance = createService(appContext ! = null ? appContext : ctx); } catch (ServiceNotFoundException e) { onServiceNotFound(e); } } return mCachedInstance; }}}Copy the code

Let’s take a look at Android 6:

SystemServiceRegistry.java

Static {// In the static block, To initialize the corresponding Fetcher CONNECTIVITY_SERVICE 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; }}}Copy the code

When the createService method is called, the ContextImpl OuterContext is passed, so there is a possibility of a leak in Android 6.

conclusion

In situations where system services need to be called, use applicationContext to avoid similar situations.

That’s it. See you next time.