對于Context,我們并不陌生,項目中,幾乎無處不在,啟動Activity,Service,Broadcast需要它,彈Dialog,Toast需要它,擴充卡裡建立布局也需要它,自定義View中,封裝的工具類中,它的影子随處可見,出鏡率如此之高,你真的了解它嗎?
Context,從翻譯中我們得知,上下文,環境,聽起來比較籠統,舉一個很簡單的例子,你從家要去目的地上班,會怎麼去?火車,飛機還是自駕,那麼你選擇的交通工具,我們就可以了解為一個Context,指的就是我們交通運輸的載體;也正因為如此,在加載資源、啟動Activity、擷取系統服務、建立View等操作,Context都要參與。
我們再做一個剖析,檢視Context的源碼,我們可以得知,Context是個抽象類,通過類的結構可以看到:Activity、Service、Application都是Context的子類,如下圖。
通過以上的分析,我們可以簡單的對Context做個小結:
從Android系統的角度來了解:Context是一個場景,描述的是一個應用程式環境的資訊,即上下文,代表與作業系統的互動的一種過程。
從程式的角度上來了解:Context是個抽象類,而Activity、Service、Application等都是該類的一個實作。
了解了Context是個什麼玩意後,不妨我們再進一步分析分析,它的繼承結構,它的常用方法,它的作用域等。
1、繼承結構
從上圖可以看出 Context的繼承結構,直接子類有兩個ContextWarpper和ContextImpl。
ContextWarpper: 是上下文功能的封裝類,它又有三個直接的子類,ContextThemeWarpper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而他有一個直接子類就是Activity。這裡我們看到了幾個比較熟悉的類:Activity、Service、Application。由此我們大緻得出結論,Context一共有三種類型,分别是Activity、Service、Application。這三個類雖然分别承擔各種不同的作用,但他們都屬于Context的一種,而他們具體Context的功能是由ContextImpl類實作的。
ContextImpl:是上下文功能實作類。
2、Context常用方法
//擷取應用程式包的AssetManager執行個體
public abstract AssetManger getAssets();
//擷取應用程式包Resources 執行個體
public abstract Resources getResources();
//擷取PackageManager執行個體,以檢視全局package資訊
public abstract PackageManager getPackageManager();
//擷取應用程式包的ContentResolver
public abstract ContentResolver getContentResolved();
//它傳回目前程序的主線程的Looper,此線程分發調用給應用元件(activities,services等)
public abstract Looper getMainLooper();
// 傳回目前程序的單執行個體全局Application對象的Context
public abstract Context getApplicationContext();
//從strng表中擷取本地化的。格式化的字元序列
public final CharSequence getText(int resId){
return getResources().getText(resId);
}
// 從string表中擷取本地化的字元串
public final String getString(int resId) {
return getResources().getString(resId);
}
public final String getString(int resId, Object... formatArgs) {
return getResources().getString(resId, formatArgs);
}
//傳回一個可用于擷取包中類資訊的class loader
public abstract ClassLoader getClassLoader();
//傳回應用程式包名
public abstract String getPackageName();
//傳回應用程式資訊
public abstract ApplicationInfo getApplicationInfo():
//根據檔案名擷取SharedPreferences
public abstract SharedPreferences getSharedPreferences(String name , int mode);
//其根目錄為: Environment.getExternalStorageDirectory()
public abstract File getExternalFilesDir(String type);
//傳回應用程式obb檔案路徑
public abstract File getObbDir();
//啟動一個新的Activity
public abstract void startActivity(Intent intent);
// 啟動一個新的activity
public void startActivityAsUser(Intent intent, UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
// 啟動一個新的activity
// intent: 将被啟動的activity的描述資訊
// options: 描述activity将如何被啟動
public abstract void startActivity(Intent intent, Bundle options);
// 啟動多個新的activity
public abstract void startActivities(Intent[] intents);
// 啟動多個新的activity
public abstract void startActivities(Intent[] intents, Bundle options);
// 廣播一個intent給所有感興趣的接收者,異步機制
public abstract void sendBroadcast(Intent intent);
// 廣播一個intent給所有感興趣的接收者,異步機制
public abstract void sendBroadcast(Intent intent,String receiverPermission);
//發送有序廣播
public abstract void sendOrderedBroadcast(Intent intent,String receiverPermission);
public abstract void sendOrderedBroadcast(Intent intent,
String receiverPermission, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
Bundle initialExtras);
public abstract void sendBroadcastAsUser(Intent intent, UserHandle user);
public abstract void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission);
// 注冊一個BroadcastReceiver,且它将在主activity線程中運作
public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter);
//取消注冊BroadcastReceiver
public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission, Handler scheduler);
public abstract void unregisterReceiver(BroadcastReceiver receiver);
// 請求啟動一個application service
public abstract ComponentName startService(Intent service);
// 請求停止一個application service
public abstract boolean stopService(Intent service);
// 連接配接一個應用服務,它定義了application和service間的依賴關系
public abstract boolean bindService(Intent service, ServiceConnection conn,
int flags);
// 斷開一個應用服務,當服務重新開始時,将不再接收到調用,
// 且服務允許随時停止
public abstract void unbindService(ServiceConnection conn);
// 傳回系統級service
public abstract Object getSystemService(String name);
//檢查權限
public abstract int checkPermission(String permission, int pid, int uid);
// 傳回一個新的與application name對應的Context對象
public abstract Context createPackageContext(String packageName,
int flags) throws PackageManager.NameNotFoundException;
// 傳回基于目前Context對象的新對象,其資源與display相比對
public abstract Context createDisplayContext(Display display);
3、Context主要功能:
1)啟動Activity
2)啟動和停止Service
3)發送廣播消息(Intent)
4)注冊廣播消息(Intent)接收者
5)可以通路APK中各種資源(如Resources和AssetManager等)
6)可以通路Package的相關資訊
7)APK的各種權限管理
4、Context 應用場景
因為Context的具體能力是由ContextImpl類去實作的,是以在絕大多數場景下,Activity、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊,比如啟動Activity,還有彈出Dialog。出于安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啟動必須要建立在另一個Activity的基礎之上,也就是以此形成的傳回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),是以在這種場景下,我們隻能使用Activity類型的Context,否則将會出錯。
Context的應用場景圖
5、Context如何擷取
1)、View.getContext,傳回目前View對象的Context對象,通常是目前正在展示的Activity對象。
2)、Activity.getApplicationContext,擷取目前Activity所在的程序的Context對象,通常我們使用Context對象時,要優先考慮這個全局的程序Context。
3)、ContextWarpper.getBaseContext():用來擷取一個ContextWarpper進行裝飾之前的Context,可以使用這個方法,這個方法在實際開發中使用并不多,也不建議使用。
4)、Activity.this 傳回目前的Activity執行個體,如果是UI控件需要使用Activity作為Context對象,但是預設的Toast實際上使用ApplicationContext也可以。
getApplicationContext()和getApplication()
getApplicationContext :取得的是目前app所使用的application,這在AndroidManifest中唯一指定。意味着,在目前app的任意位置使用這個函數得到的是同一個Context,getApplicationContext():傳回應用的上下文,生命周期是整個應用,應用摧毀,它才摧毀。
getApplication:android開發中共享全局資料;
getApplication() 隻能在Activity和Service裡使用,指向的是Application對象,因為Application也是Context的一個子類,是以getApplication()可以被用來指向Context。
比如如果想要擷取在應用清單檔案中聲明的類,最好不要使用getApplicationContext(),并且最好使用強制轉換為自己自定義的Application,因為那樣可能會得不到Application對象。
如果我們也想在BroadcastReceiver中也想獲得Application執行個體,這時就可以借助getApplicationContext()方法了。
6、Context 使用過程中的注意項
1)、Activity mActivity =new Activity()
這樣寫文法上沒有任何錯誤,Android的應用程式開發采用JAVA語言,Activity本質上也是一個對象。但是,
Android程式不像Java程式一樣,随便建立一個類,寫個main()方法就能運作,Android應用模型是基于元件的應用設計模式,元件的運作要有一個完整的Android工程環境,在這個環境下,Activity、Service等系統元件才能夠正常工作,而這些元件并不能采用普通的Java對象建立方式,new一下就能建立執行個體了,而是要有它們各自的上下文環境,才能使得其正常工作。即走正常的onCreate-onStart-onResume。
2)、我們在編寫成單例的方式時,這些工具類大多需要去通路資源,也就說需要Context的參與。
在這樣的情況下,就需要注意Context的引用問題。
public class TestInstence
{
private static TestInstence sInstance;
private Context mContext;
private TestInstence(Context context)
{
this.mContext = context;
}
public static synchronized TestInstence getInstance(Context context)
{
if (sInstance == null)
{
sInstance = new TestInstence(context);
}
return sInstance;
}
}
對于上述的單例,内部保持了一個Context的引用;這麼寫是沒有問題的,問題在于,這個Context哪來的我們不能确定,很大的可能性,你在某個Activity裡面為了友善,直接傳了個this;這樣問題就來了,我們的這個類中的sInstance是一個static且強引用的,在其内部引用了一個Activity作為Context,也就是說,我們的這個Activity隻要我們的項目活着,就沒有辦法進行記憶體回收。而我們的Activity的生命周期肯定沒這麼長,是以造成了記憶體洩漏。那麼,我們如何才能避免這樣的問題呢?有人會說,我們可以軟引用,嗯,軟引用,假如被回收了,你不怕NullPointException麼。把上述代碼做下修改:
public static synchronized TestInstence getInstance(Context context)
{
if (sInstance == null)
{
sInstance = new TestInstence(context.getApplicationContext());
}
return sInstance;
}
這樣,我們就解決了記憶體洩漏的問題,因為我們引用的是一個ApplicationContext,它的生命周期和我們的單例對象一緻。
3)、Intent也要求指出上下文,如果想啟動一個新的Activity,就必須在Intent中使用Activity的上下文,這樣新啟動的Activity才能和目前Activity有關聯(在activity棧);也可以使用application的context,但是需要在Intent中添加 Intent.FLAG_ACTIVITY_NEW_TASK标志,當作一個新任務。ApplicationContext去啟動一個LaunchMode為standard的Activity的時候會報錯,非Activity類型的Context并沒有所謂的任務棧,是以待啟動的Activity就找不到棧了。解決這個問題的方法就是為待啟動的Activity指定FLAG_ACTIVITY_NEW_TASK标記位,這樣啟動的時候就為它建立一個新的任務棧,而此時Activity是以singleTask模式啟動的。是以這種用Application啟動Activity的方式不推薦使用,Service同Application。
public static void startActivity(Context context){
Intent intent = new Intent(context.getApplicationContext(), SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.getApplicationContext().startActivity(intent);
}
總結,如何在程式中正确的使用Context:
一般Context造成的記憶體洩漏,幾乎都是當Context銷毀的時候,因為被引用導緻銷毀失敗。而Application的Context對象可以簡單的了解為伴随着程序存在的(它的生命周期也很長,畢竟APP加載的時候先加載Application,我們可以自定義Application然後繼承系統的Application)。
正确使用:
1、當Applicatin的Context能搞定的情況下,并且生命周期長的對象,優先使用Application的Context;
2、不要讓生命周期長于Activity的對象持有Activity的引用。
3、盡量不要在Activity中使用非靜态内部類。非靜态内部類會隐式持有外部類執行個體的引用。如果使用靜态内部類,将外部執行個體引用作為弱引用持有。