從源碼解析部分系統服務引起的內存泄露

寫在開頭

寫文章,其實是一個梳理思維,學習成長的過程,有意的可以一塊學習,互相交流。謝謝大家!有錯誤或者意見還請指出共同進步。

今天在用LeakCanary檢測內存泄露過程中,發現ConnectivityManager報出內存泄露的警告(context),這不是系統Framework的網絡管理模塊嗎,系統服務還能泄露了。。。

問題分析(就ConnectivityManager而言)

通過在不同的測試手機上測試,發現此問題只在android6.0以上纔有。5.0或4.4的手機中並無此問題。大家都知道,使用這些系統服務會傳入一個上下文環境,也就是我們所說的context,在android的源碼中,這些服務的對象是通過單例創建的,但是在6.0以上,它會持有我們傳入的context的引用。所以,如果你傳入的是activity,當你的activity銷燬時,它卻持有這個activity的引用,就會造成內存泄露。在5.1的源碼中,ConnectivityManager實現爲單例但不持有Context的引用,在5.0有以下版本ConnectivityManager既不爲單例,也不持有Context的引用。本文就6.0以上說明一下。

Context詳解

在解釋問題前,先說下context。都知道,Context是維持Android程序中各組件能夠正常工作的一個核心功能類。他的繼承關係如下圖:

這裏寫圖片描述

Context的繼承結構如圖,它的直系子類有兩個,一個是ContextWrapper,一個是ContextImpl。ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類。而ContextWrapper又有三個直接的子類,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity。

通過上面我們可以看出,我們說的context就三種,activity,service,application。這三個類雖然分別各種承擔着不同的作用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的。

PS:如果您想看ContextImpl的源碼,你會發現從AS看context的繼承結構並沒有ContextImpl,這時你需要去sdk中/android-sdk/sources/android-25/android/app/ContextImpl.java 去查找觀看。

進一步分析Context

從上面可以看到,Activity根本上是從ContextWrapper繼承而來的,跟進源碼,ContextWrapper中持有一個mBase實例

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }

這個實例指向一個ContextImpl對象,我們可以看ContextWrapper中的attachBaseContext方法。

    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

這個方法中傳入了一個base參數,並把這個參數賦值給了mBase對象。而attachBaseContext()方法其實是由系統來調用的,它會把ContextImpl對象作爲參數傳遞到attachBaseContext()方法當中,從而賦值給mBase對象,之後ContextWrapper中的所有方法其實都是通過這種委託的機制交由ContextImpl去具體實現的。

同時ContextImpl對象持有一個OuterContext對象,對於Activity來說,這個OuterContext就是Activity對象。
也就是構造中的那個this。

     private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
        mOuterContext = this;

        // If creator didn't specify which storage to use, use the default
        // location for application.
        if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE
                | Context.CONTEXT_DEVICE_PROTECTED_STORAGE)) == 0) {
            final File dataDir = packageInfo.getDataDirFile();
            if (Objects.equals(dataDir, packageInfo.getCredentialProtectedDataDirFile())) {
                flags |= Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
            } else if (Objects.equals(dataDir, packageInfo.getDeviceProtectedDataDirFile())) {
                flags |= Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
            }
        }

        mMainThread = mainThread;
        mActivityToken = activityToken;
        mFlags = flags;
    ......

所以您通過context調用getSystemService時最終會調用到ContextImpl的getSystemService方法。而這個getSystemService是通過ContextImpl的SystemServiceRegistry來完成。

代碼流程如下。

第一步:

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

第二步:

    public static Object getSystemService(ContextImpl ctx, String name) { 
          ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); 
     }

第三步:SystemServiceRegistry提供ConnectivityManager的實例。

    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) { 
    if (mCachedInstance == null) { 
    mCachedInstance = createService(ctx.getOuterContext()); 
        } 
    } 
} 
    public abstract T createService(Context applicationContext); }

總結

Context在ConnectivityManager 創建時傳入,這個Context在StaticOuterContextServiceFetcher中由ContextImpl對象轉換爲OuterContext,如果我們的context是Activity對象,ConnectivityManager的單實例就持有了Activity的實例引用。這樣即使Activity退出後仍然無法釋放,導致內存泄漏。在源碼中,public abstract T createService(Context applicationContext); },人家提示的非常明顯了,這個上下文環境,人家想要的是一個Application中提供的context。

省心的解決辦法

獲取系統服務getSystemService時使用我們在自定義Application中提供的context。

寫在後頭

對於其他的系統服務,有些傳入activty的引用也是不會造成內存泄露的,所以這方面還是就實際而言,有問題解決,看人家源碼的提示,沒問題就沒問題嘍,安心的寫其他代碼。

發佈了39 篇原創文章 · 獲贊 33 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章