Context知識詳解

Context知識詳解

建議配合context知識架構圖食用。

一、什麼是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.

上面的意思:context是一個應用程序環境的全局信息的接口。這是一個抽象類,其實現由Android系統提供。它允許訪問特定於應用程序的資源和類,以及對應用程序級操作(如啓動活動,廣播和接收意圖等)的調用。

這個解釋可能聽起來比較抽象,我的理解是一些Android組件(如activity、service)的運行需要一定的“環境”,就好像我們工作一般都是在辦公室 ,休息則是在家裏,我們都是處在一定的“環境”下去工作、學習、休息的,Android組件也是類似,它們不能脫離“環境”去運轉,而這個“環境”在Android中就是context。

二、Context子類以及其繼承關係

先貼個圖

由圖我們可以看出context有兩個子類ContextImpl和ContextWrapper。

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;
    }
    
    */***
*     * Set the base context for this ContextWrapper.  All calls will then be*
*     * delegated to the base context.  Throws*
*     * IllegalStateException if a base context has already been set.*
*     * *
*     ****@param***base The new base context for this wrapper.*
*     */*
protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException(“Base context already set”);
        }
        mBase = base;
    }

    */***
*     ****@return***the base context as set by the constructor or setBaseContext*
*     */*
public Context getBaseContext() {
        return mBase;
    }

    @Override
    public AssetManager getAssets() {
        return mBase.getAssets();
    }

    @Override
    public Resources getResources() {
        return mBase.getResources();
    }

    @Override
    public PackageManager getPackageManager() {
        return mBase.getPackageManager();
    }

    @Override
    public ContentResolver getContentResolver() {
        return mBase.getContentResolver();
    }

    @Override
    public Looper getMainLooper() {
        return mBase.getMainLooper();
    }
    
    @Override
    public Context getApplicationContext() {
        return mBase.getApplicationContext();
    }
    
    @Override
    public void setTheme(int resid) {
        mBase.setTheme(resid);
    }

    */*****@hide****/*
@Override
    public int getThemeResId() {
        return mBase.getThemeResId();
    }

    @Override
    public Resources.Theme getTheme() {
        return mBase.getTheme();
    }
    @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);
    }
    @Override
    public void sendBroadcast(Intent intent) {
        mBase.sendBroadcast(intent);
    }
//...
}

該類直接繼承自Context,並實現了Context定義的抽象方法。不過我們看源碼發現其實它並未實質的去實現Context定義的操作只是通過mBase調用對應的方法去執行。這個mBase也是一個Context類型的變量,它的賦值是通過attachBaseContext賦值的。我們還知道service和application都是ContextWrapper子類,所以service和application都是Context。

public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
    //...
}

public class Application extends ContextWrapper implements ComponentCallbacks2 {
    //...
}

ContextWrapper還有一個子類ContextThemeWrapper。

public class ContextThemeWrapper extends ContextWrapper {
    private int mThemeResource;
    private Resources.Theme mTheme;
    private LayoutInflater mInflater;

    public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
        super(base);
        mThemeResource = themeResId;
    }

    public ContextThemeWrapper(Context base, Resources.Theme theme) {
        super(base);
        mTheme = theme;
    }

    @Override
    public Resources getResources() {
        return getResourcesInternal();
    }

    private Resources getResourcesInternal() {
        if (mResources == null) {
            if (mOverrideConfiguration == null) {
                mResources = super.getResources();
            } else if (Build.VERSION.SDK_INT >= 17) {
                final Context resContext = createConfigurationContext(mOverrideConfiguration);
                mResources = resContext.getResources();
            }
        }
        return mResources;
    }

    @Override
    public void setTheme(int resid) {
        if (mThemeResource != resid) {
            mThemeResource = resid;
            initializeTheme();
        }
    }

    public int getThemeResId() {
        return mThemeResource;
    }

    @Override
    public Resources.Theme getTheme() {
        if (mTheme != null) {
            return mTheme;
        }

        if (mThemeResource == 0) {
            mThemeResource = R.style.Theme_AppCompat_Light;
        }
        initializeTheme();

        return mTheme;
    }

    private void initializeTheme() {
        final boolean first = mTheme == null;
        if (first) {
            mTheme = getResources().newTheme();
            Resources.Theme theme = getBaseContext().getTheme();
            if (theme != null) {
                mTheme.setTo(theme);
            }
        }
        onApplyThemeResource(mTheme, mThemeResource, first);
    }
