Android進階之Context(上下文)你必須知道的一切
1 Context概念
(1)在啟動Activity/Service,傳送廣播,獲取系統資源,獲取系統服務等都需要Context的參與,可見Context的常見性。到底什麼是Context,Context字面意思上下文,或者叫做場景,也就是使用者與作業系統操作的一個過程,比如你打電話,場景包括電話程式對應的介面,以及隱藏在背後的資料。
1.1 Android系統的角度Context是什麼呢?
Context是一個場景,代表與作業系統的互動的一種過程,是維持Android程式中各元件能夠正常工作的一個核心功能類。
1.2 在程式的角度Context是什麼呢?
(1)在程式的角度,Context是個抽象類,定義了各種抽象方法,包括啟動Activity/Service,傳送廣播,獲取系統資源,獲取系統服務等。Activity、Service、Application都是Context的的一個實現(子類),可以直接通過看其類結構來說明答案:
(2)Context類原始碼解析
public abstract class Context {
public abstract Resources getResources();
}
(3)ContextWrapper的原始碼解析:Activity、Service、Application都是繼承自ContextWrapper,而ContextWrapper內部會包含一個mBase的Context,由這個mBase去實現了絕大多數的方法。
public class ContextWrapper extends Context { Context mBase; public ContextWrapper(Context base) { mBase = base; } protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; } @Override public Resources getResources() { return mBase.getResources(); } }
(4)ContextThemeWrapper的原始碼解析
Context直接子類為ContextIml(具體實現類)和ContextWrapper(上下文功能包裝類),而ContextWrapper又有三個子類,分別是ContextThemeWrapper、Service和Application。基於Activity和Service、Application不在一個繼承層級裡,而是又繼承了ContextThemeWrapper。
ContextThemeWrapper是一個帶主題的封裝類,內部包含了主題(Theme)相關的介面,當Activity在啟動的時候系統都會載入一個主題,也就是我們在配置檔案AndroidManifest.xml裡面寫的android:theme=”@style/AppTheme”的屬性啦!(如下圖所示),可是Service和Applicaton並不需要載入主題,因此他們繼承自ContextWrapper。
2 Context與Application的Context(getApplicationContext)的區別
XXXActivity和getApplicationContext返回的肯定不是一個物件,一個是當前Activity的例項,一個是專案的Application的例項。各自的使用場景肯定不同,亂使用可能會帶來一些問題。
(1)工具類,可能會編寫成單例的方式,這些工具類大多需要去訪問資源,也就說需要Context的參與。在這樣的情況下,就需要注意Context的引用問題。
public class CustomManager{
private static CustomManager sInstance;
private Context mContext;
private CustomManager(Context context) {
this.mContext = context;
}
public static synchronized CustomManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new CustomManager(context);
}
return sInstance;
}
//some methods
private void someOtherMethodNeedContext(){
}
}
問題在於:這個Context哪來的我們不能確定,很大的可能性。在某個Activity裡面為了方便,直接傳了個this,這樣問題就來了,這個類中的sInstance是一個static且強引用的,在其內部引用了一個Activity作為Context,也就是說,我們的這個Activity只要我們的專案活著,就沒有辦法進行記憶體回收。而我們的Activity的生命週期肯定沒這麼長,所以造成了記憶體洩漏。
解決方法1:可以軟引用,嗯,軟引用,假如被回收了,會引起NullPointException。
解決方法2:引用的是一個ApplicationContext,讓它的生命週期和單例物件一致。
public static synchronized CustomManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new CustomManager(context.getApplicationContext());
}
return sInstance;
}
3 Context的應用場景
3.1 場景圖
3.2 數字標註提示
(1)數字1:啟動Activity在這些類中是可以的,但是需要建立一個新的task。一般情況不推薦。
(2)數字2:在這些類中去layout inflate是合法的,但是會使用系統預設的主題樣式,如果你自定義了某些樣式可能不會被使用。
(3)數字3:在receiver為null時允許,在4.2或以上的版本中,用於獲取黏性廣播的當前值。(可以無視)
(4)注意:ContentProvider、BroadcastReceiver之所以在上述表格中,是因為在其內部方法中都有一個context用於使用。
3.3 小結
(1)和UI相關的方法基本都不建議或者不可使用Application,並且,前三個操作基本不可能在Application中出現。實際上,凡是跟UI相關的,都應該使用Activity做為Context來處理;其他的一些操作(Service,Activity,Application)等例項都可以,注意Context引用的持有,防止記憶體洩漏。
(2)Toast通常使用Activity和Application的context,也可以使用Service、ContentProvider和BroadcastReceiver的context。但是在IntentService的onHandleIntent()不能使用,因為其在子執行緒中。
4 Context數量
在建立Activity、Service、Application時都會自動建立Context,它們各自維護著自己的上下文。在Android系統中Context類的繼承結構中Context一共有Application、Activity和Service三種類型,因此如果要統計一個app中Context數量,可以這樣來表示:
// 1表示Application數量。一個應用程式中可以有多個Activity和多個Service,但只有一個Application。
Context數量 = Activity數量 + Service數量 + 1
備註:可能有人會說一個應用程式裡面可以有多個Application啊,我的理解是:一個應用程式裡面可以有多個Application,可是在配置檔案AndroidManifest.xml中只能註冊一個,只有註冊的這個Application才是真正的Application,才會呼叫到全部的生命週期,所以Application的數量是1。
5 總結
Context的分析基本完成了,希望在以後的使用過程中,能夠稍微考慮下,這裡使用Activity合適嗎?會不會造成記憶體洩漏?這裡傳入Application work嗎?