getSystemService()引起的内存泄漏​

Quibbler 2022-8-17 1389

getSystemService()引起的内存泄漏


        通常获取系统服务通过ContextgetSystemService(String name)方法,举个例子:

    //获取ConnectivityManager,这里的Context是Activity
    ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

        每个系统服务都有独一无二的名称对应,其中CONNECTIVITY_SERVICE是定义在Context中的"connectivity"

    /**
     * Use with {@link #getSystemService(String)} to retrieve a {@link
     * android.net.ConnectivityManager} for handling management of
     * network connections.
     *
     * @see #getSystemService(String)
     * @see android.net.ConnectivityManager
     */
    public static final String CONNECTIVITY_SERVICE = "connectivity";



1、getSystemService方法

        getSystemService(String name)方法是定义在Context中的抽象方法:

    public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);

        在ContextImpl类中实现了getSystemService(String name)方法,关于Context极其各种子类在《各种Context及其初始化流程》一文中有详细的剖析。

    @Override
    public Object getSystemService(String name) {
        if (vmIncorrectContextUseEnabled()) {
            // Check incorrect Context usage.
            if (WINDOW_SERVICE.equals(name) && !isUiContext()) {
                final String errorMessage = "Tried to access visual service "
                        + SystemServiceRegistry.getSystemServiceClassName(name)
                        + " from a non-visual Context:" + getOuterContext();
                final String message = "WindowManager should be accessed from Activity or other "
                        + "visual Context. Use an Activity or a Context created with "
                        + "Context#createWindowContext(int, Bundle), which are adjusted to "
                        + "the configuration and visual bounds of an area on screen.";
                final Exception exception = new IllegalAccessException(errorMessage);
                StrictMode.onIncorrectContextUsed(message, exception);
                Log.e(TAG, errorMessage + " " + message, exception);
            }
        }
        //通过SystemServiceRegistry拿到对应的Service
        return SystemServiceRegistry.getSystemService(this, name);
    }


        接下来进入到SystemServiceRegistry类中,从类名不难看出这个类负责系统服务注册。SystemServiceRegistry类中的getSystemService(ContextImpl ctx, String name)实现如下:

    /**
     * Gets a system service from a given context.
     * @hide
     */
    public static Object getSystemService(ContextImpl ctx, String name) {
        if (name == null) {
            return null;
        }
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        if (fetcher == null) {
            if (sEnableServiceNotFoundWtf) {
                Slog.wtf(TAG, "Unknown manager requested: " + name);
            }
            return null;
        }
        final Object ret = fetcher.getService(ctx);
        if (sEnableServiceNotFoundWtf && ret == null) {
            // Some services do return null in certain situations, so don't do WTF for them.
            switch (name) {
                case Context.CONTENT_CAPTURE_MANAGER_SERVICE:
                case Context.APP_PREDICTION_SERVICE:
                case Context.INCREMENTAL_SERVICE:
                case Context.ETHERNET_SERVICE:
                    return null;
            }
            Slog.wtf(TAG, "Manager wrapper not available: " + name);
            return null;
        }
        return ret;
    }

        通过服务nameSYSTEM_SERVICE_FETCHERS拿到ServiceFetcher,进而通过ServiceFetcher获取对应的系统服务。这个SYSTEM_SERVICE_FETCHERS想必不看也应该知道是一个 系统服务名 到 系统服务 的映射:

    private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new ArrayMap<String, ServiceFetcher<?>>();