//...
}

可以看出ContextThemeWrapper主要是包含了主題Theme相關的接口,即android:theme屬性指定的。而activity則是繼承自ContextThemeWrapper。

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {
    //...
}

ContextImpl

由ContextWrapper源碼我們知道實際上它並沒有實現Context定義的相關操作。那麼Context的真實實現類到底是誰呢 答案就是ContextImpl。它是Android系統提供的唯一的Context真實 實現類。

class ContextImpl extends Context {

    @Override
    public void startActivity(Intent intent) {
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    }

    @Override
    public void sendBroadcast(Intent intent) {
        warnIfCallingFromSystemProcess();
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            intent.prepareToLeaveProcess(this);
            ActivityManager.getService().broadcastIntent(
                    mMainThread.getApplicationThread(), intent, resolvedType, null,
                    Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
                    getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        return registerReceiver(receiver, filter, null, null);
    }

    @Override
    public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, false, mUser);
    }
    
    //...

}

由源碼看出ContextImpl確是是真實的實現了Context。

三、一個應用Context個數

通過上面Context子類繼承關係的分析,一個應用Context個數顯而易見。
APP Context總數 = Application(1) + Activity個數+ Service個數;

四、不同的Context之間差異

我們知道Application的生命週期跟應用的生命週期是相同的,所以Application的Context生命週期與應用程序完全相同。同理
Activity或者Service的Context與他們各自類生命週期相同。

由此可知Context使用不當會引起內存泄漏,我們在使用Context時必須要注意其生命週期。

  • 儘量使用 Application 的 Context

  • 不要讓生命週期長於 Activity 的對象持有其的引用

  • 儘量不要在 Activity 中使用非靜態內部類,因爲非靜態內部類會隱式持有外部類示例的引用,如果使用靜態內部類,將外部實例引用作爲弱引用持有。

五、不同Context的應用場景

Context應用場景

大家注意看到有一些NO上添加了一些數字,其實這些從能力上來說是YES,但是爲什麼說是NO呢?下面一個一個解釋:
數字1:啓動Activity在這些類中是可以的,但是需要創建一個新的task。一般情況不推薦。
數字2:在這些類中去layout inflate是合法的,但是會使用系統默認的主題樣式,如果你自定義了某些樣式可能不會被使用。
數字3:在receiver爲null時允許,在4.2或以上的版本中,用於獲取黏性廣播的當前值。(可以無視)
注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因爲在其內部方法中都有一個context用於使用。

以上參考https://blog.csdn.net/lmj623565791/article/details/40481055

由表格我們可以歸納出這樣一個結論:操作涉及UI的應該使用Activity做爲Context,不涉及UI的Service,Activity,Application等實例都可以。

六、不同Context實例化過程

Activity 中Context實例化過程

在Activity的啓動過程中,activity的創建是在ActivityThread.
performLaunchActivity方法中完成的。

//ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r,Intent customIntent){
        //...
        ContextImpl appContext=createBaseContextForActivity(r);//1、創建ContextImpl實例
        Activity activity=null;
        try{
        java.lang.ClassLoader cl=appContext.getClassLoader();
        //...
        activity=mInstrumentation.newActivity(
        cl,component.getClassName(),r.intent);//2、創建Activity
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if(r.state!=null){
        r.state.setClassLoader(cl);
        }
        }catch(Exception e){
        if(!mInstrumentation.onException(activity,e)){
        throw new RuntimeException(
        "Unable to instantiate activity "+component
        +": "+e.toString(),e);
        }
        }

        try{
        Application app=r.packageInfo.makeApplication(false,mInstrumentation);
        if(activity!=null){
        appContext.setOuterContext(activity);//3、調用setOuterContext
        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);//4、調用attach
        }
       //...
    }

