摘自:柯元旦 《Android内核剖析》
Android程序员把”场景“抽象为Contex类,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service)。一个应用程序可以认为是一个工作环境,用户在这个工作环境中会切换到不同的场景。
接下来,从代码方面来看。Activity类的确是基于Context,而Service类也是基于Context.Activity除了基于Context类外,还实现了一些其他重要接口,从设计角度来看,interface仅仅是某些功能,而extends才是类的本质,即Activity的本质是一个Context,其所实现的其他接口只是为了扩充Context的功能而已,扩充后的类称之为一个Acitvity或者Service.
一个应用程序包含多少个Context对象?
可以明确的是:
1> 一个Activity就是一个场景(Context),一个Service也是一个场景,所以,应用程序中有多少个Activity或Service,就会有多少个Context对象。
2> Context.getResources()等方法的确返回的是同一个全局对象。
创建Context
程序在第一次启动时,会辗转调用到handleBindApplication()方法中,该方法中有两处创建了ContextImpl对象,但都是在if(data.instrumentationName!=null)条件中。该条件在一般的程序执行时都不会被执行到,只有当创建一个Android Unit Test工程时,相应的Test程序会满足这个条件。
而如果不是测试工程的话,则调用makeApplication()方法,代码如下:
Application app=data.info.makeApplication(...);
而makeApplication()方法中,主要包含以下代码:
ConextImpl appContext=new ContextImpl();
appContext.init(this,mull,mActivityThread);
app=mActivityThread.mInstrumentation.newApp(cl,appClass,appContext);
appContext.setOuterContext(app);
即创建一个ContextImpl对象,然后调用init()方法,其中第一个参数this指的就是当前PackageInfo对象,该对象将赋值给ContexxtImpl类中的重要成员变量mPackageInfo.
这里需要注意,这个PackageInfo对象又是从何而来呢?这就要回溯到是谁调用了makeApplication()方法,回溯流程如下:
首先,AmS通过远程调用到ActivityThread的bindApplication()方法,该方法的参数包括两种。一种是ApplicationInfo,这是实现了Parcelable接口的数据类,意味着这个对象是由AmS创建的,通过IPC传递到ActivityThread中,另一种是其他相关参数。
在bindApplication()方法中,会用以上两种参数构造一个本地AppBindData数据类,然后再去调用handleBindApplication().
在调用handleBindApplication()的时候,AppBindData对象中的info变量还是空值,然后会使用data.info=getPackageInfoNoChecked()方法为info变量赋值,而这个方法的内部实际上会根据AppBindData中的ApplicationInfo中的mPackageName创建一个PackageInfo对象,并把这个对象保存为AcivityThread类的全局对象。显然,一个应用程序中的所有Activity或者Application对象对应的mPackageName都是一样的,即为包的名称,所以ActivityThread中产生一个全局PackageInfo对象。
接下来,就回到上面所说的调用data.makeApplication()方法了,这就是PackageInfo对象的来源。
scheduleLaunchActivity()方法中会根据以上两种参数构造一个本地ActivityRecord数据类,ActivityThread内部会为每一个Activity创建一个ActivityRecord对象,并使用这些数据对象来管理Activity.
接着会调用到handleLaunchActivity(),然后再调用到performLaunchActivity(),该方法中创建ContextImpl的代码如下:
ContextImpl appContext=new ContextImpl();
appContext.init(r.packageInfo,r.token,this);
appContext.setOuterContext(activity);
这个参数r.packageInfo是如何而来的呢?答案如下代码所示:
ActivityInfo aInfo=r.activityInfo;
if(r.packageInfo==null){
r.packageInfo=getPackageInfo(aInfo.applicationInfo,Context.CONTEXT_INCLUDE_CODE);
}
即在performLaunchActivity()开始执行时,首先为r.packageInfo变量赋值,getpackageInfo方法的执行逻辑和getPackageInfoNoChecked()基本相同。所以,r.packageInfo对象的PackageInfo对象和Application对应的packageInfo对象是同一个。
在scheduleCreateService()方法中,会使用以上两种参数构造一个CreateServiceData的数据对象,ActivityThread会为其所包含的每一个Service创建该数据对象,并通过这些对象来管理Service.
接着,会执行handleCreateService()方法,其中创建ContextImpl对象的代码如下:
ContextImpl context=new ContextImpl();
context.init(packageInfo,null,this);
Application app=packageInfo.makeApplication(false...);
context.setOuterContext(service);
这与前面的基本相同,调用context.init()方法的第一个参数packageInfo赋值的代码如下:
PackageInfo packageInfo=getPackageInfoNOCheck(data.info.applicationInfo);
赋值代码同样使用了getPackageInfoNOCheck()方法,这意味着Service对应的Context对象内部的mPckageInfo与Activity、Application中是完全相同的。
实际上一个应用程序包含的Context个数应该为:
Context个数 = Service个数 + Activity个数 +1
最后一个1代表的是Application类本身也是对应一个Context对象。
this 与 getApplicationContext() 两种上下文的区别?
① getApplicationContext();生命周期长,只要应用还存活它就存在;
this 生命周期短,只要 Activity 不存在了,系统就会回收;
② getBaseContext(),getApplication(),getApplicationContext();
都不能放在 AlertDialog 做上下文;
③ getApplicationContext() 使用场景是比如频繁需要操作的数据库
推荐用法:Activity.this
Context是什么?
一个Context意味着一个场景,一个场景就是用户和操作系统交互的一种过程。比如当你打电话、看短信时,场景就包括电话、短信程序对应的界面,以及隐藏在界面后的数据。Android程序员把”场景“抽象为Contex类,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service)。一个应用程序可以认为是一个工作环境,用户在这个工作环境中会切换到不同的场景。
接下来,从代码方面来看。Activity类的确是基于Context,而Service类也是基于Context.Activity除了基于Context类外,还实现了一些其他重要接口,从设计角度来看,interface仅仅是某些功能,而extends才是类的本质,即Activity的本质是一个Context,其所实现的其他接口只是为了扩充Context的功能而已,扩充后的类称之为一个Acitvity或者Service.
一个应用程序包含多少个Context对象?
可以明确的是:
1> 一个Activity就是一个场景(Context),一个Service也是一个场景,所以,应用程序中有多少个Activity或Service,就会有多少个Context对象。
2> Context.getResources()等方法的确返回的是同一个全局对象。
创建Context
1. Application对应的Context
每个应用程序在第一次启动时,都会首先创建一个Application对象,默认的Application是应用程序的包名,用户可以重载默认的Application.方法是在AndroidManifest.xml的Appliction标签中声明一个新的Application名称,然后在源码中添加该名称的类,该类的父类使用Application类即可。程序在第一次启动时,会辗转调用到handleBindApplication()方法中,该方法中有两处创建了ContextImpl对象,但都是在if(data.instrumentationName!=null)条件中。该条件在一般的程序执行时都不会被执行到,只有当创建一个Android Unit Test工程时,相应的Test程序会满足这个条件。
而如果不是测试工程的话,则调用makeApplication()方法,代码如下:
Application app=data.info.makeApplication(...);
而makeApplication()方法中,主要包含以下代码:
ConextImpl appContext=new ContextImpl();
appContext.init(this,mull,mActivityThread);
app=mActivityThread.mInstrumentation.newApp(cl,appClass,appContext);
appContext.setOuterContext(app);
即创建一个ContextImpl对象,然后调用init()方法,其中第一个参数this指的就是当前PackageInfo对象,该对象将赋值给ContexxtImpl类中的重要成员变量mPackageInfo.
这里需要注意,这个PackageInfo对象又是从何而来呢?这就要回溯到是谁调用了makeApplication()方法,回溯流程如下:
首先,AmS通过远程调用到ActivityThread的bindApplication()方法,该方法的参数包括两种。一种是ApplicationInfo,这是实现了Parcelable接口的数据类,意味着这个对象是由AmS创建的,通过IPC传递到ActivityThread中,另一种是其他相关参数。
在bindApplication()方法中,会用以上两种参数构造一个本地AppBindData数据类,然后再去调用handleBindApplication().
在调用handleBindApplication()的时候,AppBindData对象中的info变量还是空值,然后会使用data.info=getPackageInfoNoChecked()方法为info变量赋值,而这个方法的内部实际上会根据AppBindData中的ApplicationInfo中的mPackageName创建一个PackageInfo对象,并把这个对象保存为AcivityThread类的全局对象。显然,一个应用程序中的所有Activity或者Application对象对应的mPackageName都是一样的,即为包的名称,所以ActivityThread中产生一个全局PackageInfo对象。
接下来,就回到上面所说的调用data.makeApplication()方法了,这就是PackageInfo对象的来源。
2. Activity对应的Context
启动Activity时,AmS会通过IPC调用到ActivityThread的scheduleLaunchActivity()方法,该方法包含两种参数。一种是ActivityInfo,这是一个实现了Parcelable接口的数据类,意味着该对象是AmS创建的,并通过IPC传递到ActivityThread;另一种是其他参数。scheduleLaunchActivity()方法中会根据以上两种参数构造一个本地ActivityRecord数据类,ActivityThread内部会为每一个Activity创建一个ActivityRecord对象,并使用这些数据对象来管理Activity.
接着会调用到handleLaunchActivity(),然后再调用到performLaunchActivity(),该方法中创建ContextImpl的代码如下:
ContextImpl appContext=new ContextImpl();
appContext.init(r.packageInfo,r.token,this);
appContext.setOuterContext(activity);
这个参数r.packageInfo是如何而来的呢?答案如下代码所示:
ActivityInfo aInfo=r.activityInfo;
if(r.packageInfo==null){
r.packageInfo=getPackageInfo(aInfo.applicationInfo,Context.CONTEXT_INCLUDE_CODE);
}
即在performLaunchActivity()开始执行时,首先为r.packageInfo变量赋值,getpackageInfo方法的执行逻辑和getPackageInfoNoChecked()基本相同。所以,r.packageInfo对象的PackageInfo对象和Application对应的packageInfo对象是同一个。
3. Service对应的Context
启动Service时,AmS首先会通过IPC调用到ActivityThread的scheduleCreateService()方法,该方法包含两种参数。一种是ServiceInfo,这是一个实现了Parcelable接口的数据类,意味着该对象是AmS创建的,并通过IPC传递到ActivityThread;另一种是其他参数。在scheduleCreateService()方法中,会使用以上两种参数构造一个CreateServiceData的数据对象,ActivityThread会为其所包含的每一个Service创建该数据对象,并通过这些对象来管理Service.
接着,会执行handleCreateService()方法,其中创建ContextImpl对象的代码如下:
ContextImpl context=new ContextImpl();
context.init(packageInfo,null,this);
Application app=packageInfo.makeApplication(false...);
context.setOuterContext(service);
这与前面的基本相同,调用context.init()方法的第一个参数packageInfo赋值的代码如下:
PackageInfo packageInfo=getPackageInfoNOCheck(data.info.applicationInfo);
赋值代码同样使用了getPackageInfoNOCheck()方法,这意味着Service对应的Context对象内部的mPckageInfo与Activity、Application中是完全相同的。
4. Context之间的关系
从以上3种情况可以看出,创建Context对象的过程基本上是相同的,包括代码的结构也很相似,所不同的仅仅是针对Application、Activity、Service使用了不同的数据对象。实际上一个应用程序包含的Context个数应该为:
Context个数 = Service个数 + Activity个数 +1
最后一个1代表的是Application类本身也是对应一个Context对象。
this 与 getApplicationContext() 两种上下文的区别?
① getApplicationContext();生命周期长,只要应用还存活它就存在;
this 生命周期短,只要 Activity 不存在了,系统就会回收;
② getBaseContext(),getApplication(),getApplicationContext();
都不能放在 AlertDialog 做上下文;
③ getApplicationContext() 使用场景是比如频繁需要操作的数据库
推荐用法:Activity.this