1. 程式人生 > >Android中Context詳解 ---- 你所不知道的Context

Android中Context詳解 ---- 你所不知道的Context

             前言:本文是我讀《Android核心剖析》第7章 後形成的讀書筆記 ,在此向欲瞭解Android框架的書籍推薦此書。

        大家好,  今天給大家介紹下我們在應用開發中最熟悉而陌生的朋友-----Context類 ,說它熟悉,是應為我們在開發中

   時刻的在與它打交道,例如:Service、BroadcastReceiver、Activity等都會利用到Context的相關方法 ; 說它陌生,完全是

   因為我們真正的不懂Context的原理、類結構關係。一個簡單的問題是,一個應用程式App中存在多少個Context例項物件呢?

   一個、兩個? 在此先賣個關子吧。讀了本文,相信您會豁然開朗的 。

      Context,中文直譯為“上下文”,SDK中對其說明如下:

      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

    從上可知一下三點,即:

        1、它描述的是一個應用程式環境的資訊,即上下文。

        2、該類是一個抽象(abstract class)類,Android提供了該抽象類的具體實現類(後面我們會講到是ContextIml類)。

        3、通過它我們可以獲取應用程式的資源和類,也包括一些應用級別操作,例如:啟動一個Activity,傳送廣播,接受Intent

      資訊 等。。

   於是,我們可以利用該Context物件去構建應用級別操作(application-level operations) 。

 一、Context相關類的繼承關係

                         

  相關類介紹:

   Context類    路徑: /frameworks/base/core/java/android/content/Context.java

            說明:  抽象類,提供了一組通用的API。

      原始碼(部分)如下:

public abstract class Context {
	 ...
 	 public abstract Object getSystemService(String name);  //獲得系統級服務
	 public abstract void startActivity(Intent intent);     //通過一個Intent啟動Activity
	 public abstract ComponentName startService(Intent service);  //啟動Service
	 //根據檔名得到SharedPreferences物件
	 public abstract SharedPreferences getSharedPreferences(String name,int mode);
	 ...
}

  ContextIml.java類  路徑 :/frameworks/base/core/java/android/app/ContextImpl.java

          說明:該Context類的實現類為ContextIml,該類實現了Context類的功能。請注意,該函式的大部分功能都是直接呼叫

      其屬性mPackageInfo去完成,這點我們後面會講到。    

         原始碼(部分)如下:

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context{
	//所有Application程式公用一個mPackageInfo物件
    /*package*/ ActivityThread.PackageInfo mPackageInfo;
    
    @Override
    public Object getSystemService(String name){
    	...
    	else if (ACTIVITY_SERVICE.equals(name)) {
            return getActivityManager();
        } 
    	else if (INPUT_METHOD_SERVICE.equals(name)) {
            return InputMethodManager.getInstance(this);
        }
    } 
    @Override
    public void startActivity(Intent intent) {
    	...
    	//開始啟動一個Activity
        mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1);
    }
}

ContextWrapper類 路徑 :\frameworks\base\core\java\android\content\ContextWrapper.java

        說明: 正如其名稱一樣,該類只是對Context類的一種包裝,該類的建構函式包含了一個真正的Context引用,即ContextIml

       物件。    原始碼(部分)如下:

public class ContextWrapper extends Context {
    Context mBase;  //該屬性指向一個ContextIml例項,一般在建立Application、Service、Activity時賦值
    
    //建立Application、Service、Activity,會呼叫該方法給mBase屬性賦值
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);  //呼叫mBase例項方法
    }
}

   ContextThemeWrapper類 路徑:/frameworks/base/core/java/android/view/ContextThemeWrapper.java

      說明:該類內部包含了主題(Theme)相關的介面,即android:theme屬性指定的。只有Activity需要主題,Service不需要主題,

   所以Service直接繼承於ContextWrapper類。

      原始碼(部分)如下:

public class ContextThemeWrapper extends ContextWrapper {
	 //該屬性指向一個ContextIml例項,一般在建立Application、Service、Activity時賦值
	 
	 private Context mBase;
	//mBase賦值方式同樣有一下兩種
	 public ContextThemeWrapper(Context base, int themeres) {
	        super(base);
	        mBase = base;
	        mThemeResource = themeres;
	 }

	 @Override
	 protected void attachBaseContext(Context newBase) {
	        super.attachBaseContext(newBase);
	        mBase = newBase;
	 }
}

     Activity類 、Service類 、Application類本質上都是Context子類, 更多資訊大家可以自行參考原始碼進行理解。

二、 什麼時候建立Context例項 

      熟悉了Context的繼承關係後,我們接下來分析應用程式在什麼情況需要建立Context物件的?應用程式建立Context例項的

 情況有如下幾種情況:

      1、建立Application 物件時, 而且整個App共一個Application物件

      2、建立Service物件時

      3、建立Activity物件時

    因此應用程式App共有的Context數目公式為:

                     總Context例項個數 = Service個數 + Activity個數 + 1(Application對應的Context例項)

  具體建立Context的時機

     1、建立Application物件的時機

       每個應用程式在第一次啟動時,都會首先建立Application物件。如果對應用程式啟動一個Activity(startActivity)流程比較

清楚的話,建立Application的時機在建立handleBindApplication()方法中,該函式位於 ActivityThread.java類中 ,如下:

//建立Application時同時建立的ContextIml例項
private final void handleBindApplication(AppBindData data){
    ...
	///建立Application物件
    Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    ...
}

