先来看看这个Context类结构图
Context类本身是一个纯abstact类,它有两个具体的实现子类:ContextImpl和ContextWrapper
ContextWrapper是一个Context的包装类,其构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper方法都会转向其所包含的真正的Context对象。
ContextImpl是Context的具体实现类,Activity,Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。ContextThemeWrapper其内部包含了与主题(Theme)相关的接口。
源码中Application,Activity,Service创建Context的过程Application,Activity,Service在初始化过程中都会创建ContextImpl,我们分别来看看各种的创建过程:
Application对应的Context程序第一次启动时,会通过bindApplication中发送_APPLICATION消息,然后handleBindBapplication进行ContextImpl创建,该方法中有两处创建了ContextImpl对象,但这两处创建后的执行条件都是在if(!=null){中,只有在创建了AndroidUnitTest工程是,相应的Test程序才会满足这个条件。
如果不是测试工程的话,则调用makeApplication方法
Applicationapp=(,null);
在makeApplication方法中
////makeApplicationContextImplappContext=(mActivityThread,this);app=(cl,appClass,appContext);(app);
ContextImpl的createAppContext
////createAppContextstaticContextImplcreateAppContext(ActivityThreadmainThread,LoadedApkpackageInfo){if(packageInfo==null)thrownewIllegalArgumentException("packageInfo");returnnewContextImpl(null,mainThread,packageInfo,null,null,false,null,null,_DISPLAY);}所以createAppContext中的packageInfo参数实际上就是,来自handleBindBapplication方法调用,而handleBindBapplication的调用则是来自bindApplication发送消息,下面来看看bindApplication的代码:
767publicfinalvoidbindApplication(StringprocessName,ApplicationInfoappInfo,768ListProviderInfoproviders,ComponentNameinstrumentationName,769ProfilerInfoprofilerInfo,BundleinstrumentationArgs,770IInstrumentationWatcherinstrumentationWatcher,771IUiAutomationConnectioninstrumentationUiConnection,intdebugMode,772booleanenableOpenGlTrace,booleanisRestrictedBackupMode,booleanpersistent,773Configurationconfig,CompatibilityInfocompatInfo,MapString,IBinderservices,774BundlecoreSettings){775···819820AppBindDatadata=newAppBindData();821=processName;822=appInfo;823=providers;824=instrumentationName;825=instrumentationArgs;826=instrumentationWatcher;827=instrumentationUiConnection;828=debugMode;829=enableOpenGlTrace;830=isRestrictedBackupMode;831=persistent;832=config;833=compatInfo;834=profilerInfo;835sMessage(_APPLICATION,data);//发送数据,在handleBindBapplication中接收836}可以看到在bindApplication方法中,会用ApplicationInfo等相关参数构建一个AppBindData的数据类,注意,此时AppBindData的info还是为空,后面在handleBindBapplication中的getPackageInfoNoCheck才会进行赋值。
=getPackageInfoNoCheck(,);复制代码
可以看到,该方法是根据AppBindData的ApplicationInfo,CompatibilityInfo这两个参数进行创建的
//getPackageInfoNoCheck1787publicfinalLoadedApkgetPackageInfoNoCheck(ApplicationInfoai,1788CompatibilityInfocompatInfo){1789returngetPackageInfo(ai,compatInfo,null,false,true,false);1790}//getPackageInfoprivateLoadedApkgetPackageInfo(ApplicationInfoaInfo,CompatibilityInfocompatInfo,1805ClassLoaderbaseLoader,booleansecurityViolation,booleanincludeCode,1806booleanregisterPackage){1807finalbooleandifferentUser=(()!=());1808synchronized(mResourcesManager){1809WeakReferenceLoadedApkref;1810if(differentUser){1811//Cachingnotsupportedacrossusers1812ref=null;1813}elseif(includeCode){1814ref=();1815}else{1816ref=();1817}18181819LoadedApkpackageInfo=ref!=null?():null;1820if(packageInfo==null||(!=null1821!().isUpToDate())){1822if(localLOGV)(TAG,(includeCode?"Loadingcodepackage"1823:"Loadingresource-onlypackage")++"(in"+(mBoundApplication!=null1825?:null)1826+")");1827packageInfo=1828newLoadedApk(this,aInfo,compatInfo,baseLoader,1829securityViolation,includeCode1830(_HAS_CODE)!=0,registerPackage);18311832if(mSystemThread"android".equals()){1833(aInfo,1834getSystemContext().());1835}18361837if(differentUser){1838//Cachingnotsupportedacrossusers1839}elseif(includeCode){1840(,1841newWeakReferenceLoadedApk(packageInfo));1842}else{1843(,1844newWeakReferenceLoadedApk(packageInfo));1845}1846}1847returnpackageInfo;1848}1849}该方法会创建一个ActivityThread类的全局packageInfo(LoadApk)对象。接下来就调用到(,null)这个就是LoadApk(packageInfo)的来源
Application中创建Context的过程Activity对应的Context启动Activity时,AMS(ActivityManagerService)会通过Binder调用到ActivityThread的scheduleLaunchActivity,在该类内存创建一个ActivityClientRecord对象,然后发送消息sMessage(_ACTIVITY,r)
630publicfinalvoidscheduleLaunchActivity(Intentintent,IBindertoken,intident,631ActivityInfoinfo,ConfigurationcurConfig,ConfigurationoverrideConfig,632CompatibilityInfocompatInfo,Stringreferrer,IVoiceInteractorvoiceInteractor,633intprocState,Bundlestate,PersistableBundlepersistentState,634ListResultInfopingResults,ListReferrerIntentpingNewIntents,635booleannotResumed,booleanisForward,ProfilerInfoprofilerInfo){636637updateProcessState(procState,false);638639ActivityClientRecordr=newActivityClientRecord();640641=token;642=ident;643=intent;644=referrer;645=voiceInteractor;646=info;647=compatInfo;···659=overrideConfig;660updatePingConfiguration(curConfig);661//发送消息,最后调用handleLaunchActivity()662sMessage(_ACTIVITY,r);663}handleLaunchActivity中会继续调用到performLaunchActivity,该方法中创建ContextImpl的代码如下:
privateActivityperformLaunchActivity(ActivityClientRecordr,IntentcustomIntent){ActivityInfoaInfo=;2297if(==null){2298=getPackageInfo(,,2299_INCLUDE_CODE);2300}···2344if(activity!=null){//创建context2345ContextappContext=createBaseContextForActivity(r,activity);···2350(appContext,this,getInstrumentation(),,2351,app,,,title,,2352,,config,2353,);可以看到首先为变量赋值,getPackageInfo方法的执行逻辑基本和getPackageInfoNoCheck相同,然后创建Context,具体的创建Context在createBaseContextForActivity方法中
2425privateContextcreateBaseContextForActivity(ActivityClientRecordr,finalActivityactivity){2426intdisplayId=_DISPLAY;2427try{2428displayId=().getActivityDisplayId();2429}catch(RemoteExceptione){2430}24312432ContextImplappContext=(2433this,,displayId,);2434(activity);···2453returnbaseContext;2454}Activity创建Context的过程Service对应的Context启动Service时,AMS会通过Binder调用到ActivityThread的scheduleCreateService方法,在该方法中构造CreateServiceData对象
718publicfinalvoidscheduleCreateService(IBindertoken,719ServiceInfoinfo,CompatibilityInfocompatInfo,intprocessState){720updateProcessState(processState,false);721CreateServiceDatas=newCreateServiceData();722=token;723=info;724=compatInfo;725726sMessage(_SERVICE,s);727}接下来发送_SERVICE,执行handleCreateService()方法,创建Context对象
2854LoadedApkpackageInfo=getPackageInfoNoCheck(2855,);2856Serviceservice=null;2867···2871ContextImplcontext=(this,packageInfo);2872(service);28732874Applicationapp=(false,mInstrumentation);2875(context,this,,,app,2876());2877();
这里和之前的Activity创建Context基本相同,赋值代码也是使用了getPackageInfoNoCheck方法,这就意味着Service对应Context内部的mPackageInfo与Activity,Application中是完全相同的。
Service中创建Context的过程Context之间的关系从上面的分析可以看出,Application/Activity/Service中创建Context对象的过程基本是相同的,代码结构也很类似,下面是不同Context子类中PackageInfo对象的来源
小结一个应用程序中包含Context个数=Service个数+Activity个数+1(Application)+其他ContextImpl个数
网上也有一些文章提出,Context个数=2x(Service个数+Activity个数+Application个数)+其他ContextImpl个数,这里的乘以2表示,创建Applciation/Service/Activity过程会创建基础对象ContextImpl,Application等是代理对象,但从前面的Context类结构就可以看出,他们最终都继承ContextWrapper对象,而ContextWrapper只是一个包装类,真正的实现是ContextImpl,所以Applciation/Service/Activity的创建过程必然包含创建ContextImpl,Application当然是代理对象,如果只是从纯个数的讨论来说并没什么太大意义。我倾向于Context表示上下文(Application,Activity,Service)的个数,不过ContextImpl还可能()去读取其他apk的资源,所以这里+其他ContextImpl个数是合理的。
应用程序中包含多个ContextImpl对象,而其内部变量的mPackageInfo却指向同一个LoadApk对象,这种设计结构一般意味着ContextImpl是一个轻量级类,而LoadApk是重量级类,从我们之前的对ContextImpl也可以看出来,ContextImpl内部的大多数重量级方法内部实现都转向了mPackageInfo(LoadApk)对象对应的方法,即事实上是调用了同一个LoadApk对象,这样从系统效率的角度看也还是合理的。
Context的作用范围Context作用域ApplicationActivityServiceShowaDialogNOYESNOStartanActivity不推荐YES不推荐LayoutInflation不推荐YES不推荐StartaServiceYESYESYESSaBroadcastYESYESYESRegisterBroadcastReceiverYESYESYESLoadResourceValueYESYESYES
如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错:CallingstartActivityfromoutsideofanActivitycontextrequirestheFLAG_ACTIVITY_NEW_?这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用,Service同Application。
getApplicaiton和getApplicationContext的区别我们通过程序调用打印出这两者
//调用("jackie","getApplication"+application)("jackie","getApplicationContext"+applicationContext)//打印结果2020-10-1822:05:55.32318590-18590//jackie:@eee72312020-10-1822:05:55.32318590-18590//jackie:@eee7231可以看到打印出的内存地址都是相同的,看来他们是同一个对象;再来看看这个两个方法的源码
//===============getApplicationContext==========//@OverridepublicContextgetApplicationContext(){();}//================getApplication=================//Activity/**Returntheapplicationthatownsthisactivity.*/publicfinalApplicationgetApplication(){returnmApplication;}//Service/**Returntheapplicationthatownsthisservice.*/publicfinalApplicationgetApplication(){returnmApplication;}Application中真的不能弹出Dialog吗?首先我们做个测试,用getApplciationContext创建Dialog,然后弹出Dialog。
//vmapi18publicvoidtest1(){Dialogalert=newDialog(getApplicationContext());//().setType(_SYSTEM_ALERT);Viewview=(this).inflate(,null);(view);();}复制代码然后就可以看到控制台报错日志
10-1910:19:17.6466451-6451//AndroidRuntime:FATALEXCEPTION:$BadTokenException:(:563)(:269)(:69)(:281)(:52)$1.onClick(:27)
提示我们token为空,无法add到window中。
这里我们需要简要介绍一下,Android中所有的视图都是通过Window来呈现的,不管是Activity,Dialog,还是Toast,它们的视图实际上都是附加在Window上的,因此Window实际上是View的直接管理者。
Window有三种类型:
应用级别窗口,层级范围1~99,比如Activity。
子窗口,不能单独存在,必须依附于特定的父window,层级范围1000~1999,比如Dialog。
系统级别窗口,层级范围2000~2999,比如Toast。系统类型的window是需要检查权限的,需要在AndroidManifest中声明。
再看看上面的错误,因为传的是getApplcationContext,所以我们没有token,而这时候要弹出Dialog就报错了。另外,系统Window比较特殊,它可以不需要token。因此在上面的例子中,只需要指定对话框的Window类型为系统类型就可以正常弹出对话框了。
//vmapi18android18上测试成功publicvoidtest1(){Dialogalert=newDialog(getApplicationContext());().setType(_SYSTEM_ALERT);Viewview=(this).inflate(,null);(view);();}//vmapi29android29上测试成功publicvoidtest(){Dialogalert=newDialog(getApplicationContext());().setType(_APPLICATION_OVERLAY);Viewview=(this).inflate(,null);(view);();}同时需要声明权限
uses-permissionandroid:name="_ALERT_WINDOW"/总结
至此,我们已经将Context类结构,Context的作用范围等都梳理了一遍,有时候要充分了解一个功能,需要深入源码,手动做实验验证,查找相关文章,才能充分掌握。
作者:伤心的猪大肠
链接: