什麼是Context
Context是一個抽象基類,我們通過它訪問當前包的資源(getResources、getAssets)和啓動其他組件(Activity、Service、Broadcast)以及得到各種服務(getSystemService),當然,通過Context能得到的不僅僅只有上述這些內容。對Context的理解可以來說:Context提供了一個應用的運行環境,在Context的大環境裏,應用纔可以訪問資源,才能完成和其他組件、服務的交互,Context定義了一套基本的功能接口,我們可以理解爲一套規範,而Activity和Service是實現這套規範的子類,這麼說也許並不準確,因爲這套規範實際是被ContextImpl類統一實現的,Activity和Service只是繼承並有選擇性地重寫了某些規範的實現。
Context相關類的繼承關係
通過對比可以清晰地發現,Service和Application的類繼承關係比較像,而Activity還多了一層繼承ContextThemeWrapper,這是因爲Activity有主題的概念,而Service是沒有界面的服務,Application更是一個抽象的東西,它也是通過Activity類呈現的。
相關類介紹:
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;
}
}
Context對資源的訪問
很明確,不同的Context得到的都是同一份資源。這是很好理解的,請看下面的分析:得到資源的方式爲context.getResources,而真正的實現位於ContextImpl中的getResources方法,在ContextImpl中有一個成員 private Resources mResources,它就是getResources方法返回的結果,mResources的賦值代碼爲:mResources =mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
下面看一下ResourcesManager的getTopLevelResources方法,這個方法的思想是這樣的:在ResourcesManager中,所有的資源對象都被存儲在ArrayMap中,首先根據當前的請求參數去查找資源,如果找到了就返回,否則就創建一個資源對象放到ArrayMap中。有一點需要說明的是爲什麼會有多個資源對象,原因很簡單,因爲res下可能存在多個適配不同設備、不同分辨率、不同系統版本的目錄,按照android系統的設計,不同設備在訪問同一個應用的時候訪問的資源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。
public Resources getTopLevelResources(String resDir, int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
final float scale = compatInfo.applicationScale;
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
token);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
if (false) {
Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
}
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
//if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
if (false) {
Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ ": appScale=" + r.getCompatibilityInfo().applicationScale);
}
return r;
}
}
//if (r != null) {
// Slog.w(TAG, "Throwing away out-of-date resources!!!! "
// + r + " " + resDir);
//}
AssetManager assets = new AssetManager();
if (assets.addAssetPath(resDir) == 0) {
return null;
}
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
DisplayMetrics dm = getDisplayMetricsLocked(displayId);
Configuration config;
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
final boolean hasOverrideConfig = key.hasOverrideConfiguration();
if (!isDefaultDisplay || hasOverrideConfig) {
config = new Configuration(getConfiguration());
if (!isDefaultDisplay) {
applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
}
if (hasOverrideConfig) {
config.updateFrom(key.mOverrideConfiguration);
}
} else {
config = getConfiguration();
}
r = new Resources(assets, dm, config, compatInfo, token);
if (false) {
Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ r.getConfiguration() + " appScale="
+ r.getCompatibilityInfo().applicationScale);
}
synchronized (this) {
WeakReference<Resources> wr = mActiveResources.get(key);
Resources existing = wr != null ? wr.get() : null;
if (existing != null && existing.getAssets().isUpToDate()) {
// Someone else already created the resources while we were
// unlocked; go ahead and use theirs.
r.getAssets().close();
return existing;
}
// XXX need to remove entries when weak references go away
mActiveResources.put(key, new WeakReference<Resources>(r));
return r;
}
}
根據上述代碼中資源的請求機制,再加上ResourcesManager採用單例模式,這樣就保證了不同的ContextImpl訪問的是同一套資源,注意,這裏說的同一套資源未必是同一個資源,因爲資源可能位於不同的目錄,但它一定是我們的應用的資源,或許這樣來描述更準確,在設備參數和顯示參數不變的情況下,不同的ContextImpl訪問到的是同一份資源。設備參數不變是指手機的屏幕和android版本不變,顯示參數不變是指手機的分辨率和橫豎屏狀態。也就是說,儘管Application、Activity、Service都有自己的ContextImpl,並且每個ContextImpl都有自己的mResources成員,但是由於它們的mResources成員都來自於唯一的ResourcesManager實例,所以它們看似不同的mResources其實都指向的是同一塊內存(C語言的概念),因此,它們的mResources都是同一個對象(在設備參數和顯示參數不變的情況下)。在橫豎屏切換的情況下且應用中爲橫豎屏狀態提供了不同的資源,處在橫屏狀態下的ContextImpl和處在豎屏狀態下的ContextImpl訪問的資源不是同一個資源對象。
代碼:單例模式的ResourcesManager類
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
if (sResourcesManager == null) {
sResourcesManager = new ResourcesManager();
}
return sResourcesManager;
}
}
getApplication和getApplicationContext的區別
getApplication返回結果爲Application,且不同的Activity和Service返回的Application均爲同一個全局對象,在ActivityThread內部有一個列表專門用於維護所有應用的application:
finalArrayList<Application> mAllApplications = newArrayList<Application>();
getApplicationContext返回的也是Application對象,只不過返回類型爲Context,看看它的實現
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
上面代碼中mPackageInfo是包含當前應用的包信息、比如包名、應用的安裝目錄等,原則上來說,作爲第三方應用,包信息mPackageInfo不可能爲空,在這種情況下,getApplicationContext返回的對象和getApplication是同一個。但是對於系統應用,包信息有可能爲空,具體就不深入研究了。從這種角度來說,對於第三方應用,一個應用只存在一個Application對象,且通過getApplication和getApplicationContext得到的是同一個對象,兩者的區別僅僅是返回類型不同。
具體創建Context的時機
Context的真正實現都在ContextImpl中,也就是說Context的大部分方法調用都會轉到ContextImpl中,而三者的創建均在ActivityThread中完成
應用程序創建Context實例的情況有如下幾種情況:
1.創建Application對象時, 而且整個App共一個Application對象
2.創建Service對象時
3.創建Activity對象時
因此應用程序App共有的Context數目公式爲:
總Context實例個數 = Service個數 + Activity個數 + 1(Application對應的Context實例)
創建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實例
}
...
}
創建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實例
...
}
...
}
創建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對象。
整理自:
http://www.cnblogs.com/android100/p/Android-Context.html
http://blog.csdn.net/qinjuning/article/details/7310620