2、系统服务注册

        让我们来看看系统服务在哪里注册到SYSTEM_SERVICE_FETCHERS映射中的。找到这个registerService()方法,系统服务通过此方法注册到SYSTEM_SERVICE_FETCHERS中。

    /**
     * Statically registers a system service with the context.
     * This method must be called during static initialization only.
     */
    private static <T> void registerService(@NonNull String serviceName,
            @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
        SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
    }

        在SystemServiceRegistry类中有长达一千多行的静态代码块,当SystemServiceRegistry类首次被虚拟机加载的时候就会执行静态代码块中的代码,把系统服务注册添加到集合SYSTEM_SERVICE_FETCHERS 中,后面使用的时候通过getSystemService()从集合中去获取。

    static {
        //CHECKSTYLE:OFF IndentationCheck
        registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
                new CachedServiceFetcher<AccessibilityManager>() {
            @Override
            public AccessibilityManager createService(ContextImpl ctx) {
                return AccessibilityManager.getInstance(ctx);
            }});
        ...
        sInitializing = true;
        try {
            // Note: the following functions need to be @SystemApis, once they become mainline
            // modules.
            ConnectivityFrameworkInitializer.registerServiceWrappers();
            JobSchedulerFrameworkInitializer.registerServiceWrappers();
            BlobStoreManagerFrameworkInitializer.initialize();
            TelephonyFrameworkInitializer.registerServiceWrappers();
            AppSearchManagerFrameworkInitializer.initialize();
            WifiFrameworkInitializer.registerServiceWrappers();
            StatsFrameworkInitializer.registerServiceWrappers();
            RollbackManagerFrameworkInitializer.initialize();
            MediaFrameworkPlatformInitializer.registerServiceWrappers();
            MediaFrameworkInitializer.registerServiceWrappers();
            RoleFrameworkInitializer.registerServiceWrappers();
            SchedulingFrameworkInitializer.registerServiceWrappers();
        } finally {
            // If any of the above code throws, we're in a pretty bad shape and the process
            // will likely crash, but we'll reset it just in case there's an exception handler...
            sInitializing = false;
        }
    }


        这里以ConnectivityManager服务注册为例,它的注册也比其它服务特殊一些,并没有直接在SystemServiceRegistry中注册,而是通过ConnectivityFrameworkInitializer中的registerServiceWrappers()方法注册。ConnectivityFrameworkInitializer位于packages/modules/Connectivity/framework/src/android/net目录中。

    /**
     * Called by {@link SystemServiceRegistry}'s static initializer and registers all core
     * connectivity services to {@link Context}, so that {@link Context#getSystemService} can
     * return them.
     *
     * @throws IllegalStateException if this is called anywhere besides
     * {@link SystemServiceRegistry}.
     */
    public static void registerServiceWrappers() {
        // registerContextAwareService will throw if this is called outside of SystemServiceRegistry
        // initialization.
        SystemServiceRegistry.registerContextAwareService(
                Context.CONNECTIVITY_SERVICE,
                ConnectivityManager.class,
                (context, serviceBinder) -> {
                    IConnectivityManager icm = IConnectivityManager.Stub.asInterface(serviceBinder);
                    return new ConnectivityManager(context, icm);
                }
        );
        SystemServiceRegistry.registerContextAwareService(
                Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
                ConnectivityDiagnosticsManager.class,
                (context) -> {
                    final ConnectivityManager cm = context.getSystemService(
                            ConnectivityManager.class);
                    return cm.createDiagnosticsManager();
                }
        );
        SystemServiceRegistry.registerContextAwareService(
                Context.TEST_NETWORK_SERVICE,
                TestNetworkManager.class,
                context -> {
                    final ConnectivityManager cm = context.getSystemService(
                            ConnectivityManager.class);
                    return cm.startOrGetTestNetworkManager();
                }
        );
        SystemServiceRegistry.registerContextAwareService(
                DnsResolverServiceManager.DNS_RESOLVER_SERVICE,
                DnsResolverServiceManager.class,
                (context, serviceBinder) -> new DnsResolverServiceManager(serviceBinder)
        );
    }

        在ConnectivityFrameworkInitializer中的registerServiceWrappers()方法中又回到了SystemServiceRegistry,最终通过SystemServiceRegistry#registerContextAwareService()方法注册ConnectivityManager

    @SystemApi
    public static <TServiceClass> void registerContextAwareService(
            @NonNull String serviceName, @NonNull Class<TServiceClass> serviceWrapperClass,
            @NonNull ContextAwareServiceProducerWithBinder<TServiceClass> serviceProducer) {
        ensureInitializing("registerContextAwareService");
        Preconditions.checkStringNotEmpty(serviceName);
        Objects.requireNonNull(serviceWrapperClass);
        Objects.requireNonNull(serviceProducer);
        registerService(serviceName, serviceWrapperClass,
                new CachedServiceFetcher<TServiceClass>() {
                    @Override
                    public TServiceClass createService(ContextImpl ctx)
                            throws ServiceNotFoundException {
                        return serviceProducer.createService(
                                //传入Context中的mOuterContext
                                ctx.getOuterContext(),
                                ServiceManager.getServiceOrThrow(serviceName));
                    }});
    }



