Skip to content

(七十七)getSystemService内存泄露探讨

happyjiatai edited this page Jul 29, 2018 · 2 revisions

前言:看了下www.jianshu.com/p/5d96983fc6db 这篇文章,文中有提及Android N之前WiFiManager会长时间持有context不释放,导致内存泄露。Android N以后修改其中的asyncChannel为非static的就好了,感觉没说的很清楚。

1.getSystemService流程

1.1 ContextImpl

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

1.2 SystemServiceRegistry

/**
 * Gets a system service from a given context.
 */
public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

这边其实是获取的先前的缓存,但是有个前提是ctx是一样的才可以,也就是说一个应用的两个activity传递各自的context是不会得到复用的,使用ApplicationContext才可以得到复用。

/**

* Manages all of the system services that can be returned by {@link Context#getSystemService}.
* Used by {@link ContextImpl}.
*/

final class SystemServiceRegistry {

private static final String TAG = "SystemServiceRegistry";

// Service registry information.
// This information is never changed once static initialization has completed.
private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
        new HashMap<Class<?>, String>();
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
        new HashMap<String, ServiceFetcher<?>>();
private static int sServiceCacheSize;

// Not instantiable.
private SystemServiceRegistry() { }

static {
    registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
            new CachedServiceFetcher<AccessibilityManager>() {
        @Override
        public AccessibilityManager createService(ContextImpl ctx) {
            return AccessibilityManager.getInstance(ctx);
        }});

    registerService(Context.CAPTIONING_SERVICE, CaptioningManager.class,
            new CachedServiceFetcher<CaptioningManager>() {
        @Override
        public CaptioningManager createService(ContextImpl ctx) {
            return new CaptioningManager(ctx);
        }});

registerService(Context.WIFI_SERVICE, WifiManager.class,
        new CachedServiceFetcher<WifiManager>() {
    @Override
    public WifiManager createService(ContextImpl ctx) throws ServiceNotFoundException {
        IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_SERVICE);
        IWifiManager service = IWifiManager.Stub.asInterface(b);
        return new WifiManager(ctx.getOuterContext(), service,
                ConnectivityThread.getInstanceLooper());
    }});

/**
 * Override this class when the system service constructor needs a
 * ContextImpl and should be cached and retained by that context.
 */
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
    private final int mCacheIndex;

    public CachedServiceFetcher() {
        mCacheIndex = sServiceCacheSize++;
    }

    @Override
    @SuppressWarnings("unchecked")
    public final T getService(ContextImpl ctx) {
        final Object[] cache = ctx.mServiceCache;
        synchronized (cache) {
            // Fetch or create the service.
            Object service = cache[mCacheIndex];
            if (service == null) {
                try {
                    service = createService(ctx);
                    cache[mCacheIndex] = service;
                } catch (ServiceNotFoundException e) {
                    onServiceNotFound(e);
                }
            }
            return (T)service;
        }
    }

    public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}
/**
 * Statically registers a system service with the context.
 * This method must be called during static initialization only.
 */
private static <T> void registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

关键代码都贴出来了

首先SystemServiceRegistry调用之初有个static的代码块,里面会将很多service的serviceFetcher存进static集合SYSTEM_SERVICE_FETCHERS中。之后有调用getSystemService,从static集合SYSTEM_SERVICE_FETCHERS 中取出对应service的serviceFetcher,接着调用getService方法。getService获取的服务是先查询context中的mServiceCache,如果没有service的cache,则重新create一下。重新create就分情况了,如果是AccessibilityManager这样的单例的manager对象还好,不会持有传入的context引用以存到context的mServiceChache中;如果是WifiManager,则传入多少个不同的context,那么就有多少个context的mServiceCache中包含WifiManager,如果WifiManager不释放,那么就导致内存泄露了。PS:不同的contextImpl都会从SystemServiceRegistry中new出一个新的Object数组,是不共享的。

ContextImpl.java
// The system service cache for the system services that are cached per-ContextImpl.
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();

SystemServiceRegistry.java
/**
 * Creates an array which is used to cache per-Context service instances.
 */
public static Object[] createServiceCache() {
    return new Object[sServiceCacheSize];
}

2. 总结

一个应用的static变量还是和applicationContext更配一点,毕竟生命周期差不多,如果调用getSystemService,也不用管manager是单例的还是重新new的,传入applicationContext比较好。

3.疑问

getOutContext和普通的ctx有什么区别?是applicationContext么?

registerService(Context.DISPLAY_SERVICE, DisplayManager.class,
        new CachedServiceFetcher<DisplayManager>() {
    @Override
    public DisplayManager createService(ContextImpl ctx) {
        return new DisplayManager(ctx.getOuterContext());
    }});

看了下OuterContext是在activity创建的时候初始化的,就是activity,appContext对应与activity 的服务端contextImpl。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");

    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                Context.CONTEXT_INCLUDE_CODE);
    }

    ComponentName component = r.intent.getComponent();
    if (component == null) {
        component = r.intent.resolveActivity(
            mInitialApplication.getPackageManager());
        r.intent.setComponent(component);
    }

    if (r.activityInfo.targetActivity != null) {
        component = new ComponentName(r.activityInfo.packageName,
                r.activityInfo.targetActivity);
    }

    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                "Unable to instantiate activity " + component
                + ": " + e.toString(), e);
        }
    }

    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
        if (localLOGV) Slog.v(
                TAG, r + ": app=" + app
                + ", appName=" + app.getPackageName()
                + ", pkg=" + r.packageInfo.getPackageName()
                + ", comp=" + r.intent.getComponent().toShortString()
                + ", dir=" + r.packageInfo.getAppDir());

        if (activity != null) {
            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
            Configuration config = new Configuration(mCompatConfiguration);
            if (r.overrideConfig != null) {
                config.updateFrom(r.overrideConfig);
            }
            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                    + r.activityInfo.name + " with config " + config);
            Window window = null;
            if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                window = r.mPendingRemoveWindow;
                r.mPendingRemoveWindow = null;
                r.mPendingRemoveWindowManager = null;
            }
            appContext.setOuterContext(activity);

。。。搜了下contextImpl初始化的时候会将outerContext初始化为自己,此外还有如下会重新赋值:

./base/core/java/android/app/LoadedApk.java:998: appContext.setOuterContext(app); ./base/core/java/android/app/ActivityThread.java:2780: appContext.setOuterContext(activity); ./base/core/java/android/app/ActivityThread.java:3371: context.setOuterContext(agent); ./base/core/java/android/app/ActivityThread.java:3445: context.setOuterContext(service); ./base/core/java/android/app/ContextImpl.java:2404: final void setOuterContext(Context context) {