使用ConnectivityManager的內(nèi)存泄漏隱患

Android里面內(nèi)存泄漏問題最突出的就是Activity的泄漏,而泄漏的根源大多在于單例的使用,也就是一個靜態(tài)實例持有了Activity的引用。靜態(tài)變量的生命周期與應(yīng)用(Application)是相同的,而Activity生命周期通常比它短,也就會造成在Activity生命周期結(jié)束后,還被引用導(dǎo)致無法被系統(tǒng)回收釋放。

生成靜態(tài)引用內(nèi)存泄漏可能有兩種情況:

  1. 應(yīng)用級:應(yīng)用程序代碼實現(xiàn)的單例沒有很好的管理其生命周期,導(dǎo)致Activity退出后仍然被引用。
  2. 系統(tǒng)級:Android系統(tǒng)級的實現(xiàn)的單例,被應(yīng)用不小心錯誤調(diào)用(當(dāng)然你也可以認(rèn)為是系統(tǒng)層實現(xiàn)地不太友好)。

這個主要講下系統(tǒng)級的情況,這樣的情況可能也有很多,舉個最近發(fā)現(xiàn)的問題ConnectivityManager。

通常我們獲取系統(tǒng)服務(wù)時采用如下方式:

context.getSystemService()

在Android6.0系統(tǒng)上,如果這里的Context如果是Activity的實例,那么即使你什么也不干也會造成內(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實例,這個實例指向一個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的實例。

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實現(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的單實例持有了Activity的實例引用。這樣即使Activity退出后仍然無法釋放,導(dǎo)致內(nèi)存泄漏。

這個問題僅在6.0上出現(xiàn),在5.1上ConnectivityManager實現(xiàn)為單例但不持有Context的引用,在5.0有以下版本ConnectivityManager既不為單例,也不持有Context的引用。

其他服務(wù)沒認(rèn)真研究,不確定有沒有這個問題。不過為了避免類似的情況發(fā)生,最好的解決辦法就是:

獲取系統(tǒng)服務(wù)getSystemService時使用ApplicationContext

context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容