3、创建系统服务

        注意,在第2节最后给用来创建系统服务的createService()方法传入的是ContextImpl中的mOuterContext

    @UnsupportedAppUsage
    final void setOuterContext(@NonNull Context context) {
        mOuterContext = context;
    }
    
    @UnsupportedAppUsage
    final Context getOuterContext() {
        return mOuterContext;
    }

        那么ContextImpl中的mOuterContext什么时候被设置的呢?以Activity为例,在Activity启动流程一文中,知道ActivityperformLaunchActivity()方法中被创建:

    /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        //创建ContextImpl 
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
		...
        //并且将当前创建的Activity 作为OuterContext设置给ContextImpl
        appContext.setOuterContext(activity);
        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);
        ...
        return activity;
    }

        其中通过createBaseContextForActivity(r)方法创建ContextImpl实例:

    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        final int displayId = ActivityClient.getInstance().getDisplayId(r.token);
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
        // The rotation adjustments must be applied before creating the activity, so the activity
        // can get the adjusted display info during creation.
        if (r.mPendingFixedRotationAdjustments != null) {
            // The adjustments should have been set by handleLaunchActivity, so the last one is the
            // override for activity resources.
            if (mActiveRotationAdjustments != null && !mActiveRotationAdjustments.isEmpty()) {
                mResourcesManager.overrideTokenDisplayAdjustments(r.token,
                        mActiveRotationAdjustments.get(
                                mActiveRotationAdjustments.size() - 1).second);
            }
            r.mPendingFixedRotationAdjustments = null;
        }
        final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
        // For debugging purposes, if the activity's package name contains the value of
        // the "debug.use-second-display" system property as a substring, then show
        // its content on a secondary display if there is one.
        String pkgName = SystemProperties.get("debug.second-display.pkg");
        if (pkgName != null && !pkgName.isEmpty()
                && r.packageInfo.mPackageName.contains(pkgName)) {
            for (int id : dm.getDisplayIds()) {
                if (id != DEFAULT_DISPLAY) {
                    Display display =
                            dm.getCompatibleDisplay(id, appContext.getResources());
                    appContext = (ContextImpl) appContext.createDisplayContext(display);
                    break;
                }
            }
        }
        return appContext;
    }

         同样在performLaunchActivity()中将当前启动的Activity设置给刚创建好的ContextImpl

    ...
    appContext.setOuterContext(activity);
    ...

        回到最初的getSystemService(ContextImpl ctx, String name)方法中,通过ServiceFetcher获取Service,使用的构造正是从ConnectivityFrameworkInitializer传入的:

        SystemServiceRegistry.registerContextAwareService(
                Context.CONNECTIVITY_SERVICE,
                ConnectivityManager.class,
                (context, serviceBinder) -> {
                    IConnectivityManager icm = IConnectivityManager.Stub.asInterface(serviceBinder);
                    return new ConnectivityManager(context, icm);
                }
        );


        那么为什么可能会导致内存泄漏呢?还是以创建ConnectivityManager实例为例,ConnectivityManager的构造方法如下:

    /**
     * {@hide}
     */
    public ConnectivityManager(Context context, IConnectivityManager service) {
        this(context, service, true /* newStatic */);
    }

        紧接着调用ConnectivityManager的构造方法,将传入的Context参数保存给内部mContext成员,通过前面的分析,传进来的可能是mOuterContext,也就是Activity对象。

    /**
     * {@hide}
     */
    public ConnectivityManager(Context context, IConnectivityManager service) {
        mContext = Objects.requireNonNull(context, "missing context");
        mService = Objects.requireNonNull(service, "missing IConnectivityManager");
        sInstance = this;
    }

        频繁创建系统服务非常消耗资源,所以系统服务通常都是单例。而这个静态单例如果持有了应用的Activity就会造成内存泄漏,这下明白了吧?隐藏的很深的内存泄漏!



4、解决内存泄漏

        去review了一下AOSP最新的代码,可以看到Google已经在2022年5月3号I87206e1bfbb1f877b5a10f5fdbc25e2f9f11bef4这笔提交中处理了这个内存泄漏:


        修改为使用getApplicationContext()作为引用的Context


        要等各手机厂商系统同步AOSP的这笔修改还需要一段时间,最简单的解决办法就是上面的方法,先getApplicationContext()再去调用getSystemService(String name)方法获取系统服务,而不应直接通过Activity或者Service作为Context去获取系统服务。

    //使用getApplicationContext()拿到应用Context再去获取系统服务
    ConnectivityManager connMgr = activity.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);



不忘初心的阿甘
最新回复 (0)
    • 安卓笔记本
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com