編輯:關於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.
Android 5.0重啟恢復Task功能分析
Android5.0新增了一個重啟後可恢復Task功能。在正常的Activity切換使用過程中AMS會將Task和對應截圖進行保存,重啟後會將Task和截圖恢復到最近任務
android popwindow仿微信右上角彈出框,dialog底部顯示
仿微信右上角彈出框1、利用popwindow實現2、popwindow的位置居於右上角新建,彈出popwindow:/** 彈popwindow **/ tv = (T
android studio 下如何配置JNI環境
版本:Android stuido 2.2 windows 10操作系統網上很多都是基於早期的eclipse環境配置的,studio的很少。以下是我親測可用的一個詳細過程
詳解SwipeListView框架實現微信\QQ滑動刪除效果
QQ或者微信出現過滑動,最近聯系人列表,可以刪去當前選中的聯系人,這個功能很棒。就是試著做了下。其實是使用了開源框架SwipeListView 。 SwipeL