編輯:關於Android編程
Context在開發Android應用的過程中扮演著非常重要的角色,比如啟動一個Activity需要使用context.startActivity方法,將一個xml文件轉換為一個View對象也需要使用Context對象,可以這麼說,離開了這個類,Android開發寸步難行,對於這樣一個類,我們又對他了解多少呢。我就說說我的感受吧,在剛開始學習Android開發時,感覺使用Context的地方一直就是傳入一個Activity對象,久而久之感覺只要是Context的地方就傳入一個Activity就行了,那麼我們現在就來詳細的分析一下Context和Activity的關系吧!
在開始本文之前我們先放置一個問題在這裡:
我們平時在獲取項目資源時使用context.getResources()的時候為什麼放回的是同一個值,明明是使用不同的Activity調用getResources返回結果卻是一樣的。
Context本身是一個純的abstract類,ContextWrapper是對Context的一個包裝而已,它的內部包含了一個Context對象,其實對ContextWrapper的方法調用最終都是調用其中的Context對象完成的,至於ContextThremeWrapper,很明顯和Theme有關,所以Activity從ContextThemmWrapper繼承,而Service從ContextWrapper繼承,ContextImpl是唯一一個真正實現了Context中方法的類。
從上面的繼承關系來看,每一個Activity就是一個Context,每一個Service就是一個Context,這也就是為什麼使用Context的地方可以被Activity或者Service替換了。
創建Context
根據前面所說,由於實現了Context的只有ContextImpl類,Activity和Service本沒有真正的實現,他們只是內部包含了一個真實的Context對象而已,也就是在在創建Activity或者Service的時候肯定要創建愛你一個ContextImpl對象,並賦值到Activity中的Context類型變量中。那我們就來看看Andorid源碼中有哪些地方創建了ContextImpl.
據統計Android中創建ContextImpl的地方一共有7處:
由於創建ContextImpl的基本原理類似,所以這裡只會分析幾個比較有代表性的地方:
1、 Application對應的Context
在應用程序啟動時,都會創建一個Application對象,所以輾轉調用到handleBindApplication()方法。
private final void handleBindApplication(AppBindData data) { mBoundApplication = data; mConfiguration = new Configuration(data.config); .... data.info = getPackageInfoNoCheck(data.appInfo); ... Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; .... }
其中data.info是LoadedApk類型的,到getPackageInfoNoCheck中看看源碼
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) { return getPackageInfo(ai, null, false, true); }
裡面其實調用的是getPackageInfo,繼續跟進:
if (includeCode) { ref = mPackages.get(aInfo.packageName); } else { ref = mResourcePackages.get(aInfo.packageName); } LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null ? mBoundApplication.processName : null) + ")"); packageInfo = new LoadedApk(this, aInfo, this, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0); if (includeCode) { mPackages.put(aInfo.packageName, new WeakReference<LoadedApk>(packageInfo)); } else { mResourcePackages.put(aInfo.packageName, new WeakReference<LoadedApk>(packageInfo)); }
由於includeCode傳入的是true,所以首先從mPackages中獲取,如果沒有,則new一個出來,並放入mPackages裡面去,注意,這裡的mPackages是ActivityThread中的屬性。
下面繼續分析一下LoadedApk這個類中的makeApplication函數
try { java.lang.ClassLoader cl = getClassLoader(); //創建一個ContextImpl對象 ContextImpl appContext = new ContextImpl(); appContext.init(this, null, mActivityThread); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { if (!mActivityThread.mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to instantiate application " + appClass + ": " + e.toString(), e); } }
這裡創建了一個ContextImpl對象,並調用了它的init方法,現在進入init方法。
mPackageInfo = packageInfo; mResources = mPackageInfo.getResources(mainThread);
對mPackageInof和mResources兩個變量初始化
回到makeApplication中,創建了一個Application對象,並將appContext傳進去,其實就是將appContext傳遞給ContextWrapper中的Context類型變量(Application也是繼承ContextWrapper)
2、Activity中的Context
在創建一個Activity時,經過輾轉調用,會執行handleLaunchActivity(),然後調用performLaunchActivity(),該方法創建ContextImpl代碼如下:
r.packageInfo= getPackageInfo(aInfo.applicationInfo, Context.CONTEXT_INCLUDE_CODE); ContextImplappContext = new ContextImpl(); appContext.init(r.packageInfo,r.token, this); appContext.setOuterContext(activity); activity.attach(appContext,this, getInstrumentation(), r.token, r.ident, app, r.intent,r.activityInfo, title, r.parent, r.embeddedID,r.lastNonConfigurationInstance, r.lastNonConfigurationChildInstances, config);
由於getPackageInfo函數之前已經分析過了,稍微有點區別,但是大致流程是差不多的,所以此處的appContext執行init之後,其中的mPackages變量和mResources變量時一樣的,activity通過attach函數將該appContext賦值到ContextWrapper中的Context類型變量。
3、Service中的Context
同樣 在創建一個Service時,經過輾轉調用會調用到scheduleCreateService方法,之後會巧用handleCreateService
LoadedApkpackageInfo = getPackageInfoNoCheck( data.info.applicationInfo); ContextImplcontext = new ContextImpl(); context.init(packageInfo, null,this); Application app =packageInfo.makeApplication(false, mInstrumentation); context.setOuterContext(service); service.attach(context, this,data.info.name, data.token, app, ActivityManagerNative.getDefault());
其思路和上面兩個基本一樣,在此就不再詳述。
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
final ArrayList<Application> mAllApplications = new ArrayList<Application>();
getApplicationContext返回的也是Application對象,只不過返回類型為Context,看看它的實現
@Override public Context getApplicationContext() { return (mPackageInfo != null) ? mPackageInfo.getApplication() : mMainThread.getApplication(); }
上面代碼中mPackageInfo是包含當前應用的包信息、比如包名、應用的安裝目錄等,原則上來說,作為第三方應用,包信息mPackageInfo不可能為空,在這種情況下,getApplicationContext返回的對象和getApplication是同一個。但是對於系統應用,包信息有可能為空,具體就不深入研究了。從這種角度來說,對於第三方應用,一個應用只存在一個Application對象,且通過getApplication和getApplicationContext得到的是同一個對象,兩者的區別僅僅是返回類型不同。
在此總結一下:
(1)Context是一個抽象類,ContextWrapper是對Context的封裝,它包含一個Context類型的變量,ContextWrapper的功能函數內部其實都是調用裡面的Context類型變量完成的。Application,Service,Activity等都是直接或者間接繼承自ContextWrapper,但是並沒有真正的實現其中的功能,Application,Service,Activity中關於Context的功能都是通過其內部的Context類型變量完成的,而這個變量的真實對象必定是ContextImpl,所以沒創建一個Application,Activity,Servcice便會創建一個ContextImpl,並且這些ContextImpl中的mPackages和mResources變量都是一樣的,所以不管使用Acitivty還是Service調用getResources得到相同的結果
(2)在一個apk中,Context的數量等於Activity個數+Service個數+1.
Android5.0新增了一個重啟後可恢復Task功能。在正常的Activity切換使用過程中AMS會將Task和對應截圖進行保存,重啟後會將Task和截圖恢復到最近任務
仿微信右上角彈出框1、利用popwindow實現2、popwindow的位置居於右上角新建,彈出popwindow:/** 彈popwindow **/ tv = (T
版本:Android stuido 2.2 windows 10操作系統網上很多都是基於早期的eclipse環境配置的,studio的很少。以下是我親測可用的一個詳細過程
QQ或者微信出現過滑動,最近聯系人列表,可以刪去當前選中的聯系人,這個功能很棒。就是試著做了下。其實是使用了開源框架SwipeListView 。 SwipeL