《第一行代碼Android2版》筆記六:Context詳解

 

1、Context 概念

 Context是個抽象類,通過類的結構可以看到:Activity、Service、Application都是Context的子類;

從Android系統的角度來理解:Context是一個場景,描述的是一個應用程序環境的信息,即上下文,代表與操作系統的交互的一種過程。

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

查看類的繼承關係:ctrl + H         (Windows系統)

應用在三種情況下會創建Context對象(即通常說的context): 
1> 創建Application 對象時,即第一次啓動app時。 整個App共一個Application對象,所以也只有一個Application 的Context,Application銷燬,它也銷燬; 
2> 創建Activity對象時。Activity銷燬,它也銷燬; 
3> 創建Service對象時。Service銷燬,它也銷燬。

由此可以得到應用程序App可以創建的Context(Activity和Service沒啓動就不會創建)個數公式一般爲: 
總Context實例個數 = Service個數 + Activity個數 + 1(Application對應的Context對象) 

 

2.Context 繼承結構

Context的繼承結構還是稍微有點複雜的,可以看到,直接子類有兩個,一個是ContextWrapper,一個是ContextImpl。那麼從名字上就可以看出,ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類。ContextWrapper又有三個直接的子類,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity。在這裏我們看到了幾個所比較熟悉的面孔,Activity、Service、還有Application。由此,我們可以大致得出結論,Context一共有三種類型,分別是Application、Activity和Service。這三個類雖然分別各種承擔着不同的作用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的。其實Context還有一個直接子類MockContext,,該類可以理解爲模擬context,源碼位於android.test.mock包中,API文檔中找不到。當我們要測試一個模塊A,他依賴於其它模塊B,但是模塊B還沒實現或現在根本沒有,這時就要使用MockContext和其他同樣位於android.test.mock包中的類。通過它可以注入其他依賴,模擬Context,或者監聽測試的類。主要是在TDD中使用這些MOCK類來代替真實的類,用法可參考Mock在Android TDD中的使用。想查看ContextImpl的源碼時,無法找到ContextImpl這個類。由於ContextImpl是抽象類Context的實現類。然而查看Context類的繼承結構,如下圖:沒有發現ContextImpl。後來查到原因是:這個文件是保護文件,就是註解了是內部保護文件,所以在eclipse,Androidstudio中都是不顯示的。所以可以去SDk的安裝目錄中的sources文件夾中直接找那個Java文件,/android-sdk/sources/android-19/android/app/ContextImpl.java。Mockcontext同理。

 

3、Context 常用方法

// 獲取應用程序包的AssetManager實例
public abstract AssetManager getAssets();

// 獲取應用程序包的Resources實例
public abstract Resources getResources();

// 獲取PackageManager實例,以查看全局package信息
public abstract PackageManager getPackageManager();

// 獲取應用程序包的ContentResolver實例
public abstract ContentResolver getContentResolver();

// 它返回當前進程的主線程的Looper,此線程分發調用給應用組件(activities, services等)
public abstract Looper getMainLooper();

// 返回當前進程的單實例全局Application對象的Context
public abstract Context getApplicationContext();

// 從string表中獲取本地化的、格式化的字符序列
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);

Context的主要功能爲:

1)啓動Activity
2)啓動和停止Service
3)發送廣播消息(Intent)
4)註冊廣播消息(Intent)接收者
5)可以訪問APK中各種資源(如Resources和AssetManager等)
6)可以訪問Package的相關信息
7)APK的各種權限管理

Context幾乎算是對APK包無所不知的大管家,大家需要什麼,Context子類裏(通常在Activity和Service)直接調用就可以了。

 

4、Context 如何獲取

通常我們想要獲取Context對象,主要有以下四種方法 
1:View.getContext,返回當前View對象的Context對象,通常是當前正在展示的Activity對象。 
2:Activity.getApplicationContext,獲取當前Activity所在的(應用)進程的Context對象,通常我們使用Context對象時,要優先考慮這個全局的進程Context。 
3:ContextWrapper.getBaseContext():用來獲取一個ContextWrapper進行裝飾之前的Context,可以使用這個方法,這個方法在實際開發中使用並不多,也不建議使用。 
4:Activity.this 返回當前的Activity實例,如果是UI控件需要使用Activity作爲Context對象,但是默認的Toast實際上使用ApplicationContext也可以。

 

public class MyActivity extends Activity {
    Context mContext;
    public void method() {

        mContext = this; //獲取當前Activity的上下文,如果需要綁定Activity的生命週期,使用它

        mContext=MyActivity.this;//獲取當前MyActivity的上下文,不方便使用this的時候推薦使用這種方式

        //調用Activity.getApplicationContext()
        mContext = getApplicationContext();//獲取當前Application的上下文,如果需要綁定應用的生命週期,使用它

        //Activity.getApplication()
        mContext = getApplication();//獲取當前Application的上下文,

        //調用ContextWrapper.getBaseContext()
        mContext = getBaseContext();//從上下文A內上下文訪問上下文A,不建議使用,如果需要,推薦使用XxxClass.this直接指出上下文
    }
}


public class MyView extends View {
    Context mContext;
    public void method() {

        //調用View.getContext()
        mContext = getContext(); //獲取這個View運行所在地的上下文
    }
}

