Android 複習筆記 —— 扒一扒 Context

Android 複習筆記目錄

  1. 嘮嘮任務棧,返回棧和生命週期
  2. 嘮嘮 Activity 的生命週期
  3. 嘮嘮 Context

本文永久更新地址: https://xiaozhuanlan.com/topic/0367548912

目錄

  • 什麼是 Context?
  • 四大組件和 Context
  • Application 和 Context
  • 爲什麼 Application 的 Context 不可以創建 Dialog ?
  • 未完待遇...

文章開頭,先來看一段代碼:

public class ContextActivity extends AppCompatActivity {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_context);
Log.e("context", "getApplication in Activity: " + getApplication().getClass().getName()); Log.e("context", "getApplicationContext in Activity: " + getApplicationContext().getClass().getName()); Log.e("context", "getBaseContext in Activity: " + getBaseContext().getClass().getName());
startService(new Intent(this,ContextService.class)); }}

你能準確的說出這三行打印語句的執行結果嗎?如果不能,你需要認真閱讀這篇文章。

什麼是 Context ?

Context 是一個抽象類。既然是抽象類,那麼它就代表了一類具體對象的通用特徵。先來看一下 Context 的類圖:

其中看到了我們很熟悉的 ActivityServiceApplication,這些都是 Context 的具體實現類,也就是說 Context 抽象了這些類的通用特徵和功能:

  • 獲取系統資源, getResources()getAssets()
  • 啓動各種系統組件
  • 獲取系統服務
  • ......

這些與系統環境息息相關的功能都是由 Context 提供的,所以一般將其稱爲 上下文,它其實就是對當前運行環境的具體描述,爲系統組件的正常運行提供必要的環境和資源。

在上面的類圖中,可能有兩個讀者比較陌生的類,ContextWraaperContextImpl

ContextImpl 很好理解,它就是 Context 的具體實現類。Context 類中的所有抽象方法都是在 ContextImpl 中實現的。

class ContextImpl extends Context {    ......
@Override public AssetManager getAssets() { return getResources().getAssets(); }
@Override public Resources getResources() { return mResources; }
@Override public PackageManager getPackageManager() { if (mPackageManager != null) { return mPackageManager; }
IPackageManager pm = ActivityThread.getPackageManager(); if (pm != null) { // Doesn't matter if we make more than one instance. return (mPackageManager = new ApplicationPackageManager(this, pm)); }
return null; }
@Override public ContentResolver getContentResolver() { return mContentResolver; }
@Override public Looper getMainLooper() { return mMainThread.getLooper(); }
......}

ContextWraaper 其實也很簡單,直接看它的實現代碼:

public class ContextWrapper extends Context {    @UnsupportedAppUsage    Context mBase;
public ContextWrapper(Context base) { mBase = base; } /** * 在這個方法中給 mBase 賦值 */ protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; }
public Context getBaseContext() { return mBase; }
@Override public AssetManager getAssets() { return mBase.getAssets(); }
@Override public Resources getResources() { return mBase.getResources(); }
......}

這是一個典型的 裝飾者模式,也叫做 修飾模式,以下來自維基百科:

修飾模式,是面向對象編程領域中,一種動態地往一個類中添加新的行爲的設計模式。就功能而言,修飾模式相比生成子類更爲靈活,這樣可以給某個對象而不是整個類添加一些功能。

通過使用修飾模式,可以在運行時擴充一個類的功能。原理是:增加一個修飾類包裹原來的類,包裹的方式一般是通過在將原來的對象作爲修飾類的構造函數的參數。裝飾類實現新的功能,但是,在不需要用到新功能的地方,它可以直接調用原來的類中的方法。修飾類必須和原來的類有相同的接口。

修飾模式是類繼承的另外一種選擇。類繼承在編譯時候增加行爲,而裝飾模式是在運行時增加行爲。

當有幾個相互獨立的功能需要擴充時,這個區別就變得很重要。在有些面向對象的編程語言中,類不能在運行時被創建,通常在設計的時候也不能預測到有哪幾種功能組合。這就意味着要爲每一種組合創建一個新類。相反,修飾模式是面向運行時候的對象實例的,這樣就可以在運行時根據需要進行組合。一個修飾模式的示例是JAVA裏的Java I/O Streams的實現。

