Android Context 你不知道的那些事

一、Context類圖:


上面就是有關Context的幾個關鍵類的組織結構,一目瞭然,不多作解釋,接下來看看Android源碼中的類註釋。

二、類註釋:

Context:類註釋解釋的很清楚,Context應用程序環境的全局信息的接口。這是一個抽象類,它的實現是由Android系統提供的。它允許訪問特定應用程序的資源和類,以及對應用級操作的追加調用,例如啓動活動、廣播和接收意圖等。

ContextImpl:上下文API的通用實現,它爲活動和其他應用程序組件提供基本上下文對象。

ContextWrapper代理上下文的實現,簡單地將所有的調用委託給另一個上下文。可以在不更改原始上下文的情況下對其進行子類修改。

ContextThemeWrapper一個上下文包裝器,允許修改或替換包裝上下文的主題。ps:AndroidManifest.xml中主題的屬性功能就是這個類來實現的)

Application維護全局應用程序狀態的基類。可以通過創建一個子類來提供自己的實現,並在AndroidManifest.xml中指定這個子類的完全限定名稱爲“android:name”屬性的值這個應用程序類,或者應用程序類的子類,在創建應用程序/包的過程,在任何其他類實例化之前先被實例化。(ps:具體的獲取全局Context後面再講)

ActivityService相信大家已經非常熟悉了,在此就不多作解釋了,感興趣的朋友自己去看源碼註釋。

三、Context數量:

一個應用程序有幾個Context呢?從上面的關係圖我們得知Context的具體實現子類就是:ApplicationActivityService,所以一個Android應用程序的Context種類= 3種,Context數量=Application數量(1個)+Activity數量 + Service數量。

四、Context能幹什麼:

Context能幹什麼,有哪些功能用到了Context這個確實有點多例如,彈出Toast顯示dialoglayout inflation啓動Activity、啓動Service、發送廣播、操作數據庫、加載資源等等都需要用到Context

上面是Context的常見的幾種應用場景,我簡單解釋一下:

Show dialog出於安全原因的考慮,Android是不允許Dialog憑空出現的,Dialog必須在一個Activity上面彈出(除非是System Alert類型的Dialog,所以只能用ActivityContext

Start activityAndroid系統中,因爲Activity涉及到Activity棧,所以一個Activity的啓動必須要建立在另一個Activity的基礎之上,也就是以此形成的返回棧。因此在這種場景下,我們最好使用Activity類型的Context

Layout inflationlayout inflate時使用application和service是合法的,但是會使用系統默認的主題樣式,如果你自定義了某些樣式可能不會被使用。

總結一下只要把握住一點,凡是跟UI相關的,都應該使用Activity做爲Context來處理;其他的一些操作,Service,Activity,Application等實例都可以,當然了要注意防止內存泄漏,那內存泄漏是怎麼發生的呢,如何才能避免呢,請接着看

五、Context引起的內存泄露

Context不能隨便亂用,用的不好有可能會引起內存泄露的問題,內存泄漏一般發生的原因是錯誤的引用持有,下面就示例兩種錯誤的引用持有方式。

1.錯誤的單例模式

public class Singleton {

    private static Singleton instance;
    private Context mContext;
 
    private Singleton(Context context) {
        this.mContext = context;
    } 

    public static Singleton getInstance(Context context) {
        if (instance == null) {
            instance = new Singleton(context);
        }
        return instance;
    }

}

這是一個非線程安全的單例模式,instance作爲靜態對象,其生命週期要長於普通的對象,其中也包含Activity,假如Activity AgetInstance獲得instance對象,傳入this,常駐內存的Singleton保存了你傳入的Activity A對象,並一直持有,即使Activity被銷燬掉,但因爲它的引用還存在於一個Singleton中,就不可能被GC掉,這樣就導致了內存泄漏。

2.View持有Activity引用

public class MainActivity extends Activity {
    private static Drawable mDrawable;

    @Override
    protected void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);
        ImageView iv = new ImageView(this);
        mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
        iv.setImageDrawable(mDrawable);
    }
}

有一個靜態的Drawable對象當ImageView設置這個Drawable時,ImageView保存了mDrawable的引用,而ImageView傳入的thisMainActivitymContext,因爲被static修飾的mDrawable是常駐內存的,MainActivity是它的間接引用,MainActivity被銷燬時,也不能被GC掉,所以造成內存泄漏。

六、正確使用Context:

一般Context造成的內存泄漏,幾乎都是當Context銷燬的時候,卻因爲被引用導致銷燬失敗,而ApplicationContext對象可以理解爲隨着進程存在的,所以我們總結出使用Context的正確姿勢:

1.ApplicationContext能搞定的情況下,並且生命週期長的對象,優先使用ApplicationContext

2.不要讓生命週期長於Activity的對象持有到Activity的引用。

3.儘量不要在Activity中使用非靜態內部類,因爲非靜態內部類會隱式持有外部類實例的引用,如果使用靜態內部類,將外部實例引用作爲弱引用持有。

4.因爲static修飾的對象常駐內存,生命週期比一般的對象要長,所以在Context中使用static時多想一下是否是必須的,如果可以,儘量不要用。

七、獲取全局Context:

一方面,爲了防止內存泄漏,我們在使用Context時,優先使用Application的Context另一方面,隨着應用程序的架構逐漸開始複雜起來的時候,很多的邏輯代碼都將脫離Activityservice類,但此時你又恰恰需要使用Context所以就需要在任何地方都能很方便的獲取到ApplicationContext

Android提供了一個Application類,每當應用程序啓動的時候,系統就會自動將這個類進行初始化。而我們可以定製一個自己的Application類,以便於管理程序內一些全局的狀態信息,比如說全局Context。因爲Application全局只有一個,它本身就已經是單例了,無需再用單例模式去爲它做多重實例保護

public class MyApplication extends Application {

    private static Context instance;

    @Override
    public void onCreate() 
    {
        instance = getApplicationContext();
    }
    
    public static Context getContext()
    {
        return instance;
    }
    
}

接下來我們需要告知系統,當程序啓動的時候應該初始化MyApplication類,而不是默認的Application類。這一步也很簡單,在AndroidManifest.xml文件的<application>標籤下進行指定就可以了,代碼如下所示:

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" 
        android:theme="@style/AppTheme" 
        android:name="com.dapeng.MyApplication">
</application>

這樣我們就已經實現了一種全局獲取Context的機制,之後不管你想在項目的任何地方使用Context,只需要調用一下MyApplication.getContext()就可以了。


本文參考閱讀:
http://blog.csdn.net/lmj623565791/article/details/40481055/
http://blog.csdn.net/yanbober/article/details/45967639
http://blog.csdn.net/guolin_blog/article/details/47028975
http://www.jianshu.com/p/94e0f9ab3f1d#
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章