-
Notifications
You must be signed in to change notification settings - Fork 12
(七十七)getSystemService内存泄露探讨
前言:看了下www.jianshu.com/p/5d96983fc6db 这篇文章,文中有提及Android N之前WiFiManager会长时间持有context不释放,导致内存泄露。Android N以后修改其中的asyncChannel为非static的就好了,感觉没说的很清楚。
@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }
/** * 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]; }
一个应用的static变量还是和applicationContext更配一点,毕竟生命周期差不多,如果调用getSystemService,也不用管manager是单例的还是重新new的,传入applicationContext比较好。
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) {