context上下文环境

106 阅读9分钟

官方定义

官方文档中,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是一个抽象类;
  • 允许通过Context获取各种资源、服务,或去启动一个Activity,发送一个广播,等等;

其实Context就是给Android应用程序提供了一个可实现各种操作的土壤环境,Context为Android提供了各种资源、功能、服务。若说编写一个Android程序像搭建一座房子,那Context就为Android提供了土地、木材和染料(启动一个Activity,弹出一个Dialog),且能提供呼叫各种将房屋建得更完善的其他帮助(发送一个广播,启动一个服务等)。

继承关系

Context是抽象类,来看看常见的子类

Context直接子类为ContextIml(实现类)和ContextWrapper(包装类)

再看看ContextWrapper的子类有什么,看到熟悉的Service和Application了吧,不过看到这里你一定有个疑问,为何Activity和他们哥俩不在一个继承层级呢?而是Activity又继承了ContextThemeWrapper,那么ContextWrapper和ContextThemeWrapper的区别在哪里呢?

看到这两个类的名字,相信你心里已经有了答案,对,区别在Theme

该类内部包含了主题(Theme)相关的接口,即android:theme属性指定的。

只有Activity需要主题,Service不需要主题,

所以Service直接继承于ContextWrapper类。而Activity因为含有Theme属性的缘故,所以继承自ContextThemeWrapper。

**所以说,Context所调用的资源是不同的了?**保留这个疑问,继续向下看。

作用

Context几乎包含了所有能想到的,一个Android程序需要的资源和操作,Context就像一个App一样,启动Activity、Service,发送Broadcast,拿到assets下的资源,获取SharedPreferences等等。

Android中的Context是一个抽象类,它是Android应用程序与系统之间的桥梁。Context类提供了许多方法,可以访问应用程序的资源,如应用程序的资源文件、SQLite数据库、SharedPreferences、应用程序的类和系统服务等。

在Android中,每个Activity、Service、BroadcastReceiver、ContentProvider都是Context的子类,它们都可以使用Context提供的方法来访问应用程序的资源。例如,通过调用getResources()方法可以访问应用程序的资源文件,通过调用getSystemService()方法可以访问系统服务。

此外,Context还具有一些其他的作用,例如可以创建新的Activity、启动Service、发送广播、获取应用程序的包名等。因此,在Android开发中,Context是非常重要的一个类,是许多操作的基础。

四大组件里的Context

ContextWrapper

成员变量

只有个成员变量:

Context mBase;//该属性指向一个ContextIml实例

成员方法

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关联

思路越来越清晰,现在就是要去寻找,Activity,Service,Application是何时与ContextImpl完成绑定关联的。

ActivityThreadmain方法,是整个Android程序的入口,所以去探究ActivityThread类,也是一件非常重要的事。ActivityThread的一个内部类H,里面定义了activity、service等启动、销毁等事件的响应,也就是说activity、service的启动、销毁都是在ActivityThread中进行的。

Application

Application是ContextWrapper子类

 #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());
    }

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的理解更进一步了,现在知道了Context是什么,它为Android提供了怎样的资源、功能、和服务,又在什么时候将Application、Activity、Service与ContextImpl相关联,但是所请求的资源是不是同一套资源呢?

在这里你一定说:“当然不是,不同的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的。

作者:小鱼人爱编程

链接:www.jianshu.com/p/bd412e96d…

来源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

来吧,看看不同Context对象的区别和用法的不同,参见以下表格。

这张表格是不是又支持了你的观点(也就是一直认为的,Context资源对象是不同的),但是还是要再次强调一次,它们所请求的,确确实实是同一块资源,看看上面进行关联的源码,都走进了Context实现类的init方法,拨云见日,去看看init方法吧。

查看ContextImpl类源码可看到,getResources方法直接返回内部的mResources变量

final void init(LoadedApk packageInfo,  
            IBinder activityToken, ActivityThread mainThread,  
            Resources container) {  
    mPackageInfo = packageInfo;  
    mResources = mPackageInfo.getResources(mainThread);  
  
    if (mResources != null && container != null  
            && container.getCompatibilityInfo().applicationScale !=  
                    mResources.getCompatibilityInfo().applicationScale) {  
        if (DEBUG) {  
            Log.d(TAG, "loaded context has different scaling. Using container's" +  
                    " compatiblity info:" + container.getDisplayMetrics());  
        }  
        mResources = mainThread.getTopLevelResources(  
                mPackageInfo.getResDir(), container.getCompatibilityInfo().copy());  
    }  
    mMainThread = mainThread;  
    mContentResolver = new ApplicationContentResolver(this, mainThread);  
  
    setActivityToken(activityToken);  
} 

mResources又是调用LoadedApkgetResources方法进行赋值。代码如下。