首先通過createBaseContextForActivity創建ContextImpl實例,那我們看下具體是如何創建的

private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
    final int displayId;
    try {
        displayId = ActivityManager.getService().getActivityDisplayId(r.token);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }

    ContextImpl appContext = ContextImpl.createActivityContext(
            this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
//...
    return appContext;
}

可以看出是調用createActivityContext,那來看下createActivityContext

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);

//...
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;
}

可以看到是調用了ContextImpl得一個構造函數創建的ContextImpl實例然後還給該實例設置了setResources,至此ContextImpl創建完成。但是我們注意到在創建了ContextImpl實例(appContext)之後又調用了setOuterContext
並把當前activity傳入,這又是爲什麼呢? 看下源碼

private Context mOuterContext;

final void setOuterContext(Context context) {
    mOuterContext = context;
}

setOuterContext只是簡單的把傳入的activity賦值給了mOuterContext,這是ContextImpl類中定義的一個變量。通過這個操作ContextImpl就可以持有activity的引用。
setOuterContext之後又調用了activity.attach並把appContext傳入。

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);
//...
}
//Activity.java
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(newBase);
    newBase.setAutofillClient(this);
}

Activity的attach我們只關注跟context有關的 那就是調用attachBaseContext,在這個函數內部調用了super.attachBaseContext。我們知道Activity繼承自 ContextThemeWrapper, ContextThemeWrapper
繼承自 ContextWrapper,所以最終會調用ContextWrapper.attachBaseContext,到這裏,ContextWrapper類就可以將它的功能交給ContextImpl類來具體實現。

//ContextWrapper.java
protected void attachBaseContext(Context base) {
    if (mBase != null) {
        throw new IllegalStateException("Base context already set");
    }
    mBase = base;
}

Service中Context實例化過程

private void handleCreateService(CreateServiceData data){
        //...
        Service service=null;
        try{
        java.lang.ClassLoader cl=packageInfo.getClassLoader();
        service=(Service)cl.loadClass(data.info.name).newInstance();//1、創建service
        }catch(Exception e){
        if(!mInstrumentation.onException(service,e)){
        throw new RuntimeException(
        "Unable to instantiate service "+data.info.name
        +": "+e.toString(),e);
        }
        }
        try{
        if(localLOGV)Slog.v(TAG,"Creating service "+data.info.name);

        ContextImpl context=ContextImpl.createAppContext(this,packageInfo);//2、創建ContextImpl實例
        context.setOuterContext(service);//3、設置OuterContext

        Application app=packageInfo.makeApplication(false,mInstrumentation);
        service.attach(context,this,data.info.name,data.token,app,
        ActivityManager.getService()); //4、調用attach
        service.onCreate();
        mServices.put(data.token,service);
        try{
        ActivityManager.getService().serviceDoneExecuting(
        data.token,SERVICE_DONE_EXECUTING_ANON,0,0);
        }catch(RemoteException e){
        throw e.rethrowFromSystemServer();
        }
        }catch(Exception e){
        if(!mInstrumentation.onException(service,e)){
        throw new RuntimeException(
        "Unable to create service "+data.info.name
        +": "+e.toString(),e);
        }
        }
     }

我們看到Service中Context實例的創建流程跟Activity基本是一樣的,首先創建Service實例然後創建ContextImpl實例,之後調用setOuterContext最後是attach。
Service中ContextImpl實例是通過函數createAppContext創建的,其內部則是通過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;
}

setOuterContext操作跟Activity是一樣的都是把引用賦值給mOuterContext。
最後就是attch了,下面是service的attach,可以看到它也是調用attachBaseContext,下面的流程跟Activity是一樣的最終都是ContextWrapper類將它的功能交給ContextImpl類來具體實現。

