天天看點

重溫Context

對于Context,我們并不陌生,項目中,幾乎無處不在,啟動Activity,Service,Broadcast需要它,彈Dialog,Toast需要它,擴充卡裡建立布局也需要它,自定義View中,封裝的工具類中,它的影子随處可見,出鏡率如此之高,你真的了解它嗎?

重溫Context

Context,從翻譯中我們得知,上下文,環境,聽起來比較籠統,舉一個很簡單的例子,你從家要去目的地上班,會怎麼去?火車,飛機還是自駕,那麼你選擇的交通工具,我們就可以了解為一個Context,指的就是我們交通運輸的載體;也正因為如此,在加載資源、啟動Activity、擷取系統服務、建立View等操作,Context都要參與。

我們再做一個剖析,檢視Context的源碼,我們可以得知,Context是個抽象類,通過類的結構可以看到:Activity、Service、Application都是Context的子類,如下圖。

重溫Context

通過以上的分析,我們可以簡單的對Context做個小結:

從Android系統的角度來了解:Context是一個場景,描述的是一個應用程式環境的資訊,即上下文,代表與作業系統的互動的一種過程。

從程式的角度上來了解:Context是個抽象類,而Activity、Service、Application等都是該類的一個實作。

了解了Context是個什麼玩意後,不妨我們再進一步分析分析,它的繼承結構,它的常用方法,它的作用域等。

1、繼承結構

重溫Context

從上圖可以看出 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的應用場景圖

重溫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中使用非靜态内部類。非靜态内部類會隐式持有外部類執行個體的引用。如果使用靜态内部類,将外部執行個體引用作為弱引用持有。