Android上下文Context的那些小事

前言

Context做爲Android中的上下文對象,是Android經常使用的類。啓動四大組件、建立視圖、獲取系統服務、訪問資源等都要用到Context。java

從Context有上下文的意思,結合Context的職能。能夠看出,Context在Android中提供了一個「語境」的意義,它提供了應用程序環境的全局信息。在這個「語境」中能夠經過Context使用相應的接口,作符合當前「語境」意義的事。好比,在Activity中建立Dialog、彈出Toast。android

這篇文章將繼續深刻了解Context。app

理解Context

Context關聯類

上面說到,Context意爲上下文,提供了應用程序環境信息。ide

Context做爲一個抽象類,它的實現有Android系統提供。咱們熟知的有ContextImpl、ContextWrapper、Application、Activity、Service等。佈局

從Context的類圖中能夠看出,ContextImpl和ContextWrapper都是繼承自Context,在ContextWrapper中依賴了ContextImpl對象(mBase)。這裏使用了裝飾者模式,ContextWrapper是裝飾類,對ContextImpl進行包裝,經過使用ContextImpl實現功能。學習

Application、Service、ContextThemeWrapper繼承自ContextWrapper,它們也是經過ContextImpl實現功能,同時在ContextWrapper的基礎上添加了本身的功能。此外ContextThemeWrapper中包含了主題相關的方法,Activity繼承自ContextThemeWrapper。ui

Context的類型

在Context的關聯類中能夠看到,Context類有不一樣的實現,有Application、Activity、Service三種。除此以外,還有BroadcastReceiver以及ContentProvider。這兩個組件不是Context的子類,可是對於BroadcastReceiver來講,在onReceive()時會接收一個Context。靜態註冊方式下的BroadReceiver在建立時傳遞的是ReceiverRestrictedContext。this

  • Application-是運行在應用程序中的單例,每一個應用進程中只存在一個Application實例。能夠經過getApplication()方法在Activity或者Service中獲取Application實例,或者經過getApplicationContext()方法在其餘繼承了Context的類中獲取實例。
  • Activity-繼承自ContextThemeWrapper,在ContextWrapper的基礎上有增長了對主題相關的支持。
  • Service-繼承自ContextWrapper,除了主題以外與Activity有相同的API。
  • BroadcastReceiver-自己不是一個Context,但在onReceive()方法中會接收一個Context。此外,不一樣的註冊方式Context不同。靜態註冊方式時接收的Context是ReceiverRestrictedContext。這個Context不支持registerReceiver()以及bindService()兩個方法。動態註冊方式的Context來自ContextImpl的mOutContext,這個取決於BroadcastReceiver的註冊者,多是Activity或者Application。
  • ContentProvider-自己不是Context,但在建立時能夠經過getContext()訪問。若是ContentProvider在相同應用程序進程運行,而後這將實際返回相同的Context。可是,若是二者(Content和Provider)都在單獨的進程中,那麼getContext()返回各自新建立的Context。

Context的建立過程

在Android程序中ActivityThread是應用進程的主線程管理類,Android四大組件最終都是從這裏開始建立。一下就分析一下各個Context的建立過程。spa

Application Context的建立過程

在ActivityThread的performLaunchActivity()方法中,建立Application。線程

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
	try {
		//建立Application
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    }
}
複製代碼

makeApplication是類LoadedApk的成員方法。

/**LoadedApk.java*/
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }
        Application app = null;

        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            java.lang.ClassLoader cl = getClassLoader();
            if (!mPackageName.equals("android")) {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        "initializeJavaContextClassLoader");
                initializeJavaContextClassLoader();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            }
            //建立ContextImpl對象
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            //建立Application對象
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            //設置ContextImpl的mOuterContext成員變量
            appContext.setOuterContext(app);
        } catch (Exception e) {
            //......
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app; //賦值
		//..........
        return app;
    }

/**ContextImpl.java*/
//建立ContextImpl對象
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo){
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null);
        context.setResources(packageInfo.getResources());
        return context;
    }
複製代碼

在makeApplication()方法中建立了Application對象,上面說到過Context都是依賴ContextImpl實現功能,在這裏也建立了ContextImpl對象,將Application保存到了ContextImpl的成員變量mOuterContext,同時Application將mBase指向了ContextImpl對象。

//mBase綁定ContextImpl對象的過程
/**Application.java*/
final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }
/**ContextWrapper.java*/
protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
//獲取ContextImpl對象
public Context getBaseContext() {
    return mBase;
}
複製代碼

