關於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的使用會有一些規則的限制。具體的限制方式我們可以參考下面這張表:
如何獲取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中使用非靜態的內部類。因為非靜態的內部類會隱式持有外部類的引用。如果要使用靜態內部類,使用弱引用來持有外部類的例項。