Android基础之Context

Android基础之Context

Context

对于Android开发者来说Context应该是非常熟悉的。我们在使用Toast,启动Activity,启动Service,加载资源,操作数据库,获取App相关的文件路径,创建View等操作时,都会涉及到一个Context引用。那么Context到底是什么,Activity和Application作为Context到底有什么区别,哪个更好呢?以及我们在项目建立最初自定义的Application提供全局Context该怎么实现呢?

什么是Context

写过一个小Android demo的应该会有一个疑问为什么一个Android应用不需要一个main()方法来启动整个程序,如果是通过一个main()方法来启动程序,那么我们就可以在里面执行例如new一个Activity等操作。Android的应用模型是基于组件的应用设计模式,组件能够运行需要一个完整的工程环境。同时,各个组件拥有自己独立的场景(Context),并不能采用new的方式创建一个组件对象。Context是维持Android程序中各组件能够正常工作的一个核心功能类。

总结:一个应用是一个完整的工程环境,在这个应用中我们有许多功能,例如打电话,发短信,发邮件。这些功能都算是这个环境中的一个场景。比如发短信场景包含界面的显示以及数据的传递等等。所以我们可以把场景(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.
 */
public abstractclassContext{......}

他的注释:Context提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。也就是说Context描述一个应用程序环境的信息(即上下文);是一个抽象类,Android提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动Activity,发广播,接受Intent等)。

引用自博客 Android应用Context详解及源码解析

那么在平常开发中我们用到哪些这个Context抽象类的实现呢?

盗用大牛郭霖的图片.png

可以发现Application,Service,Activity都是者间接继承自Context。在上面的继承关系中我们看到ContextImpl和ContextWrapper是直接继承自Context。ContextWrapper是Context类的一个封装类,而ContextImpl是Context抽象类的实现类。ContextWrapper的构造函数中有一个真正的Context的引用(ContextImpl对象,mBase)。ContextWrapper的子类有一个ContextThemeWrapper,是带主题封装的类,即通过android:theme属性指定的(例如Activity带UI显示)。

那么我们一个应用中有多少个Context呢?

Android应用程序只有四大组件,而其中两大组件都继承自

Context,另外每个应用程序还有一个全局的Application对象。所以在我们了解了上面继承关系之后我们就可以计算出来Context总数,如下:

appContextCount = Application + ActivityCount + ServiceCount;

Context的实例化

Activity,Application,Service实例都是在ActivityThread中创建的。我参考了博客Android应用Context详解及源码解析(因为自己还没有深入了解到App的启动,只是想了解Context基础知识)

Activity中ContextImpl实例化源码分析

通过startActivity启动一个新的Activity时系统会回调ActivityThread的handleLaunchActivity()方法,该方法内部会调用performLaunchActivity()方法去创建一个Activity实例,然后回调Activity的onCreate()等方法。所以Activity的ContextImpl实例化是在ActivityThread类的performLaunchActivity方法中,如下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent){......//已经创建好新的activity实例if(activity !=null){//创建一个Context对象
                Context appContext =createBaseContextForActivity(r, activity);......//将上面创建的appContext传入到activity的attach方法
                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);......}......return activity;}

通过createBaseContextForActivity(r, activity);创建appContext,然后通过activity.attach设置值。

createBaseContextForActivity(r, activity)部分源代码:

private Context createBaseContextForActivity(ActivityClientRecord r,final Activity activity){//实质就是new一个ContextImpl对象,调运ContextImpl的有参构造初始化一些参数    
        ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);//特别特别留意这里!!!//ContextImpl中有一个Context的成员叫mOuterContext,通过这条语句就可将当前新Activity对象赋值到创建的ContextImpl的成员mOuterContext(也就是让ContextImpl内部持有Activity)。
        appContext.setOuterContext(activity);//创建返回值并且赋值
        Context baseContext = appContext;......//返回ContextImpl对象return baseContext;}

Activity.attach()部分源代码:

finalvoidattach(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){//特别特别留意这里!!!//与上面createBaseContextForActivity方法中setOuterContext语句类似,不同的在于://通过ContextThemeWrapper类的attachBaseContext方法,将createBaseContextForActivity中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Context的实现类ContextImplattachBaseContext(context);......}

总结:Activity通过ContextWrapper的成员mBase来引用了一个ContextImpl对象,这样,Activity组件以后就可以调用这个ContextImpl对象的一系列方法(启动Service等);同时ContextImpl类又通过自己的成员mOuterContext引用了与它关联的Activity,这样ContextImpl类也可以操作Activity。

由此说明一个Activity就有一个Context,而且生命周期和Activity类相同。在使用Activity作为Context引用时要考虑到内存泄漏。
Service中ContextImpl实例化源码分析

通过startService或者bindService方法创建一个新Service时就会回调ActivityThread类的handleCreateService()方法完成相关数据操作。具体handleCreateService方法代码如下:

private void handleCreateService(CreateServiceData data){......//类似上面Activity的创建,这里创建service对象实例
        Service service =null;try{
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            service =(Service) cl.loadClass(data.info.name).newInstance();}catch(Exception e){......}try{......//不做过多解释,创建一个Context对象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);//特别特别留意这里!!!//ContextImpl中有一个Context的成员叫mOuterContext,通过这条语句就可将当前新Service对象赋值到创建的ContextImpl的成员mOuterContext(也就是让ContextImpl内部持有Service)。
            context.setOuterContext(service);

            Application app = packageInfo.makeApplication(false, mInstrumentation);//将上面创建的context传入到service的attach方法
            service.attach(context,this,data.info.name,data.token, app,
                    ActivityManagerNative.getDefault());
            service.onCreate();......}catch(Exception e){......}}

Service中的attach方法:

    public finalvoidattach(
            Context context,
            ActivityThread thread, String className, IBinder token,
            Application application, Object activityManager){//特别特别留意这里!!!//与上面handleCreateService方法中setOuterContext语句类似,不同的在于://通过ContextWrapper类的attachBaseContext方法,将handleCreateService中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Context的实现类ContextImplattachBaseContext(context);......}
说明一个Service就有一个Context,而且生命周期和Service类相同,在使用Service作为Context引用时要考虑到内存泄漏
Application中ContextImpl实例化源码分析

一个APP以后每次重新启动时都会首先创建Application对象(每个APP都有一个唯一的全局Application对象,与整个APP的生命周期相同)。创建Application的过程也在ActivityThread类的handleBindApplication()方法完成相关数据操作。而ContextImpl的创建是在该方法中调运LoadedApk类的makeApplication方法中实现,LoadedApk类的makeApplication()方法中源代码如下:

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation){//只有新创建的APP才会走if代码块之后的剩余逻辑if(mApplication !=null){return mApplication;}//即将创建的Application对象
        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")){initializeJavaContextClassLoader();}//不做过多解释,创建一个Context对象
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread,this);//将Context传入Instrumentation类的newApplication方法
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);//特别特别留意这里!!!//ContextImpl中有一个Context的成员叫mOuterContext,通过这条语句就可将当前新Application对象赋值到创建的ContextImpl的成员mOuterContext(也就是让ContextImpl内部持有Application)。
            appContext.setOuterContext(app);}catch(Exception e){......}......return app;}

Instrumentation.newApplication方法部分源码:

publicApplicationnewApplication(ClassLoader cl,String className,Context context)throwsInstantiationException,IllegalAccessException,ClassNotFoundException{returnnewApplication(cl.loadClass(className), context);}

newApplication方法部分源码:

staticpublicApplicationnewApplication(Class<?> clazz,Context context)throwsInstantiationException,IllegalAccessException,ClassNotFoundException{......//继续传递context
        app.attach(context);return app;}

Application类的attach方法部分源码:

finalvoidattach(Context context){//特别特别留意这里!!!//与上面makeApplication方法中setOuterContext语句类似,不同的在于://通过ContextWrapper类的attachBaseContext方法,将makeApplication中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Application的实现类ContextImplattachBaseContext(context);......}
说明一个Application就有一个Context,而且生命周期和Application类相同(然而一个App只有一个Application,而且与应用生命周期相同)。

通过Context引用获取资源

看到这里我有个疑问不同的Context引用,获取到的资源是同一份吗?

classContextImplextendsContext{......privatefinalResourcesManager mResourcesManager;privatefinalResources mResources;......@OverridepublicResourcesgetResources(){return mResources;}......}

有了上面分析我们可以很确定平时写的App中context.getResources方法获得的Resources对象就是上面ContextImpl的成员变量mResources。那我们追踪可以发现mResources的赋值操作如下:

privateContextImpl(ContextImpl container,ActivityThread mainThread,LoadedApk packageInfo,IBinder activityToken,UserHandle user,boolean restricted,Display display,Configuration overrideConfiguration){......//单例模式获取ResourcesManager对象
        mResourcesManager =ResourcesManager.getInstance();......//packageInfo对于一个APP来说只有一个,所以resources 是同一份Resources resources = packageInfo.getResources(mainThread);if(resources !=null){if(activityToken !=null|| displayId !=Display.DEFAULT_DISPLAY
                    || overrideConfiguration !=null||(compatInfo !=null&& compatInfo.applicationScale
                            != resources.getCompatibilityInfo().applicationScale)){//mResourcesManager是单例,所以resources是同一份
                resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
                        packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
                        packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
                        overrideConfiguration, compatInfo, activityToken);}}//把resources赋值给mResources
        mResources = resources;......}