1)this和getBaseContext()

  • this:代表當前,在Activity當中就是代表當前的Activity,換句話說就是Activity.this在Activity當中可以縮寫爲this。Activity.this的context 返回當前activity的上下文,屬於activity ,activity 摧毀他就摧毀。
  • getBaseContext() 返回由構造函數指定或setBaseContext()設置的上下文。
    Spinner spinner = (Spinner) findViewById(R.id.spinner);
    spinner.setAdapter(adapter);            
    spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?>arg0, View arg1, int arg2, long arg3){
          //Toast.makeText(getBaseContext(),"SELECTED", Toast.LENGTH_SHORT).show();//可以使用它,但是不建議
            Toast.makeText(MainActivity.this,"SELECTED", Toast.LENGTH_SHORT).show();//推薦使用XxxClass.this直接指出了使用的是誰的上下文,更簡捷。
          //Toast.makeText(this,"SELECTED", Toast.LENGTH_SHORT).show();//不可用,這裏this指的不是Activity,而是spinner這個類。
    
        }
    }

     

 

 

2)getApplicationContext()和getApplication()

  • getApplicationContext 取得的是當前app所使用的application,這在AndroidManifest中唯一指定。意味着,在當前app的任意位置使用這個函數得到的是同一個Context,getApplicationContext(): 返回應用的上下文,生命週期是整個應用,應用摧毀,它才摧毀。
  • getApplication():andorid 開發中共享全局數據;

getApplication()只能在Activity和Service裏使用,指向的是Application對象,因爲Application也是Context的一個子類,所以getApplication()可以被用來指向Context。

比如如果想要獲取在應用清單文件中聲明的類,最好不要使用getApplicationContext(),並且最好使用強制轉換爲自己自定義的Application,因爲那樣可能會得不到Application對象。

Log.i("dyl", "getApplication is = " + myApp);
  Log.i("dyl", "getApplicationContext is = " + appContext);

通過上面的代碼,打印得出兩者的內存地址都是相同的,看來它們是同一個對象。其實這個結果也很好理解,因爲前面已經說過了,Application本身就是一個Context,所以這裏獲取getApplicationContext()得到的結果就是Application本身的實例。那麼問題來了,既然這兩個方法得到的結果都是相同的,那麼Android爲什麼要提供兩個功能重複的方法呢?實際上這兩個方法在作用域上有比較大的區別。getApplication()方法的語義性非常強,一看就知道是用來獲取Application實例的,但是這個方法只有在Activity和Service中才能調用的到。那麼也許在絕大多數情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場景,比如BroadcastReceiver中也想獲得Application的實例,這時就可以藉助getApplicationContext()方法了。

 

5.Context 應用場景

因爲Context的具體能力是由ContextImpl類去實現的,所以在絕大多數場景下,Activity、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊,比如啓動Activity,還有彈出Dialog。出於安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啓動必須要建立在另一個Activity的基礎之上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下,我們只能使用Activity類型的Context,否則將會出錯。

Context的應用場景圖

大家注意看到有一些NO上添加了一些數字,其實這些從能力上來說是YES,但是爲什麼說是NO呢?下面一個一個解釋:

數字1:啓動Activity在這些類中是可以的,但是需要創建一個新的task。一般情況不推薦。

數字2:在這些類中去layout inflate是合法的,但是會使用系統默認的主題樣式,如果你自定義了某些樣式可能不會被使用。

數字3:在receiver爲null時允許,在4.2或以上的版本中,用於獲取黏性廣播的當前值。(可以無視)

注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因爲在其內部方法中都有一個context用於使用。

 好了,這裏我們看下錶格,重點看Activity和Application,可以看到,和UI相關的方法基本都不建議或者不可使用Application,並且,前三個操作基本不可能在Application中出現。實際上,只要把握住一點,凡是跟UI相關的,都應該使用Activity做爲Context來處理;其他的一些操作,Service,Activity,Application等實例都可以,當然了,注意Context引用的持有,防止內存泄漏。

 

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 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;  
    }  
}  

對於上述的單例,大家應該都不陌生(請別計較getInstance的效率問題),內部保持了一個Context的引用;這麼寫是沒有問題的,問題在於,這個Context哪來的我們不能確定,很大的可能性,你在某個Activity裏面爲了方便,直接傳了個this;這樣問題就來了,我們的這個類中的sInstance是一個static且強引用的,在其內部引用了一個Activity作爲Context,也就是說,我們的這個Activity只要我們的項目活着,就沒有辦法進行內存回收。而我們的Activity的生命週期肯定沒這麼長,所以造成了內存泄漏。那麼,我們如何才能避免這樣的問題呢?有人會說,我們可以軟引用,嗯,軟引用,假如被回收了,你不怕NullPointException麼。把上述代碼做下修改:

 

public static synchronized CustomManager getInstance(Context context)  
    {  
        if (sInstance == null)  
        {  
            sInstance = new CustomManager(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 openActivity(Context context){
        Intent intent = new Intent(context.getApplicationContext(), SecondActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.getApplicationContext().startActivity(intent);
    }

 

 

 

參考博文:

http://blog.csdn.net/guolin_blog/article/details/47028975

http://www.jianshu.com/p/94e0f9ab3f1d

http://blog.csdn.net/u012585964/article/details/52268235

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章