Android里面內(nèi)存泄漏問題最突出的就是Activity的泄漏,而泄漏的根源大多在于單例的使用,也就是一個靜態(tài)實(shí)例持有了Activity的引用。靜態(tài)變量的生命周期與應(yīng)用(Application)是相同的,而Activity生命周期通常比它短,也就會造成在Activity生命周期結(jié)束后,還被引用導(dǎo)致無法被系統(tǒng)回收釋放。
生成靜態(tài)引用內(nèi)存泄漏可能有兩種情況:
- 應(yīng)用級:應(yīng)用程序代碼實(shí)現(xiàn)的單例沒有很好的管理其生命周期,導(dǎo)致Activity退出后仍然被引用。
- 系統(tǒng)級:Android系統(tǒng)級的實(shí)現(xiàn)的單例,被應(yīng)用不小心錯誤調(diào)用(當(dāng)然你也可以認(rèn)為是系統(tǒng)層實(shí)現(xiàn)地不太友好)。
這個主要講下系統(tǒng)級的情況,這樣的情況可能也有很多,舉個最近發(fā)現(xiàn)的問題ConnectivityManager。
通常我們獲取系統(tǒng)服務(wù)時采用如下方式:
context.getSystemService()
在Android6.0系統(tǒng)上,如果這里的Context如果是Activity的實(shí)例,那么即使你什么也不干也會造成內(nèi)存泄漏。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
}
}
用LeakCanary可以直接看到內(nèi)存泄漏:
D/LeakCanary: In com.kkmoving.main:1.0:1.
D/LeakCanary: * com.kkmoving.main.MainActivity has leaked:
D/LeakCanary: * GC ROOT static android.net.ConnectivityManager.sInstance
D/LeakCanary: * references android.net.ConnectivityManager.mContext
D/LeakCanary: * leaks com.kkmoving.main.MainActivity instance
D/LeakCanary: * Retaining: 3.5 KB.
D/LeakCanary: * Reference Key: 4a1b4c92-78f8-4233-b16f-8924e11cae9d
D/LeakCanary: * Device: LGE google Nexus 5 hammerhead
D/LeakCanary: * Android Version: 6.0 API: 23 LeakCanary: 1.4-beta1 02804f3
一步一步來分析下。
先從Context的getSystemService方法開始,我們知道Activity是從ContextWrapper繼承而來的,ContextWrapper中持有一個mBase實(shí)例,這個實(shí)例指向一個ContextImpl對象,同時ContextImpl對象持有一個OuterContext對象,對于Activity來說,這個OuterContext就是Activity對象。所以調(diào)用getSystemService最終會調(diào)用到ContextImpl的getSystemService方法。
在6.0上ContextImpl的getSystemService方法調(diào)用SystemServiceRegistry來完成。
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
SystemServiceRegistry提供ConnectivityManager的實(shí)例。
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
registerService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class,
new StaticOuterContextServiceFetcher<ConnectivityManager>() {
@Override
public ConnectivityManager createService(Context context) {
IBinder b = ServiceManager.getService(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;
}
}
public abstract T createService(Context applicationContext);
}
在6.0上,ConnectivityManager實(shí)現(xiàn)為單例:
private static ConnectivityManager sInstance;
精彩的部分來了,ConnectivityManager 持有了一個Context的引用:
private final Context mContext;
public ConnectivityManager(Context context, IConnectivityManager service) {
mContext = checkNotNull(context, "missing context");
mService = checkNotNull(service, "missing IConnectivityManager");
sInstance = this;
}
這個Context在ConnectivityManager 創(chuàng)建時傳入,這個Context在StaticOuterContextServiceFetcher中由ContextImpl對象轉(zhuǎn)換為OuterContext,與就是Activity對象,所以最終ConnectivityManager的單實(shí)例持有了Activity的實(shí)例引用。這樣即使Activity退出后仍然無法釋放,導(dǎo)致內(nèi)存泄漏。
這個問題僅在6.0上出現(xiàn),在5.1上ConnectivityManager實(shí)現(xiàn)為單例但不持有Context的引用,在5.0有以下版本ConnectivityManager既不為單例,也不持有Context的引用。
其他服務(wù)沒認(rèn)真研究,不確定有沒有這個問題。不過為了避免類似的情況發(fā)生,最好的解決辦法就是:
獲取系統(tǒng)服務(wù)getSystemService時使用ApplicationContext
context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);