1. 程式人生 > >android context

android context

nag 優先 copyto 效率問題 cte 解釋 tools extend 應用程序

韓夢飛沙 韓亞飛 [email protected] yue31313 han_meng_fei_sha

context 就是 上下文環境,

常見的 比如 應用環境, activity,服務,

Context的兩個子類分工明確,其中ContextImpl是Context的具體實現類,ContextWrapper是Context的包裝類。Activity,Application,Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper),但它們初始化的過程中都會創建ContextImpl對象,由ContextImpl實現Context中的方法。

技術分享

一般Context造成的內存泄漏,幾乎都是當Context銷毀的時候,卻因為被引用導致銷毀失敗,而Application的Context對象可以理解為隨著進程存在的,所以我們總結出使用Context的正確姿勢:
1:當Application的Context能搞定的情況下,並且生命周期長的對象,優先使用Application的Context。
2:不要讓生命周期長於Activity的對象持有到Activity的引用。
3:盡量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類實例的引用,如果使用靜態內部類,將外部實例引用作為弱引用持有。

==============

大家在編寫一些類時,例如工具類,可能會編寫成單例的方式,這些工具類大多需要去訪問資源,也就說需要Context的參與。

在這樣的情況下,就需要註意Context的引用問題。

例如以下的寫法:

[java] view plain copy
  1. package com.mooc.shader.roundimageview;
  2. import android.content.Context;
  3. public class CustomManager
  4. {
  5. private static CustomManager sInstance;
  6. private Context mContext;
  7. private CustomManager(Context context)
  8. {
  9. this.mContext = context;
  10. }
  11. public static synchronized CustomManager getInstance(Context context)
  12. {
  13. if (sInstance == null)
  14. {
  15. sInstance = new CustomManager(context);
  16. }
  17. return sInstance;
  18. }
  19. //some methods
  20. private void someOtherMethodNeedContext()
  21. {
  22. }
  23. }


對於上述的單例,大家應該都不陌生(請別計較getInstance的效率問題),內部保持了一個Context的引用;

這麽寫是沒有問題的,問題在於,這個Context哪來的我們不能確定,很大的可能性,你在某個Activity裏面為了方便,直接傳了個this;這樣問題就來了,我們的這個類中的sInstance是一個static且強引用的,在其內部引用了一個Activity作為Context,也就是說,我們的這個Activity只要我們的項目活著,就沒有辦法進行內存回收。而我們的Activity的生命周期肯定沒這麽長,所以造成了內存泄漏。

那麽,我們如何才能避免這樣的問題呢?

有人會說,我們可以軟引用,嗯,軟引用,假如被回收了,你不怕NullPointException麽。

把上述代碼做下修改:

[java] view plain copy
  1. public static synchronized CustomManager getInstance(Context context)
  2. {
  3. if (sInstance == null)
  4. {
  5. sInstance = new CustomManager(context.getApplicationContext());
  6. }
  7. return sInstance;
  8. }


這樣,我們就解決了內存泄漏的問題,因為我們引用的是一個ApplicationContext,它的生命周期和我們的單例對象一致。

這樣的話,可能有人會說,早說嘛,那我們以後都這麽用不就行了,很遺憾的說,不行。上面我們已經說過,Context和Application Context的區別是很大的,也就是說,他們的應用場景(你也可以認為是能力)是不同的,並非所有Activity為Context的場景,Application Context都能搞定。

下面就開始介紹各種Context的應用場景。

Context的應用場景

技術分享

大家註意看到有一些NO上添加了一些數字,其實這些從能力上來說是YES,但是為什麽說是NO呢?下面一個一個解釋:

數字1:啟動Activity在這些類中是可以的,但是需要創建一個新的task。一般情況不推薦。

數字2:在這些類中去layout inflate是合法的,但是會使用系統默認的主題樣式,如果你自定義了某些樣式可能不會被使用。

