前言
網上關於 Context 的文章也已經有不少了,比如值得參考的有:
Android Context完全解析,你所不知道的Context的各種細節
Android Context 到底是什麼?
但看了一下,發現還有值得討論的地方,比如這個等式:
Context個數 = Service 個數 + Activity 個數 + 1
老實說,我不明白這個等式有什麼意義,而且還是錯的。首先多程式情況下,Application 物件就不止一個;其次,Activity、Service、Application 繼承自 ContextWrapper,它們自己就是一個 Context,裡面又有一個 Base Context;最後,還有各種 outer context、display context 什麼的,這部分沒深入研究過,但 Context 的數量絕對大於上述等式的兩倍了。
上面這部分算一個討論,下面正式進入正題,本文目錄為:
- Context 家族
- Context 有什麼用?
- ContextImpl 、ContextWrapper、ContextThemeWrapper 有什麼區別?
- Activity Context、Service Context、Application Context、Base Context 有什麼區別?
- 為什麼不推薦使用 Base Context?
- 總結
Context 家族
Context 本身是一個抽象類,主要實現類為 ContextImpl,另外有子類 ContextWrapper 和 ContextThemeWrapper,這兩個子類都是 Context 的代理類,主要區別是 ContextThemeWrapper 有自己的主題資源。它們繼承關係如下:
Context 有什麼用?
如果要弄清楚 “某個類有什麼用” 這樣的問題,其實很簡單,看一下它提供了什麼介面就知道了,下面列舉一些主要的:
/**
* 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.
*/
public abstract class Context {
// 四大元件相關
public abstract void startActivity(@RequiresPermission Intent intent);
public abstract void sendBroadcast(@RequiresPermission Intent intent);
public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
IntentFilter filter);
public abstract void unregisterReceiver(BroadcastReceiver receiver);
public abstract ComponentName startService(Intent service);
public abstract boolean stopService(Intent service);
public abstract boolean bindService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags);
public abstract void unbindService(@NonNull ServiceConnection conn);
public abstract ContentResolver getContentResolver();
// 獲取系統/應用資源
public abstract AssetManager getAssets();
public abstract Resources getResources();
public abstract PackageManager getPackageManager();
public abstract Context getApplicationContext();
public abstract ClassLoader getClassLoader();
public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { ... }
public final String getString(@StringRes int resId) { ... }
public final int getColor(@ColorRes int id) { ... }
public final Drawable getDrawable(@DrawableRes int id) { ... }
public abstract Resources.Theme getTheme();
public abstract void setTheme(@StyleRes int resid);
public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { ... }
// 獲取應用相關資訊
public abstract ApplicationInfo getApplicationInfo();
public abstract String getPackageName();
public abstract Looper getMainLooper();
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
// 檔案相關
public abstract File getSharedPreferencesPath(String name);
public abstract File getDataDir();
public abstract boolean deleteFile(String name);
public abstract File getExternalFilesDir(@Nullable String type);
public abstract File getCacheDir();
...
public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
public abstract boolean deleteSharedPreferences(String name);
// 資料庫相關
public abstract SQLiteDatabase openOrCreateDatabase(...);
public abstract boolean deleteDatabase(String name);
public abstract File getDatabasePath(String name);
...
// 其它
public void registerComponentCallbacks(ComponentCallbacks callback) { ... }
public void unregisterComponentCallbacks(ComponentCallbacks callback) { ... }
...
}
public interface ComponentCallbacks {
void onConfigurationChanged(Configuration newConfig);
void onLowMemory();
}
複製程式碼
結合註釋,可以發現,Context 就相當於 Application 的大管家,主要負責:
- 四大元件的互動,包括啟動 Activity、Broadcast、Service,獲取 ContentResolver 等
- 獲取系統/應用資源,包括 AssetManager、PackageManager、Resources、System Service 以及 color、string、drawable 等
- 檔案,包括獲取快取資料夾、刪除檔案、SharedPreference 相關等
- 資料庫(SQLite)相關,包括開啟資料庫、刪除資料庫、獲取資料庫路徑等
- 其它輔助功能,比如設定 ComponentCallbacks,即監聽配置資訊改變、記憶體不足等事件的發生
ContextImpl 、ContextWrapper、ContextThemeWrapper 有什麼區別?
ContextWrapper
先看 ContextWrapper:
/**
* Proxying implementation of Context that simply delegates all of its calls to
* another Context. Can be subclassed to modify behavior without changing
* the original Context.
*/
public class ContextWrapper extends Context {
// 注意這個成員
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
// 這就是經常讓人產生疑惑的 Base Context 了
public Context getBaseContext() {
return mBase;
}
// 下面這些方法全都直接通過 mBase 完成
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources() {
return mBase.getResources();
}
@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
...
}
複製程式碼
可以看到,ContextWrapper 實際上就是 Context 的代理類而已,所有的操作都是通過內部成員 mBase 完成的,另外,Activity、Service 的 getBaseContext 返回的就是這個 mBase。
ContextThemeWrapper
接著看 ContextThemeWrapper,這個類的程式碼並不多,主要看 Resource 和 Theme 相關的:
/**
* A context wrapper that allows you to modify or replace the theme of the
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
private Configuration mOverrideConfiguration;
private Resources mResources;
public ContextThemeWrapper() {
super(null);
}
public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
super(base);
mThemeResource = themeResId;
}
public ContextThemeWrapper(Context base, Resources.Theme theme) {
super(base);
mTheme = theme;
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
}
// 在 Recource 初始化之前,傳入配置資訊
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
if (mResources != null) {
throw new IllegalStateException(...);
}
if (mOverrideConfiguration != null) {
throw new IllegalStateException(...);
}
mOverrideConfiguration = new Configuration(overrideConfiguration);
}
public Configuration getOverrideConfiguration() {
return mOverrideConfiguration;
}
// 沒有重寫 setResource,即 setResource 行為和父類一樣
@Override
public Resources getResources() {
return getResourcesInternal();
}
private Resources getResourcesInternal() {
if (mResources == null) {
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else {
// 根據配置資訊初始化 Resource
// 注意,這裡建立了另一個和 Base Context 不同的 Resource
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
@Override
public void setTheme(int resid) {
if (mThemeResource != resid) {
mThemeResource = resid;
initializeTheme();
}
}
private void initializeTheme() {
final boolean first = mTheme == null;
if (first) {
// 根據 Resource 獲取 Theme
mTheme = getResources().newTheme();
// 複製內容
final Resources.Theme theme = getBaseContext().getTheme();
if (theme != null) {
mTheme.setTo(theme);
}
}
onApplyThemeResource(mTheme, mThemeResource, first);
}
protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) {
theme.applyStyle(resId, true);
}
@Override
public Resources.Theme getTheme() {
// 只會初始化一次
if (mTheme != null) {
return mTheme;
}
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getApplicationInfo().targetSdkVersion);
initializeTheme();
return mTheme;
}
...
}
複製程式碼
結合註釋及原始碼,可以發現,相比 ContextWrapper,ContextThemeWrapper 有自己的另外 Resource 以及 Theme 成員,並且可以傳入配置資訊以初始化自己的 Resource 及 Theme。即 Resource 以及 Theme 相關的行為不再是直接呼叫 mBase 的方法了,也就說,ContextThemeWrapper 和它的 mBase 成員在 Resource 以及 Theme 相關的行為上是不同的。
ContextImpl
下面看一下 ContextImpl 有關 Theme 以及 Resource 的部分,以分析它和 ContextThemeWrapper 的區別:
/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {
private int mThemeResource = 0;
private Resources.Theme mTheme = null;
private @NonNull Resources mResources;
// 用於建立 Activity Context
static ContextImpl createActivityContext(...) {
ContextImpl context = new ContextImpl(...);
context.setResources(resourcesManager.createBaseActivityResources(...));
return context;
}
// 用於建立 Application Context、Service Context
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
ContextImpl context = new ContextImpl(...);
context.setResources(packageInfo.getResources());
return context;
}
private static Resources createResources(...) {
return ResourcesManager.getInstance().getResources(...);
}
// ContextThemeWrapper 沒有重寫父類的 setResources
// 因此會呼叫 mBase 的 setResources,即和 ContextImpl 的行為一樣
void setResources(Resources r) {
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
}
mResources = r;
}
@Override
public Resources getResources() {
return mResources;
}
/* ---------- 主題相關 ------------ */
@Override
public void setTheme(int resId) {
synchronized (mSync) {
if (mThemeResource != resId) {
mThemeResource = resId;
initializeTheme();
}
}
}
// 直接建立一個 Themem 物件,相比 ContextThemeWrapper,少了一部分內容
private void initializeTheme() {
if (mTheme == null) {
mTheme = mResources.newTheme();
}
mTheme.applyStyle(mThemeResource, true);
}
@Override
public Resources.Theme getTheme() {
synchronized (mSync) {
// 和 ContextThemeWrapper 基本一樣
if (mTheme != null) {
return mTheme;
}
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getOuterContext().getApplicationInfo().targetSdkVersion);
initializeTheme();
return mTheme;
}
}
}
複製程式碼
從程式碼中可以看出,ContextImpl 和 ContextThemeWrapper 最大的區別就是沒有一個 Configuration 而已,其它的行為大致是一樣的。另外,ContextImpl 可以用於建立 Activity、Service 以及 Application 的 mBase 成員,這個 Base Context 時除了引數不同,它們的 Resource 也不同。需要注意的是,createActivityContext 等方法中 setResource 是 mBase 自己呼叫的,Activity、Service 以及 Application 本身並沒有執行 setResource。
小結
-
ContextWrapper、ContextThemeWrapper 都是 Context 的代理類,二者的區別在於 ContextThemeWrapper 有自己的 Theme 以及 Resource,並且 Resource 可以傳入自己的配置初始化
-
ContextImpl 是 Context 的主要實現類,Activity、Service 和 Application 的 Base Context 都是由它建立的,即 ContextWrapper 代理的就是 ContextImpl 物件本身
-
ContextImpl 和 ContextThemeWrapper 的主要區別是, ContextThemeWrapper 有 Configuration 物件,Resource 可以根據這個物件來初始化
-
Service 和 Application 使用同一個 Recource,和 Activity 使用的 Resource 不同
Activity Context、Service Context、Application Context、Base Context 有什麼區別?
Activity Context
先看 Activity,Activity 在啟動時,最終會執行 ActivityThread 的 performLaunchActivitiy:
public final class ActivityThread {
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
// 這個 Context 將會作為 Activity 的 Base Context
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
ClassLoader cl = appContext.getClassLoader();
// 建立 Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
} catch (Exception e) {
...
}
try {
// 建立 Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
// 初始化 Activity,注意引數 appContext
activity.attach(appContext, ...);
...
}
} catch (...) {
...
}
return activity;
}
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
ContextImpl appContext = ContextImpl.createActivityContext(...);
...
}
}
複製程式碼
可以看到,Activity 的 Base Context 就是上面分析過的 ContextImpl 的 createActivityContext 建立的。
同時,Service 的 Base Context 的建立過程和 Application 一樣,呼叫的都是 ContextImpl 的 createAppContext,即 Service Context 和 Application Context 的 Resource 是相同的。因此這裡跳過 Service,下面看一下 Application Context。
Application Context
在上面 ActivityThread 的 performLaunchActivity 方法中,可以看到一個 makeApplication 的呼叫,它是 LoaedApk 的方法:
public final class LoadedApk {
private Application mApplication;
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
Application app = null;
try {
// 建立 Base Context
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// 建立 Application 並設定 Base Context
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
...
}
// Application 建立成功,賦值給 mApplication
mApplication = app;
...
return app;
}
// 獲取 mApplication
Application getApplication() {
return mApplication;
}
}
複製程式碼
看完上面的程式碼,其實已基本能猜出 Application 及其 Base Context 的建立過程了,但為了完整地瞭解整個過程,下面還是看一下 Instrumentation 的實現:
public class Instrumentation {
public Application newApplication(ClassLoader cl, String className, Context context) throws ... {
return newApplication(cl.loadClass(className), context);
}
static public Application newApplication(Class<?> clazz, Context context) throws ... {
// 反射建立 Application 物件
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
}
}
複製程式碼
public class Application extends ContextWrapper implements ComponentCallbacks2 {
/* package */ final void attach(Context context) {
// 呼叫父類的 attachBaseContext 以設定 mBase
attachBaseContext(context);
}
}
複製程式碼
可以看到,Instrumentation 是使用反射的方法建立 Application 物件,建立完畢後,會執行 Application 的 attach 方法設定 mBase 成員。
Application 及其 Base Context 的建立過程我們瞭解了,接下來看一下 getApplicationContext 的實現:
class ContextImpl extends Context {
ActivityThread mMainThread;
LoadedApk mPackageInfo;
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
}
複製程式碼
從程式碼中可以看出,getApplicationContext 的返回值可能有兩個:第一個是 LoadedApk 的 getApplication 方法,這個方法的返回值就是剛剛建立的 Application 物件;第二個是 ActivityThread 的 getApplication 方法:
public final class ActivityThread {
Application mInitialApplication;
public Application getApplication() {
return mInitialApplication;
}
public static ActivityThread systemMain() {
// 建立 ActivityThread
ActivityThread thread = new ActivityThread();
thread.attach(true);
return thread;
}
private void attach(boolean system) {
mSystemThread = system;
if (!system) {
...
} else {
try {
mInstrumentation = new Instrumentation();
// 注意引數 getSystemContext().mPackageInfo
ContextImpl context = ContextImpl.createAppContext(
this, getSystemContext().mPackageInfo);
// 建立 Application
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
mInitialApplication.onCreate();
} catch (Exception e) {
...
}
}
...
}
}
複製程式碼
ActivityThread 中的 mInitialApplication 是在 systemMain 方法執行時建立的,而這個方法又是 SystemServer 啟動時呼叫的,結合引數 getSystemContext().mPackageInfo,因此個人推測 mInitialApplication 對應的是系統的某個 apk,即系統級別的 Application,但具體是不是這樣,目前還沒有深入研究過,有興趣的可以自己研究。
小結
-
Activity、Service 和 Application 的 Base Context 都是由 ContextImpl 建立的,且建立的都是 ContextImpl 物件,即它們都是 ContextImpl 的代理類
-
getApplicationContext 返回的就是 Application 物件本身,一般情況下它對應的是應用本身的 Application 物件,但也可能是系統的某個 Application
為什麼不推薦使用 Base Context?
一般情況下,使用代理而不直接使用某個物件,目的可能有兩個:
- 定製自己的行為
- 不影響原物件
其中 Servcie 和 Application 的父類 ContextWrapper 完全沒有自定義的行為,而 Activity 的父類 ContextThemeWrapper 則自定義了 Resource 以及 Theme 的相關行為,因此,個人理解:
- 對於 Service 和 Application 而言,不推薦使用 Base Context,是擔心使用者修改了 Base Context 而導致錯誤的發生
- 對於 Activity 而言,除了擔心使用者的修改之外,Base Context 和 Activity 本身對於 Reource 以及 Theme 的相關行為是不同的(如果應用了 Configuration 的話),使用 Base Context 可能會出現無法預期的現象
對於 Activity 的 getResource 問題,我寫了一份程式碼來驗證:
public class MainActivity extends AppCompatActivity {
private Configuration mOverrideConfiguration;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i(TAG, "getResources: " + getResources() + ", getBaseContext().getResources():"
+ getBaseContext().getResources());
}
// 因為 Android 會在 onCreate 之前自動呼叫 getResource
// 因此需要在這裡執行 applyOverrideConfiguration
@Override
public Resources getResources() {
if (mOverrideConfiguration == null) {
mOverrideConfiguration = new Configuration();
applyOverrideConfiguration(mOverrideConfiguration);
}
return super.getResources();
}
}
複製程式碼
輸出(我用的是小米手機):
getResources: android.content.res.MiuiResources@3c660a7,
getBaseContext().getResources():android.content.res.MiuiResources@5143954
複製程式碼
可以看到,就像原始碼顯示的那樣,應用了 Configuration 之後,Activity 的 getResource 方法返回的和 getBaseContext().getResources() 方法返回的不是同一個物件
總結
Context 的繼承關係如下:
Context 相當於 Application 的大管家,主要負責:
- 四大元件的互動,包括啟動 Activity、Broadcast、Service,獲取 ContentResolver 等
- 獲取系統/應用資源,包括 AssetManager、PackageManager、Resources、System Service 以及 color、string、drawable 等
- 檔案,包括獲取快取資料夾、刪除檔案、SharedPreference 相關等
- 資料庫(SQLite)相關,包括開啟資料庫、刪除資料庫、獲取資料庫路徑等
- 其它輔助功能,比如設定 ComponentCallbacks,即監聽配置資訊改變、記憶體不足等事件的發生
ContextWrapper、ContextThemeWrapper、ContextImpl 的區別:
- ContextWrapper、ContextThemeWrapper 都是 Context 的代理類,二者的區別在於 ContextThemeWrapper 有自己的 Theme 以及 Resource,並且 Resource 可以傳入自己的配置初始化
- ContextImpl 是 Context 的主要實現類,Activity、Service 和 Application 的 Base Context 都是由它建立的,即 ContextWrapper 代理的就是 ContextImpl 物件本身
- ContextImpl 和 ContextThemeWrapper 的主要區別是, ContextThemeWrapper 有 Configuration 物件,Resource 可以根據這個物件來初始化
Activity Context、Service Context、Application Context、Base Context 的區別:
- Activity、Service 和 Application 的 Base Context 都是由 ContextImpl 建立的,且建立的都是 ContextImpl 物件,即它們都是 ContextImpl 的代理類
- Service 和 Application 使用同一個 Recource,和 Activity 使用的 Resource 不同
- getApplicationContext 返回的就是 Application 物件本身,一般情況下它對應的是應用本身的 Application 物件,但也可能是系統的某個 Application
為什麼不推薦使用 Base Context:
- 對於 Service 和 Application 而言,不推薦使用 Base Context,是擔心使用者修改了 Base Context 而導致錯誤的發生
- 對於 Activity 而言,除了擔心使用者的修改之外,Base Context 和 Activity 本身對於 Reource 以及 Theme 的相關行為是不同的(如果應用了 Configuration 的話),使用 Base Context 可能出現無法預期的現象