Context 是基本的抽象類,無論是實現類,還是裝飾類,都直接或間接的實現它。ContextImpl 是 Context 的直接實現類,但各個組件並不是直接繼承 ContextImpl,而是通過裝飾類 ContextWrapper 來持有 ContextImpl。這是爲什麼呢?對於 Activity 和 Service 來說,它們都需要系統上下文運行環境,但它們又是不同的。Activity 需要顯示到前臺,它有頁面,它需要主題,於是有了繼承自 ContextWrapper 的 ContextThemeWrapper,擴展了功能,給 Activity 提供了主題。同時,Activity、Service、Application 這些具體組件本身又擴展出了不同的生命週期功能。

所以,裝飾器模式通過組合和擴展裝飾類,來給不同的具體對象提供了不同的功能擴展。

ActivityServiceApplication 最終都是繼承自裝飾類 ContextWrapperContextWrapper 通過 attachBaseContext() 方法來獲取實際做事的 ContextImpl 對象。所以這些組件的創建過程中,一定會在某一時機調用 attachBaseContext() 方法對 mBase 對象進行賦值,讓我們從源碼裏面找找答案。

四大組件和 Context

Activity 和 Context

先說 Activity,Activity 的啓動過程極其複雜,我們就直接從 ActivityThreadperformLaunchActivity() 方法看起。

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {        ActivityInfo aInfo = r.activityInfo;        if (r.packageInfo == null) {            // 1. 獲取 LoadedApk 對象            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,                    Context.CONTEXT_INCLUDE_CODE);        }
......
// 2. 創建 ContextImpl 對象 ContextImpl appContext = createBaseContextForActivity(r); Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); // 3. 反射創建 Activity 對象 activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); ...... } catch (Exception e) { ...... }
try { // 4. 創建 Application 對象 Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) { ...... appContext.setOuterContext(activity); // 5. 綁定 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);
...... int theme = r.activityInfo.getThemeResource(); if (theme != 0) { // 設置主題 activity.setTheme(theme); }
// 6. 回調 onCreate() if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } ...... r.activity = activity; } r.setState(ON_CREATE);
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) { throw e; } catch (Exception e) { ...... } return activity; }

整理一下大致的執行流程:

  1. 獲取 LoadedApk 對象,表示加載過的 Apk ,通常一個 App 對應着一個 LoadedApk
  2. 通過 createBaseContextForActivity() 方法創建 ContextImpl 對象
  3. 反射創建 Activity 對象
  4. 創建 Application 對象,這裏也是用的反射。如果開發者沒有聲明自己的 Application 的話,就是默認的 androoid.app.Application
  5. 調用 activity.attach() ,這個方法很重要,後面詳細說
  6. 回調 onCreate()

接着就是 Activity 正常的生命週期流程了。

重點看一下 createBaseContextForActivity() 方法和 attach() 方法。

    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {            ContextImpl appContext = ContextImpl.createActivityContext(                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);        ......        return appContext;    }

調用了 ContextImpl.createActivityContext() 方法。

   static ContextImpl createActivityContext(ActivityThread mainThread,            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,            Configuration overrideConfiguration) {        ......        // 創建 ContextImpl 對象        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,                activityToken, null, 0, classLoader);
......
final ResourcesManager resourcesManager = ResourcesManager.getInstance(); context.setResources(resourcesManager.createBaseActivityResources(activityToken, packageInfo.getResDir(), splitDirs, packageInfo.getOverlayDirs(), packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, classLoader)); context.mDisplay = resourcesManager.getAdjustedDisplay(displayId, context.getResources()); return context; }

裝飾類 ContextWrapper 真正需要的 ContextImpl 對象現在已經創建出來了,但是還沒有綁定到 Activity 。繼續看 Activity.attach() 方法,注意 attach() 方法的第一個參數就是剛剛創建出來的 ContextImpl 對象。

    final void attach(Context context, ActivityThread aThread,            Instrumentation instr, IBinder token, int ident,            Application application, Intent intent, ActivityInfo info,            CharSequence title, Activity parent, String id,            NonConfigurationInstances lastNonConfigurationInstances,            Configuration config, String referrer, IVoiceInteractor voiceInteractor,            Window window, ActivityConfigCallback activityConfigCallback) {        // 回調 attachBaseContext()        attachBaseContext(context);
......
// 創建 PhoneWindow mWindow = new PhoneWindow(this, window, activityConfigCallback);
......
mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); ...... }

你對 attachBaseContext() 方法應該還有印象。ContextWrapper 正是通過這個方法給 mBase 對象賦值,拿到真正的 ContextImpl 對象。到這裏,整個邏輯就通順了。

注意 attach() 方法中的 setWindowManager() 方法中的 mToken 參數,這決定了 Application Context 無法創建和顯示 Dialog 。後續會進行詳細分析。

再回頭看看文章開頭的問題。

Log.e("context", "getApplication in Activity: " + getApplication().getClass().getName());Log.e("context", "getApplicationContext in Activity: " + getApplicationContext().getClass().getName());Log.e("context", "getBaseContext in Activity: " + getBaseContext().getClass().getName());

第一個 getApplication() ,看下源碼就知道了:

public final Application getApplication() {    return mApplication;}

getApplication() 返回的是當前的 Application 對象。開發者沒有聲明自己實現的 Application 的話,就是系統默認的 android.app.Application

第二個 getApplicationContext(),它並不是 Activity 中的方法,而是 ContextWrapper 的。直接看源碼:

@Overridepublic Context getApplicationContext() {    return mBase.getApplicationContext();}

調用的是 ContextImpl.getApplicationContext()

@Overridepublic Context getApplicationContext() {    return (mPackageInfo != null) ?        mPackageInfo.getApplication() : mMainThread.getApplication();}

所以返回的同樣是 Application 對象。

第三個,getBaseContext() ,同樣是 ContextWrapper 中的方法:

public Context getBaseContext() {    return mBase;}

所以這裏返回的是 ContextImpl 對象。

最後的打印語句是:

E/context: getApplication in Activity: luyao.android.AppE/context: getApplicationContext in Activity: luyao.android.AppE/context: getBaseContext in Activity: android.app.ContextImpl

關於 Activity 就說這麼多了。下面來看看 Service 。

Service 和 Context

Service 其實和 Activity 的整體流程基本一致,創建服務的主要邏輯在 ActivityThread.handleCreateService() 方法中。這裏我就不貼源碼了,簡單敘述一下:

  1. 創建 LoadedApk 對象
  2. 反射創建 Service 對象
  3. 調用 ContextImpl.createAppCntext() 創建 ContextImpl 對象
  4. 創建 Application 對象
  5. 調用 service.attach() 進行綁定
  6. 回調 service 的 onCreate() 方法

直接看一下 Service.attach() 方法:

    public final void attach(            Context context,            ActivityThread thread, String className, IBinder token,            Application application, Object activityManager) {        attachBaseContext(context);        ......    }

又看到了熟悉的 attachBaseContext() 方法。

ActivityService 都是繼承自 ContextWrapper 的,最後都是通過 attachBaseContext() 對 ContextImpl 類型的 mBase 賦值。而 ContentProviderBroadcastReceiver 都沒有繼承 Context,所以它們獲取 Context 的方式會有一點不一樣。

ContentProvider 和 Context

先來看 ContentProvider,創建 Provider 的邏輯在 Activity.installProvider() 方法中:

    private ContentProviderHolder installProvider(Context context,            ContentProviderHolder holder, ProviderInfo info,            boolean noisy, boolean noReleaseNeeded, boolean stable) {        ContentProvider localProvider = null;        IContentProvider provider;     	    // 創建 LoadedApk 和 ContextImpl        c = context.createPackageContext(ai.packageName,Context.CONTEXT_INCLUDE_CODE);                       try {            ......			// 創建 ContentProvider            localProvider = packageInfo.getAppFactory()                    .instantiateProvider(cl, info.name);            provider = localProvider.getIContentProvider();            ......            // 綁定 Context            localProvider.attachInfo(c, info);        } catch (java.lang.Exception e) {            ......        }              ......        return retHolder;}

最後在 ContentProvider.attachInfo() 方法中進行了 ContextImpl 的賦值操作。

    private void attachInfo(Context context, ProviderInfo info, boolean testing) {        if (mContext == null) {            // 給 mContext 賦值            mContext = context;            ......            // 回調 onCreate()            ContentProvider.this.onCreate();        }    }

這樣 ContentProvider 也能拿到 Context 對象了。

BroadcastReceiver 和 Context

最後就是 BroadcastReceiver 了,對應 ActivityThread.handleReceiver() 方法:

    private void handleReceiver(ReceiverData data) {        ......
// 創建 LoadedApk 對象 LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo);
Application app; BroadcastReceiver receiver; ContextImpl context; try { // 創建 Application 對象 app = packageInfo.makeApplication(false, mInstrumentation); // 創建 ContextImpl 對象 context = (ContextImpl) app.getBaseContext(); ......
// 創建 BroadcastReceiver 對象 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 { sCurrentBroadcastIntent.set(null); }
...... }

大多數步驟和 Activity 還是類似的,只是到最後回調 onReceive() 方法的時候,纔會把 ContextImpl 對象傳過去。注意,這裏並不是直接返回原生的 ContextImpl 對象,而是調用 context.getReceiverRestrictedContext() 返回一個 受限制ReceiverRestrictedContext,你無法使用這個 Context 對象啓動 Service 。

這不正是 裝飾者模式 的體現?想給廣播的 Context 對象加點限制,那就再來一個裝飾類 ReceiverRestrictedContext ,它繼承了 ContextWrapper , 重寫部分方法以限制應用場景。通過增加和組合裝飾類,而不是增加子類,來實現功能擴展。

Application 和 Context

四大組件說完了,別忘了 Application 也是 Context 的間接子類。

Application 的創建時機得從應用進程的創建開始說起。Zygote 進程在接收到客戶端請求創建應用進程的 socket 請求之後,會 fork 出子進程,並反射調用 ActivityThread 的靜態 main() 方法。接着是 AMS 和客戶端的一系列 Binder 調用以及 Handler 通信,最終主線程在收到 BIND_APPLICATION 消息之後回調 handleBindApplication() 方法,到這裏就是我們需要的邏輯了:

private void handleBindApplication(AppBindData data){    ......
// 獲取 ContextImpl final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
......
// 創建 Application 對象 app = data.info.makeApplication(data.restrictedBackupMode, null);
......
// 調用 Application 的 onCreate() 方法 mInstrumentation.callApplicationOnCreate(app);}

你可能會疑惑怎麼沒有回調 attBaseContext() 方法,別急,看看 LoadedApk.makeApplication() 方法是如何創建 Application 的。

public Application makeApplication(boolean forceDefaultAppClass,            Instrumentation instrumentation) {    ......
// 創建 ContextImpl ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); // 反射創建 Application app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app);}

通過 Instrumentation.newApplication() 方法創建 Application 。

    public Application newApplication(ClassLoader cl, String className, Context context)            throws InstantiationException, IllegalAccessException,             ClassNotFoundException {        // 反射創建        Application app = getFactory(context.getPackageName())                .instantiateApplication(cl, className);        // 重點        app.attach(context);        return app;    }

重點就在 Application.attach() 方法。

final void attach(Context context) {    attachBaseContext(context);    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;}

在這裏調用了 attachBaseContext() 方法進行賦值,也驗證了 attachBaseContext() 的確比 onCreate() 先調用。

爲什麼 Application 的 Context 不可以創建 Dialog ?

使用 Application 的 Context 創建 Dialog 並顯示,會報如下錯誤:

Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?    at android.view.ViewRootImpl.setView(ViewRootImpl.java:951)    at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387)    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:96)    at android.app.Dialog.show(Dialog.java:344)

注意錯誤信息 token null is not valid ,還記得文章前面說到 Activity 和 Context 的時候,有這麼一段代碼:

mWindow.setWindowManager(                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),                mToken, mComponent.flattenToString(),                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

注意其中的 mToken 參數不爲 null ,是不是就說明了 Application 的 token 參數爲空呢?

本來準備接着說說這個問題,但可能造成文章篇幅過長,所以 Android 複習筆記 下一篇會單獨來嘮嘮這個問題。


Android 複習筆記 將會是一個付費專欄,由於需要一定的曝光度,前面幾(N)期文章會同步到掘金。

如果你覺得文章還不錯,那麼不妨到 小專欄 支持我,同時關注我的微信公衆號:秉心說TM


本文分享自微信公衆號 - 秉心說TM(gh_c6504b1af5ae)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章