由此可以看出在设备其他因素不变的情况下我们通过不同的Context实例得到的Resources是同一套资源。

以上源码分析完全是引用自别人的博客,自己对上面的分析也只是停留在一个字面意思上,只是通过上面的分析理解了不同Context的实例相对于整个App的生命周期,可以在实际开发中作为判断到底使用哪个Context引用的依据。以及解决了心中对于不同Context引用获取资源的疑问

getApplication和getApplicationContext的区别

getApplication()方法

Activity和Service提供了getApplication,而且返回类型都是Application。

publicfinalApplicationgetApplication(){return mApplication;}

这个mApplication都是在各自类的attach方法参数出入的,也就是说这个mApplication都是在ActivityThread中各自实例化时获取的makeApplication方法返回值。而且不同的Activity和Service返回的Application均为同一个全局对象。

getApplicationContext()方法

通过ContextImpl对象调用getApplicationContext()方法

classContextImplextendsContext{......@OverridepublicContextgetApplicationContext(){return(mPackageInfo !=null)?
                mPackageInfo.getApplication(): mMainThread.getApplication();}......}

可以看到getApplicationContext方法是Context的方法,而且返回值是Context类型,返回对象和上面通过Service或者Activity的getApplication返回的是一个对象。

区别

虽然这两个方法最终的得到的值都是同一个对象的引用,但是它们的类型不同(一个是Application,一个是Context)且作用域有很大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法。

还有一个得到Context的方法,getBaseContext()。getBaseContext()方法得到的是一个ContextImpl对象。也就是调用ContextWrapper的getBaseContext()方法返回存储ContextImpl对象的mBase。

Activity和Application作为Context引用

Context引起的内存泄漏

在实际开发中我们需要用到工具类,一般都会采用单例模式:

publicclassSingleton{privatestaticSingleton instance;privateContext mContext;privateSingleton(Context context){this.mContext = context;
    }
    publicstaticsynchronizedSingletongetInstance(Context context){if(instance ==null){
            instance =newSingleton(context);}return instance;}}

你在某个Activity调用了getInstance()方法,直接传了个this;这样问题就来了,我们的这个类中的sInstance是一个static且强引用的,在其内部引用了一个Activity作为Context,也就是说,我们的这个Activity只要我们的项目活着,就没有办法进行内存回收。而我们的Activity的生命周期肯定没这么长,所以造成了内存泄漏。

Java强引用会导致GC内存无法回收

解决办法:

publicclassSingleton{privatestaticSingleton instance;privateContext mContext;privateSingleton(Context context){this.mContext = context;
    }
    publicstaticsynchronizedSingletongetInstance(Context context){if(instance ==null){
            instance =newSingleton(context.getApplicationContext());}return instance;}}

由于Application对象的生命周期如上面分析的那样,和整个项目一样长。也就是它的生命周期和我们的单例对象一致。

不同的Context适用场景

正如上面所说的Context引用不当会造成内存泄漏,那么我们是否可以都引用Application作为Context引用呢?答案是否定的,不同Context(指的是Activity,Application,Service)的应用场景是不同的,并非所有Activity为Context的场景,Application Context都能搞定。

Application

Activity

Service

show dialog

N

Y

N

start Activity

N

Y

N

Layout Inflation

N

Y

N

start service

Y

Y

Y

send a BroadCast

Y

Y

Y

register a BroadCast Receiver

Y

Y

Y

load Resource

Y

Y

Y

上述表格中可以看到,在启动一个Activity和Dialog时,不推荐使用Application和Service。Android系统出于安全原因的考虑,是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。调用除了Activity的,其他Context实例的LayoutInflation,会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。

总结:和UI相关的方法基本都不建议或者不可使用Application,并且,前三个操作基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。

参考

Context都没弄明白,还怎么做Android开发?

Android应用Context详解及源码解析

Android Context完全解析,你所不知道的Context的各种细节

Android Context 上下文 你必须知道的一切

目标

  • Activity启动

  • 项目初始化,自定义Application细节

本文仅用于笔记的收藏;

原作者:狮_子歌歌

原链接:Android基础之Context - 简书 (jianshu.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值