public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
	...
	try {
        java.lang.ClassLoader cl = getClassLoader();
        ContextImpl appContext = new ContextImpl();    //建立一個ContextImpl物件例項
        appContext.init(this, null, mActivityThread);  //初始化該ContextIml例項的相關屬性
        ///新建一個Application物件 
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
       appContext.setOuterContext(app);  //將該Application例項傳遞給該ContextImpl例項         
    } 
	...
}

    2、建立Activity物件的時機

       通過startActivity()或startActivityForResult()請求啟動一個Activity時,如果系統檢測需要新建一個Activity物件時,就會

  回撥handleLaunchActivity()方法,該方法繼而呼叫performLaunchActivity()方法,去建立一個Activity例項,並且回撥

 onCreate(),onStart()方法等, 函式都位於 ActivityThread.java類 ,如下:

//建立一個Activity例項時同時建立ContextIml例項
private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) {
	...
	Activity a = performLaunchActivity(r, customIntent);  //啟動一個Activity
}
private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent) {
	...
	Activity activity = null;
    try {
    	//建立一個Activity物件例項
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    }
    if (activity != null) {
        ContextImpl appContext = new ContextImpl();      //建立一個Activity例項
        appContext.init(r.packageInfo, r.token, this);   //初始化該ContextIml例項的相關屬性
        appContext.setOuterContext(activity);            //將該Activity資訊傳遞給該ContextImpl例項
        ...
    }
    ...    
}

3、建立Service物件的時機

       通過startService或者bindService時,如果系統檢測到需要新建立一個Service例項,就會回撥handleCreateService()方法,

 完成相關資料操作。handleCreateService()函式位於 ActivityThread.java類,如下:

//建立一個Service例項時同時建立ContextIml例項
private final void handleCreateService(CreateServiceData data){
	...
	//建立一個Service例項
	Service service = null;
    try {
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = (Service) cl.loadClass(data.info.name).newInstance();
    } catch (Exception e) {
    }
	...
	ContextImpl context = new ContextImpl(); //建立一個ContextImpl物件例項
    context.init(packageInfo, null, this);   //初始化該ContextIml例項的相關屬性
    //獲得我們之前建立的Application物件資訊
    Application app = packageInfo.makeApplication(false, mInstrumentation);
    //將該Service資訊傳遞給該ContextImpl例項
    context.setOuterContext(service);
    ...
}

另外,需要強調一點的是,通過對ContextImp的分析可知,其方法的大多數操作都是直接呼叫其屬性mPackageInfo(該屬性類

型為PackageInfo)的相關方法而來。這說明ContextImp是一種輕量級類,而PackageInfo才是真正重量級的類。而一個App裡的

有ContextIml例項,都對應同一個packageInfo物件。

     最後給大家分析利用Context獲取SharedPreferences類的使用方法,SharedPreferences類想必大家都使用過,其一般獲取方

法就是通過呼叫getSharedPreferences()方法去根據相關資訊獲取SharedPreferences物件。具體流程如下:

    1 、呼叫  getSharedPreferences()獲取對應的的檔案,該函式實現功能如下:

//Context類靜態資料集合,以鍵值對儲存了所有讀取該xml檔案後所形成的資料集合
private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs = 
	   new HashMap<File, SharedPreferencesImpl>(); 

@Override
public SharedPreferences getSharedPreferences(String name, int mode){
	 //其所對應的SharedPreferencesImpl物件 ,該物件已一個HashMap集合儲存了我們對該檔案序列化結果
	 SharedPreferencesImpl sp;  
     File f = getSharedPrefsFile(name);  //該包下是否存在對應的檔案,不存在就新建一個
     synchronized (sSharedPrefs) {       //是否已經讀取過該檔案,是就直接返回該SharedPreferences物件
         sp = sSharedPrefs.get(f);
         if (sp != null && !sp.hasFileChanged()) {
             //Log.i(TAG, "Returning existing prefs " + name + ": " + sp);
             return sp;
         }
     }
     //以下為序列化該xml檔案,同時將資料寫到map集合中     
     Map map = null;
     if (f.exists() && f.canRead()) {
         try {
             str = new FileInputStream(f);
             map = XmlUtils.readMapXml(str);
             str.close();
         } 
         ...
     }
     
     synchronized (sSharedPrefs) {
         if (sp != null) {
             //Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map);
             sp.replace(map);   //更新資料集合
         } else {
             sp = sSharedPrefs.get(f);
             if (sp == null) {  
            	 //新建一個SharedPreferencesImpl物件,並且設定其相關屬性
                 sp = new SharedPreferencesImpl(f, mode, map);  
                 sSharedPrefs.put(f, sp);
             }
         }
         return sp;
     }
}

   2、 SharedPreferences 不過是個介面,它定義了一些操作xml檔案的方法,其真正實現類為SharedPreferencesImpl ,該類是

    ContextIml的內部類,該類如下:

//soga,這種形式我們在分析Context ContextIml時接觸過 
//SharedPreferences只是一種介面,其真正實現類是SharedPreferencesImpl類
private static final class SharedPreferencesImpl implements SharedPreferences{
	 private Map mMap;  //儲存了該檔案序列化結果後的操作, 鍵值對形式
	 
	 //通過key值獲取對應的value值
	 public String getString(String key, String defValue) {
         synchronized (this) {
             String v = (String)mMap.get(key);
             return v != null ? v : defValue;
         }
     }
	 ...
	 //獲得該SharedPreferencesImpl物件對應的Edito類,對資料進行操作
	 public final class EditorImpl implements Editor {
		 private final Map<String, Object> mModified = Maps.newHashMap(); //儲存了對鍵值變化的集合
	 }
}



       基本上獲取SharedPreferences 物件就是這麼來的,關於Context裡的更多方法請大家參照原始碼認真學習吧。