public Resources getResources(ActivityThread mainThread) {  
    if (mResources == null) {  
        mResources = mainThread.getTopLevelResources(mResDir, this);  
    }  
    return mResources;  
} 

从代码中可看到,最终mResources的赋值是由AcitivtyThreadgetTopLevelResources方法返回。代码如下。

Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {  
    ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);  
    Resources r;  
    synchronized (mPackages) {  
        // Resources is app scale dependent.  
        if (false) {  
            Slog.w(TAG, "getTopLevelResources: " + resDir + " / "  
                    + compInfo.applicationScale);  
        }  
        WeakReference<Resources> wr = mActiveResources.get(key);  
        r = wr != null ? wr.get() : null;  
          
        if (r != null && r.getAssets().isUpToDate()) {  
            if (false) {  
                Slog.w(TAG, "Returning cached resources " + r + " " + resDir  
                        + ": appScale=" + r.getCompatibilityInfo().applicationScale);  
            }  
            return r;  
        }  
    }  
  
    AssetManager assets = new AssetManager();  
    if (assets.addAssetPath(resDir) == 0) {  
        return null;  
    }  
  
    DisplayMetrics metrics = getDisplayMetricsLocked(false);  
    r = new Resources(assets, metrics, getConfiguration(), compInfo);  
    if (false) {  
        Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "  
                + r.getConfiguration() + " appScale="  
                + r.getCompatibilityInfo().applicationScale);  
    }  
      
    synchronized (mPackages) {  
        WeakReference<Resources> wr = mActiveResources.get(key);  
        Resources existing = wr != null ? wr.get() : null;  
        if (existing != null && existing.getAssets().isUpToDate()) {  
            // Someone else already created the resources while we were  
            // unlocked; go ahead and use theirs.  
            r.getAssets().close();  
            return existing;  
        }  
        // XXX need to remove entries when weak references go away  
        mActiveResources.put(key, new WeakReference<Resources>(r));  
        return r;  
    }  
}  

以上代码中,mActiveResources对象内部保存了该应用程序所使用到的所有Resources对象,其类型为WeakReference,所以当内存紧张时,可释放Resources占用的资源,自然这不是探究的重点,ResourcesKey的构造需要resDir和compInfo.applicationScale。resdDir变量的含义是资源文件所在路径,实际指的是APK程序所在路径,比如可是:/data/app/com.haii.android.xxx-1.apk,该apk会对应/data/dalvik-cache目录下的:data@app@com.haii.android.xxx-1.apk@classes.dex文件。

所以结论来了:

若一个应用程序没有访问该程序以外的资源,那么mActiveResources变量中就仅有一个Resources对象。

总结:

当ActivityThread类中创建Application、Service、Activity的同时,完成了与ContextImpl的关联绑定,通过ContextImpl类中init方法,获得了一个唯一的Resources对象,根据上述代码中资源的请求机制,再加上ResourcesManager采用单例模式,这样就保证了不同的ContextImpl访问的是同一套资源。

若这篇博客现在就结束了,你一定会杀了我 - -,现在就来分析下,是什么造成了唯一的这个Resources,却展现出了“不同”。

举个通俗易懂的例子,我和我老妈都拿到同一块土豆,但是因为处理这个土豆的方法有区别,导致这个土豆最后表现出来的也不一样,我想把它做成薯片,我妈妈把它炒成了土豆丝,:-D。

再具体一点,比如除了Activity可创建一个Dialog,其它Context都不可创建Dialog。比如在Application中创建Dialog会报错,还有Application和Service可启动一个Activity,但是需要创建一个新的task。比如你在Application中调用startActivity(intent)时系统也会崩溃报错。

报错的原因并不是因为他们拿到的Context资源不同,拿到的都是一个Resoucres对象,但是在创建Dialog的时候会使用到Context对象去获取当前主题信息,但是知道Application和Service是继承自ContextWrapper,没有实现关于主题的功能,然而Activity是继承自ContextThemeWrapper,该类是实现了关于主题功能的,因此创建Dialog的时候必须依附于Activity的Context引用。

结论:

Application、Service、Activity,它们本身对Resources资源处理方法的不同,造成了这个Resoucres最后表现出来的不一样,这么说大家就都懂了吧!

Context内存泄漏

关于Context的内存泄漏,找到一篇比较不错的文章分享给大家。

Android开发,中可能会导致内存泄露的问题

写在最后:

Context可能还有更多深层次的知识需要去了解,比如Context这些封装类,是具体如何通过Binder跟ContextImpl进行关联的;资源对象都被存储在ArrayMap,为何ArrayMap中会有可能存在多个资源对象,如何访问其他应用程序的Context资源等等,剩下的这些就靠大家慢慢发掘了~

参考

duanqz.github.io/2017-12-25-…