public final void attach(
        Context context,
        ActivityThread thread, String className, IBinder token,
        Application application, Object activityManager) {
    attachBaseContext(context);//調用attachBaseContext
    mThread = thread;           // NOTE:  unused - remove?
    mClassName = className;
    mToken = token;
    mApplication = application;
    mActivityManager = (IActivityManager)activityManager;
    mStartCompatibility = getApplicationInfo().targetSdkVersion
            < Build.VERSION_CODES.ECLAIR;
}

Application中的Context的實例化過程

Application 的創建是在LoadedApk.makeApplication中。

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

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 appContext = ContextImpl.createAppContext(mActivityThread, this);//1、創建ContextImpl實例
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);//2、創建application
            appContext.setOuterContext(app);//3、設置mOuterContext
        } catch (Exception e) {
            if (!mActivityThread.mInstrumentation.onException(app, e)) {
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                throw new RuntimeException(
                    "Unable to instantiate application " + appClass
                    + ": " + e.toString(), e);
            }
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;
//...
}

可以看到Application中是先創建了ContextImpl實例然後創建Application實例最後調用了setOuterContext。看上去跟Service和Activity相比缺少了attach,而我們知道attach是ContextWrapper類將它的功能交給ContextImpl類來具體實現的過程,Application缺少attach那它是如何實現ContextWrapper的代理過程的呢? 其實Application是有attach的 它在newApplication創建Application的過程中調用的。

public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    return newApplication(cl.loadClass(className), context);
}

 static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = (Application)clazz.newInstance();

        app.attach(context);//調用application的attach方法
        return app;
    }

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

嗯,這樣看application和Service還有Activity的流程基本上是一致的。

至此Application、Service、Activity中context的實例化過程都已分析完了。

七、其他

無侵入式獲取全局Context

使用一個ContentProvider,ContentProvider的onCreate()方法調用時,調用getContext()即可獲取到Context,再靜態變量保存,後續直接獲取即可。

public class AppContextProvider extends ContentProvider {
    static Context mContext;
    @Override
    public boolean onCreate() {
        //mContext保存爲靜態變量
        mContext = getContext();
        return false;
    }

    //...
}

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.app.contextprovider">

    <application>
        <!-- 全局Context提供者 -->
        <provider
                android:name=".AppContextProvider"
                android:authorities="${applicationId}.contextprovider"
                android:exported="false" />
    </application>
</manifest>

getApplication和getApplicationContext的區別

首先來看getApplication方法,它只有在Activity和Service中有實現

Activity
/** Return the application that owns this activity. */
public final Application getApplication() {
    return mApplication;
}

Service
/** Return the application that owns this service. */
public final Application getApplication() {
    return mApplication;
}

Activity和Service中getApplication返回的是一個application對象。

getApplicationContext是ContextWrapper提供的方法,由源碼可知它調用的是mBase的getApplicationContext()。此處的mBase實際是一個ContextImpl,所以我們看下ContextImpl的getApplicationContext(),可以看到返回的是mPackageInfo.getApplication()(此處的mPackageInfo包含當前應用的包信息、比如包名、應用的安裝目錄等信息,一般不爲空)。

//ContextWrapper
public Context getApplicationContext() {
    return mBase.getApplicationContext();
}

//ContextImpl
public Context getApplicationContext() {
    return (mPackageInfo != null) ?
            mPackageInfo.getApplication() : mMainThread.getApplication();
}

我們知道一個應用只有一個Application所以getApplication和getApplicationContext 實際上都是返回當前應用的Application,它們是同一個對象。這兩個函數的區別就是getApplication只能在Activity和Service中調用,而getApplicationContext 的使用範圍則要大一些,比如在廣播中想要獲取全局的Context則需要使用getApplicationContext 而不是getApplication。

以上就是Context相關知識點的整理解析。

本文所有源碼基於Android-8.0.0_r1

Android

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