前言
Android開發過程中,Context是繞不開的東西,因此本篇文章將一探究竟。
通過這篇文章,你將了解到:
1、Context衍生的子類
2、Context作用
3、四大組件里的Context
4、Context與Resources
5、不同Context關聯與使用
Context家族
Context是抽象類,來看看常見的子類
上圖展示了常見的Context子類的繼承關系。
我們平時接觸比較多的是Activity、Application、Service。
Context作用
根據上圖,我們主要講解Resource和四大組件相關的。
四大組件里的Context
Context 子類
Context里沒有特別需要留意的成員變量,其成員方法也多靠子類實現。
ContextWrapper
成員變量
遍觀ContextWrapper,只有個成員變量:
Context mBase;
成員方法
ContextWrapper重寫Context的方法,內部依靠mBase調用。
mBase實際上就是ContextImpl類型的,來看看它的內容。
ContextImpl
成員變量
//ContentResolver
private final ApplicationContentResolver mContentResolver;
//通過ResourcesManager管理Resource
private final @NonNull ResourcesManager mResourcesManager;
//Resource引用
private @NonNull Resources mResources;
//主題資源
private int mThemeResource = 0;
//主題
private Resources.Theme mTheme = null;
//緩存context.getSystemService 獲取的實例
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
//省略...
成員方法
ContextImpl成員方法是Context方法具體實現的地方。
Context/ContextWrapper/ContextImpl 三者關系
如上圖,ContextWrapper、ContextImpl繼承自Context,ContextWrapper作為ContextImpl 代理。
ContextWrapper和ContextImpl關聯
ContextWrapper和ContextImpl如何關聯以及在什么時機進行關聯的呢?
Application
Application是ContextWrapper子類,先來看看它是如何關聯的。以下部分涉及到Application/Activity啟動流程,有興趣的請移步:Android Activity創建到View的顯示過程
#LoadedApk.java
#makeApplication(...)
//創建ContextImpl 實例
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
//創建Application實例,并將mBase指向appContext
//Application.attach(...)->ContextWrapper.attachBaseContext(...)->mBase=appContext
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
//ContextImpl mOuterContext指向app
appContext.setOuterContext(app);
在創建Application的時候,會先構造ContextImpl對象,然后構造Application實例,并將Application里的mBase指向ContextImpl對象,最后將ContextImpl mOuterContext指向app。完成了Application和ContextImpl關聯,也即是ContextWrapper和ContextImpl的關聯。
Service
ContextWrapper還有另一個常見的子類:Service。來看看Service如何關聯ContextImpl的。
#ActivityThread.java
private void handleCreateService(CreateServiceData data) {
//獲取LoadedApk
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
//創建service
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
} catch (Exception e) {
}
try {
//創建ContextImpl
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
//contextImpl 持有該Service
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
//初始化Service一些成員變量,關聯ContextImpl
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
//service onCreate方法,一般會重寫該方法監聽service的創建
service.onCreate();
} catch (Exception e) {
}
}
接著看看service.attach(...)
public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {
//關聯ContextImpl
attachBaseContext(context);
mThread = thread; // NOTE: unused - remove?
mClassName = className;
mToken = token;
mApplication = application;
mActivityManager = (IActivityManager)activityManager;
mStartCompatibility = getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.ECLAIR;
}
以上,ContextImpl和Service關聯起來了。
Activity
ContextWrapper還有一個子類ContextThemeWrapper。
ContextThemeWrapper顧名思義,和主題相關的。
{
//主題資源id
private int mThemeResource;
//theme
private Resources.Theme mTheme;
private LayoutInflater mInflater;
private Configuration mOverrideConfiguration;
private Resources mResources;
}
而Activity繼承自ContextThemeWrapper,來看看Activity和ContextImpl如何關聯上的。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//省略...
//創建ContextImp
ContextImpl appContext = createBaseContextForActivity(r);
//創建Activity
Activity activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
//關聯ContextImpl和activity
appContext.setOuterContext(activity);
//初始化Activity成員變量
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
//省略
}
類似的,繼續看activity.attach(...)
最終也是調用到了ContextWrapper attachBaseContext(...),關聯mBase。
BroadcastReceiver
BroadcastReceiver 并沒有繼承自Context,但可以在onReceive(...)里拿到Context,那么這個Context是怎么來的呢?
Context作為參數傳進來的,那么就看看onReceive(...)的調用棧。
#ActivityThread.java
private void handleReceiver(ReceiverData data) {
//省略...
Application app;
BroadcastReceiver receiver;
ContextImpl context;
try {
app = packageInfo.makeApplication(false, mInstrumentation);
//用的是Application的mBase,也就是ContextImpl
context = (ContextImpl) app.getBaseContext();
//構造receiver
receiver = packageInfo.getAppFactory()
.instantiateReceiver(cl, data.info.name, data.intent);
} catch (Exception e) {}
try {
//調用onReceive
receiver.onReceive(context.getReceiverRestrictedContext(),
data.intent);
} catch (Exception e) {
} finally {
}
}
我們注意到context.getReceiverRestrictedContext():
#ContextImpl.java
final Context getReceiverRestrictedContext() {
//ReceiverRestrictedContext 是ContextWrapper的子類
if (mReceiverRestrictedContext != null) {
return mReceiverRestrictedContext;
}
//該Context是關聯Application的,也即是ContextImpl,因此getOuterContext()返回的是
//Application實例。最后ReceiverRestrictedContext的mBase指向Application實例
return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext());
}
因此我們得出結論是:
onReceive里的Context是ReceiverRestrictedContext類型,繼承自ContextWrapper,其mBase指向Application。
可以看出,mBase不一定是ContextImpl類型,但是最終都會調用到ContextImpl。
ContentProvider
ContentProvider沒有繼承自Context,但是其成員變量mContext是Context類型的,那么這個變量是怎么賦值的呢?
在構造ContextImpl時,會初始化ContentResolver
mContentResolver = new ApplicationContentResolver(this, mainThread);
這個this即是ContextImpl自身,傳進去賦值給了ContentResolver變量:
private final Context mContext;
當使用ContentResolver查詢ContentProvider并且創建ContentProvider的時候,這個mContext就賦值給ContentProvider的mContext。
上面分析了Application和四大組件與Context關系,用圖表示:
Context與Resources
之前列舉了Context的用處,最常用的莫過于通過Context獲取資源文件(Resources),具體情況是怎么樣的,接下來分析。
Context并沒有Resources類型的成員變量,ContextWrapper也沒有,ContextImpl有成員變量:
private @NonNull Resources mResources;
而Context里有獲取Resources的成員方法:
public abstract Resources getResources();
最終會調用ContextImpl,返回mResources。因此重點是ContextImpl的mResources如何賦值的。
上面提到過,Application/Activity/Service等關聯ContextImpl時,會新構造一個ContextImpl實例,在初始化的時候,會給mResources賦值。而Resources是通過ResourcesManager管理的,最終來看ResourcesManager如何管理Resources的。
ResourcesManager
Resources 和 ResourcesImpl
Resources里有個成員變量:
private ResourcesImpl mResourcesImpl;
顧名思義,Resources具體加載資源是通過ResourcesImpl實現的。
ResourcesImpl里成員變量:
final AssetManager mAssets;
AssetManager負責與底層通信(操作文件)。
ResourcesManager獲取Resources核心代碼:
Resources getOrCreateResources(@android.annotation.Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
//ResourcesKey 記錄著資源文件的路徑、Configuration等信息,最后用來生成AssetManager
synchronized (this) {
//activityToken 不為空,說明是Activity的Resource
if (activityToken != null) {
//從ResourcesImpl 的Map里尋找相應的緩存
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
} else {
//非Activity的Resource
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
}
//沒找到現成的,生成ResourcesImpl 實例
//同時創建AssetManager
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
//記錄到Map里
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
final Resources resources;
if (activityToken != null) {
//Activity Resource
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
//非Activity Resource
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
總結來說:
1、通過ResourcesKey去檢索之前是否創建過ResourcesImpl,如果沒有則創建新的對象,并記錄到Map里。
2、通過ArrayList遍歷尋找可以復用的Resources對象,判斷的依據是傳入的ResourcesImpl與已有的相等(其中的條件)。如果沒有可以復用的,則創建Resources對象,設置ResourcesImpl,并將Resources對象放入List等待下次復用。
3、可以看出ResourcesManager通過設置緩存來管理Resource。而ResourcesManager是單例,Context通過getResources(...)獲取Resource本質是通過ResourcesManager獲取的。
接下來看看Application/Service/Activity獲取的Resource是同一個Resource嗎?
ActivityThread為Application/Service創建ContextImpl時使用如下方法:
#ContextImpl.java
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
String opPackageName) {
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null, opPackageName);
//從packageInfo 獲取resources
context.setResources(packageInfo.getResources());
return context;
}
packageInfo.getResources():
#LoadedApk
public Resources getResources() {
//LoadedApk是全局的,因此它的mResources也只有一個
if (mResources == null) {
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
getClassLoader());
}
return mResources;
}
可以看出,通過createAppContext(xx)創建的ContextImpl共用同一個Resources對象。
而ActivityThread為Activity創建ContextImpl時使用的是:
#ContextImpl.java
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader, null);
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
//通過resourcesManager 獲取
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
return context;
}
可以看出,直接使用了ResourcesManager獲取Resource,最終不同的Activity獲取的Resource對象不同,但是共用同一個ResourcesImpl對象。
不同Context關聯與使用
這么多的Context,什么時候該使用哪種Context呢?以下列舉幾個易混的地方。
啟動Activity
public void startActivity(Intent intent, Bundle options) {
final int targetSdkVersion = getApplicationInfo().targetSdkVersion;
//targetSdkVersion 小于7.0 或者大于9.0時
//通過ContextImpl啟動Activity,如果沒有加入FLAG_ACTIVITY_NEW_TASK,則拋出異常
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& (targetSdkVersion < Build.VERSION_CODES.N
|| targetSdkVersion >= Build.VERSION_CODES.P)
&& (options == null
|| ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
TargetSdkVersion相關請查看:targetSdkVersion、compileSdkVersion、minSdkVersion作用與區別
非得要用非Activity的Context啟動Activity,只需要加入FLAG_ACTIVITY_NEW_TASK標記即可。建議持有當前棧頂Activity對象引用,通過Activity啟動另一個Activity。
啟動Dialog
非Activity Context啟動Dialog會失敗。原因是:
Activity帶有IBinder appToken,Window關聯WindowManager時會將appToken賦予Window的IBinder mAppToken,在添加Window到WindowManagerService過程中,會將mAppToken賦值給WindowManager.LayoutParams IBinder token。WindowManagerService檢查添加窗口的合法性,發現如果要添加的窗口類型是Dialog,但是有沒有Token,則拋出異常。
Activity啟動Dialog時,Dialog獲取的WindowManager即是Activity的WindowManager,其parentWindow是Activity的Window,而Activity的Window是有token的,最后該token賦值給LayoutParams。
Activity Window的關系有興趣請查看:Android Activity創建到View的顯示過程
因此啟動Dialog需要Activity作為Context傳進去。
View的Context
View的Context mContext變量是在創建View對象時賦值的。我們知道創建View對象有兩種方式:
1、動態創建new View(Context),此時決定于傳入的Context。
2、xml布局,最終是通過LayoutInflater加載的。LayoutInflater from(Context context),該context最終傳給View。
View的Context并沒有明確限制需要使用什么類型。但是如果沒有使用Activity作為Context的話,就無法使用Activity Theme的特性。
主題相關請移步:
全網最深入 Android Style/Theme/Attr/Styleable/TypedArray 清清楚楚明明白白
自從看了這篇文章,媽媽再也不擔心我不會Android 事件分發了
Android 輸入事件一擼到底之View接盤俠(3)