Activity Context的建立過程

在ActivityThread的performLaunchActivity()方法中,建立Activity。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
	//建立ContextImpl對象
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
		//建立Activity
        java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
    }
    	//......
    	if (activity != null) {
                //......
                //設置ContextImpl的mOuterContext成員變量
                appContext.setOuterContext(activity);
            	//綁定ContextImpl
                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);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }
                //......
            }
    
     //......
}
//建立ContextImpl對象
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        //......
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig); //ContextImpl.createActivityContext()方法執行了建立過程
    	//......
        return appContext;
    }
複製代碼

能夠看到在performLaunchActivity()方法中建立了Activity對象,同時也建立了用於Activity的ContextImpl對象。而且執行了Context的綁定過程。

/**Activity.java*/
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(context);
            }
//綁定ContextImpl對象
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(newBase);
    newBase.setAutofillClient(this);
}
//獲取ContextImpl對象
public Context getBaseContext() {
    return mBase;
}
複製代碼

Service Context的建立過程

在ActivityThread中的handleCreateService()方法中建立Service。

private void handleCreateService(CreateServiceData data) {
	try {
        //建立ContextImpl對象
		ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        context.setOuterContext(service); //設置ContextImpl的mOuterContext成員變量

        Application app = packageInfo.makeApplication(false, mInstrumentation);
        //綁定ContextImpl對象
        service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
        service.onCreate();
        mServices.put(data.token, service);
        //......
    }
    //......
}
複製代碼
/**Service.java*/
public final void attach( Context context, ActivityThread thread, String className, IBinder token, Application application, Object activityManager) {
        attachBaseContext(context); //綁定ContextImpl到mBase
        mThread = thread;           // NOTE: unused - remove?
        mClassName = className;
        mToken = token;
        mApplication = application;
        mActivityManager = (IActivityManager)activityManager;
        mStartCompatibility = getApplicationInfo().targetSdkVersion
                < Build.VERSION_CODES.ECLAIR;
    }

/**ContextWrapper.java*/
protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
//獲取ContextImpl對象
public Context getBaseContext() {
        return mBase;
    }
複製代碼

能夠看到在handleCreateService()方法中建立了Service對象,同時也建立了用於Service的ContextImpl對象。而且執行了Context的綁定過程。


以上講到了Application、Activity、Service中Context的建立過程,能夠看到Context的建立過程正好映照了Context的類圖之間的關聯。經過ContextImpl統一實現Context的功能。

Context的功能

Application Activity Service BroadcastReceiver ContentProvider
顯示對話框 × × × ×
啓動Activity - - - -
建立佈局 × × - ×
啓動Service
綁定Service -
發送廣播 -
註冊廣播 -
獲取資源

Tips:(√:表示能夠執行;×:表示不能夠執行;-:表示視狀況而定)

  1. 從表中能夠看出任何Context都是能夠獲取到資源數據的,這裏的資源就是res目錄下的資源,好比:字符串、顏色、尺寸等。

  2. 對於跟UI有關的,只有Activity的Context能夠執行,好比:建立Dialog或者使用LayoutInflater建立佈局。由於UI相關的須要使用主題(Theme),而只有Activity繼承的ContextThemeWrapper擁有主題相關的方法。

  3. 對於啓動Activity來講,除了Activity Context之外。其餘的Context啓動Activity時須要加入FLAG_ACTIVITY_NEW_TASK標誌。由於在啓動Activity須要獲取到當前的棧信息。而其餘的Context沒有棧信息,因此這裏加入FLAG_ACTIVITY_NEW_TASK,起到的做用是從新建立一個Activity棧。

  4. BroadcastReceiver分爲靜態註冊和動態註冊,這兩種方式建立BroadCastReceiver實例的過程不同。

    • 其中,靜態註冊的廣播使用的是ReceiverRestrictedContext,這種Context不容許執行bindService(),會直接拋出ReceiverCallNotAllowedException異常。同時也對registerReceiver()有限制,當receiver == null用於獲取sticky廣播, 容許使用。不然也會拋出ReceiverCallNotAllowedException異常。
    • 動態註冊的廣播在onReceive()方法中接收到的context是來自ContextImpl的mOuterContext,因此取決於啓動它的Context。

總結

經過上面對Contex的相關知識的學習,對Context的建立以及相關類的關聯和使用有了更加清晰的瞭解。在開發中用到的時候可以使用準確。

相關文章
相關標籤/搜索