1. 程式人生 > >關於Context的理解與總結——什麼是Context?

關於Context的理解與總結——什麼是Context?

作為一個Android開發者,我們在Android開發中經常會使用到Context這個類。它在載入資源、啟動Activity、獲取系統服務、建立View等活動中都需要參與。

但Context到底是什麼,我就很少去關注了…那麼我們該如何理解去Context呢?它到底是什麼呢?

什麼是Context

翻譯角度

Context翻譯為中文,有:上下文、背景、環境等翻譯,我們可以把Context理解成一種環境。Android應用模型是基於元件的應用設計模式,元件的執行要有一個完整的Android工程環境 。

因此Android不像普通Java程式一樣,隨便建立一個類,寫上main方法就可以執行,每個元件需要有自己工作的環境,才能正常執行。而Context,就是我們這裡所說的環境。

比如,當我們需要建立一個Button時,也需要給它提供一個環境:Button button = new Button(context);

一個Activity可以是一個Context,一個Service也可以是一個Context。

原始碼角度

/**
 * Interface to global information about an application environment.  This is
 * an abstract class whose implementation is provided by
 * the Android system.  It
 * allows access to application-specific resources and classes, as well as
 * up-calls for application-level operations such as launching activities,
 * broadcasting and receiving intents, etc.
 */
public abstract class Context { ... }

我們可以看到Context原始碼中的註釋,裡面說到Context提供了關於應用環境的全域性資訊的介面,它是抽象類,呼叫由Android系統來進行。它可以獲取有應用特徵的資源和類,可以執行一些應用級別的操作(如啟動Activity,傳送廣播,接收Intent等等)

Context是一個抽象類,它有兩個實現的子類——ContextImpl 和 ContextWrapper。

ContextWrapper類僅僅是一個包裝類,它的建構函式需要傳遞一個真正的Context的引用。並且它提供了attachBaseContext() 方法來指定真正的Context。呼叫ContextWrapper最終都會呼叫它包含的真正的Context物件的方法。

ContextImpl才是真正的實現類,它實現了Context中的所有方法。平時我們呼叫的所有Context的方法的實現均來自這個類。

Activity、Application、Service三個類均繼承自ContextWrapper,而在具體初始化過程中,則會構造ContextImpl物件,來實現Context中的方法。

Context的作用域

Context在我們日常開發中使用的非常廣泛,但是我們並不是拿到了Context就可以為所欲為。Context的使用會有一些規則的限制。具體的限制方式我們可以參考下面這張表:

img

如何獲取Context

要獲取一個Context,有下面的四種方法:

  • **View.getContext():**返回當前View物件的Context物件,通常是正在展示的Activity物件。
  • **Activity.getApplicationContext():**獲取當前Activity所在的Application的Context物件。(通常我們使用Context物件時,要優先考慮這個全域性的程序Context)
  • **ContextWrapper.getBaseContext()**要獲取一個ContextWrapper裝飾前的Context,可以使用這個方法。
  • **Activity.this():**返回當前的Activity例項,如果是UI控制元件需要使用Activity作為Context物件。但是Toast實際上使用ApplicationContext也可以。

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去呼叫getInstance獲取instance,則Singleton類儲存了Activity的引用,導致Activity被銷燬後仍然不能被GC回收。

例子2

public class MainActivity extends Activity {
    private static Drawable mDrawable;

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

在上面這個例子中,Drawable是靜態的。呼叫ImageView的setImageDrawable設定Drawable時,ImageView就會持有這個Drawable的引用。而ImageView同時還持有Activity的引用,並且它持有的Drawable是常駐記憶體的,導致MainActivity被銷燬時,無法被GC回收。

如何避免

一般Context所導致的記憶體洩漏,都是由於Context被銷燬時,由於它的引用導致無法被回收。但是我們可以使用Application的引用,因為Application的生命週期是隨著程序存在的。

因此我們儘量在使用Context的時候用如下的姿勢:

  • 生命週期長的物件,並且Application的Context可以滿足使用時,優先使用Application的Context。
  • 不要讓宣告週期比Activity長的物件持有Activity的引用
  • 儘量不要在Activity中使用非靜態的內部類。因為非靜態的內部類會隱式持有外部類的引用。如果要使用靜態內部類,使用弱引用來持有外部類的例項。