數字3:在receiver為null時允許,在4.2或以上的版本中,用於獲取黏性廣播的當前值。(可以無視)

註:ContentProvider、BroadcastReceiver之所以在上述表格中,是因為在其內部方法中都有一個context用於使用。

好了,這裏我們看下表格,重點看Activity和Application,可以看到,和UI相關的方法基本都不建議或者不可使用Application,並且,前三個操作基本不可能在Application中出現。實際上,只要把握住一點,凡是跟UI相關的,都應該使用Activity做為Context來處理;其他的一些操作,Service,Activity,Application等實例都可以,當然了,註意Context引用的持有,防止內存泄漏。

大家在以後的使用過程中,能夠稍微考慮下,這裏使用Activity合適嗎?會不會造成內存泄漏?這裏傳入Application work嗎?

=========

技術分享

Context類本身是一個純abstract類,它有兩個具體的實現子類:ContextImpl和ContextWrapper。其中ContextWrapper類,如其名所言,這只是一個包裝而已,ContextWrapper構造函數中必須包含一個真正的Context引用,同時ContextWrapper中提供了attachBaseContext()用於給ContextWrapper對象中指定真正的Context對象,調用ContextWrapper的方法都會被轉向其所包含的真正的Context對象。ContextThemeWrapper類,如其名所言,其內部包含了與主題(Theme)相關的接口,這裏所說的主題就是指在AndroidManifest.xml中通過android:theme為Application元素或者Activity元素指定的主題。當然,只有Activity才需要主題,Service是不需要主題的,因為Service是沒有界面的後臺場景,所以Service直接繼承於ContextWrapper,Application同理。而ContextImpl類則真正實現了Context中的所以函數,應用程序中所調用的各種Context類的方法,其實現均來自於該類。一句話總結:Context的兩個子類分工明確,其中ContextImpl是Context的具體實現類,ContextWrapper是Context的包裝類。Activity,Application,Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper),但它們初始化的過程中都會創建ContextImpl對象,由ContextImpl實現Context中的方法。



技術分享

Context作用域.png ======== getApplication()和getApplicationContext() Application本身就是一個Context,所以這裏獲取getApplicationContext()得到的結果就是Application本身的實例。那麽問題來了,既然這兩個方法得到的結果都是相同的,那麽Android為什麽要提供兩個功能重復的方法呢?實際上這兩個方法在作用域上有比較大的區別。getApplication()方法的語義性非常強,一看就知道是用來獲取Application實例的,但是這個方法只有在Activity和Service中才能調用的到。那麽也許在絕大多數情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場景,比如BroadcastReceiver中也想獲得Application的實例,這時就可以借助getApplicationContext()方法了。
====

Context引起的內存泄露

但Context並不能隨便亂用,用的不好有可能會引起內存泄露的問題,下面就示例兩種錯誤的引用方式。

錯誤的單例模式

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 A去getInstance獲得instance對象,傳入this,常駐內存的Singleton保存了你傳入的Activity A對象,並一直持有,即使Activity被銷毀掉,但因為它的引用還存在於一個Singleton中,就不可能被GC掉,這樣就導致了內存泄漏。

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傳入的this是MainActivity的mContext,因為被static修飾的mDrawable是常駐內存的,MainActivity是它的間接引用,MainActivity被銷毀時,也不能被GC掉,所以造成內存泄漏。

正確使用Context

一般Context造成的內存泄漏,幾乎都是當Context銷毀的時候,卻因為被引用導致銷毀失敗,而Application的Context對象可以理解為隨著進程存在的,所以我們總結出使用Context的正確姿勢:
1:當Application的Context能搞定的情況下,並且生命周期長的對象,優先使用Application的Context。
2:不要讓生命周期長於Activity的對象持有到Activity的引用。
3:盡量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類實例的引用,如果使用靜態內部類,將外部實例引用作為弱引用持有。


=======

android context