• Application/Activity/Service创建Context的细节

    ContextImpl 类
    服务器君一共花费 16.410 ms 进行了 2 次数据库查询,努力地为您提供了这个页面。
    广告很萌的

    Context(翻译为场景):一个Activity就是一个Context,一个Service也是一个Context,应用程序中有多少个Activity或者Service,就会有多少个Context对象。 Android把“场景”抽象为Context类,用户和操作系统的每一次交互都是一个场景,如打电话为有界面的场景,后台服务service为没界面的场景。

    Context类本身是一个纯abstract类,为了使用方便,定义了ContextWrapper类,这只是一个包装而已,它的构造函数中必须包含一个真正的Context引用,同时它提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。

    ContextThemeWrapper类,内部包含了主题相关的接口,在此说的主题是指在Manifest.xml中通过android:theme为Application元素或者Activity元素指定的主题。

    ContextImpl 类真正实现了Context中所有函数,应用程序中调用的各种Context类的方法,其实现均来自于该类。每一个应用程序都是从ActivityThread类开始的,创建Context对象也是在该类中完成,具体创建ContexImpl类的地方一共有7处:

    • PackageInfo.makeApplication()
    • performLaunchActivity()
    • handleCreateBackupAgent()
    • handleCreateService()
    • handleBindApplication()
    • handleBindApplication()
    • attach()

    attach()方法仅在Framework进程(system_server)启动时调用,应用程序运行时不会调用到该方法,需要明确的是system_server进程本身也是一个应用程序,所以其入口也是ActivityThread类,只是这个ActivityThread和一些系统服务运行在同一个进程空间中而已。

    attach()方法中创建ContextImpl对象的基本逻辑如同以下三小点:

    1. Application对应的Context

    每个应用程序在第一次启动时,都会首先创建一个Application对象,默认的Application是应用程序的包名,用户可以重载默认的Application; 程序第一次启动时,会辗转调用到handleBindApplication()方法中,该方法中有两处创建了ContextImpl对象,但都是在if(data.instrumentationName!=null)条件中,该条件在一般的程序执行时都不会被执行到,只有当创建一个Android UnitTest工程时,相应的Test程序才能满足这个条件。

    而如果不是测试工程的话,则调用makeApplication()方法:

    // If the app is being launched for full backup or restore, bring it up in
    // a restricted environment with the base application class.
    Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    

    下面是makeApplication函数处理方法代码:

    java.lang.ClassLoader cl = getClassLoader();
    ContextImpl appContext = new ContextImpl();
    appContext.init(this, null, mActivityThread);
    app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
    appContext.setOuterContext(app);
    

    即创建一个ContextImpl对象,然后调用init方法,其中第一个参数this指的是当前PackageInfo对象{LoadedApk.Java},该对象将赋值给ContextImpl类中的重要成员变量mPackageInfo。

    需要注意的是PackageInfo对象从什么地方来的?就需要回溯到是谁调用了makeApplication()方法。

    首先,AmS通过远程调用到ActivityThread的bindApplication()方法,该方法的参数包括两种,一种是ApplicationInfo,这是实现了Parcelable接口的数据类,意味着这个对象是由AmS创建的,通过IPC传递到ActivityThread中,另一种是其他的相关参数在bindApplication()方法中。

    public final void bindApplication(String processName,
    		ApplicationInfo appInfo, List<ProviderInfo> providers,
    		ComponentName instrumentationName, String profileFile,
    		Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
    		int debugMode, boolean isRestrictedBackupMode, Configuration config,
    		Map<String, IBinder> services) {
    	//////
    	AppBindData data = new AppBindData();
    	//////
    }
    

    bindApplication方法创建一个本地AppBindData数据类,然后再去调用handleBindApplication。在调用handleBindApplication()的时候,AppBindData对象中的info变量还是空值,然后会使用data.info = getPackageInfoNoChecked方法为info变量赋值。

    private final void handleBindApplication(AppBindData data) {
    	mBoundApplication = data;
    	/////
    	data.info = getPackageInfoNoCheck(data.appInfo);
    	/////
    

    getPackageInfoNoCheck方法如下:

    public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) {
    	return getPackageInfo(ai, null, false, true);
    }
    

    getPackageInfo方法如下:

    private final LoadedApk getPackageInfo(ApplicationInfo aInfo,
    	ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
    	LoadedApk packageInfo = ref != null ? ref.get() : null;
    	if (packageInfo == null || (packageInfo.mResources != null
    			&& !packageInfo.mResources.getAssets().isUpToDate())) {
    		//...if (includeCode) {
    			mPackages.put(aInfo.packageName, new WeakReference<LoadedApk>(packageInfo));
    		} else {
    			mResourcePackages.put(aInfo.packageName, new WeakReference(packageInfo));
    		}
    	}
    	return packageInfo;
    }
    

    这个方法的内部实际上会根据AppBindData中的ApplicationInfo中的mPackageName创建一个PackageInfo对象,并把这个对象保存为ActivityThread类的全局对象,显然,一个应用程序中的所有Activity或者Application对象对应的mPackageName都一样,即为包的名称,所以ActivityThread中会产生一个全局PackageInfo对象。

    接下来,回到data.info.makeApplication()方法,这是PackageInfo对象的来源,以上流程可以总结如下:

    2. Activity对象中获取Context对象

    启动Activity时,AmS会通过IPC调用到ActivityThread的scheduleLaunchActivity方法,该方法包含一个参数ActivityInfo,这是一个实现了Parcelable接口的数据类,意味着该对象是AmS创建的,并通过IPC传递到ActivityThread;另一些参数如下。

    public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
    	ActivityInfo info, Bundle state, List<ResultInfo> pendingResults,
    	List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) {
    ActivityClientRecord r = new ActivityClientRecord(); 
    	......
    	}
    

    scheduleLaunchActivity方法中会创建一个本地ActivityRecord数据类,ActivityThread内部会为每一个Activity创建一个ActivityRecord对象,并使用这些数据来管理Activity。

    接下来会调用handleLaunchActivity(),然后再调用performLaunchActivity(),该方法中创建ContextImpl代码如下:

    ContextImpl appContext = new ContextImpl();
    appContext.init(r.packageInfo, r.token, this);
    appContext.setOuterContext(activity);
    

    ContextImpl.init方法中的第一个参数来源即为performLaunchActivity中

    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
    	r.packageInfo = getPackageInfo(aInfo.applicationInfo,
    		Context.CONTEXT_INCLUDE_CODE);
    }
    

    即在performLaunchActiviy开始执行时,首先为r.packageInfo赋值,getpackageInfo方法的执行逻辑和getPackageInfoNoChecked()基本相同,所以,r.packageInfo对象的PackageInfo对象和Application对应的packageInfo对象是同一个。

    3. Service对应的Context

    启动Service时,AmS首先会通过IPC调用到ActivityThread的scheduleCreateService()方法,该方法包含两个参数。第一种是ServiceInfo,这是实现了一个Parcelable接口的类,意味着该对象由Ams创建,并通过IPC传递到ActivityThread内部,第二种是其他参数。

    public final void scheduleCreateService(IBinder token, ServiceInfo info) {
    	CreateServiceData s = new CreateServiceData(); // ......
    	s.token = token;
    	s.info = info;
    	queueOrSendMessage(H.CREATE_SERVICE, s);
    }
    

    在scheduleCreateService()方法中,会创建一个CreateServiceData的数据对象,ActivityThread会为其所包含的每一个Service创建该数据对象,并通过这些对象来管理Service。

    接着,会执行handleCreateService()方法,其中创建ContextImpl对象的代码如下:

    ContextImpl context = new ContextImpl();
    context.init(packageInfo, null, this);
    context.setOuterContext(agent);
    agent.attach(context);
    

    同样context.init()方法中packageInfo的赋值操作为handleCreateService方法中通过如下代码实现:

    LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo);
    

    赋值代码同样使用了getPackageInfoNoCheck()方法,意味着Service对应的Context对象内部的mPackageInfo与Activity、Application中是完全相同的。总结流程图如下:

    4. Context之间的关系

    从以上三节可以看出,创建Context对象的过程基本上是相同的,包括代码结构也很类似,不同的是针对Application、Activity、Service使用了不同的数据对象

    不同Context子类中PackageInfo的来源如上表

    一个应用程序中包含的Context个数为:Service个数 + Activity个数 + 1(Application类本身对应一个Context对象)

    应用程序中包含多个ContextImpl对象,而其内部变量mPackageInfo指向同一个PackageInfo对象,这种设计意味着ContextImpl是一种轻量级,而PackageInfo是一个重量级;ContextImpl中的大多数进行包操作的重量级函数实际上都转向了mPackageInfo对象相应的方法,即事实上是调用了同一个PackageInfo对象。

更多 推荐条目

Welcome to NowaMagic Academy!

现代魔法 推荐于 2013-02-27 10:23   

本章最新发布
随机专题
  1. [计算机算法] 从双端队列引出的卡特兰数 3 个条目
  2. [Python程序设计] Django模板系统 11 个条目
  3. [移动开发] Android加载器Loaders 5 个条目
  4. [Python程序设计] 从PHP到Python 3 个条目
  5. [PHP程序设计] PHP里的引用 5 个条目
  6. [Python程序设计] Django Web环境配置 2 个条目
  7. [PHP程序设计] PHP扩展模块安装 1 个条目
  8. [移动开发] Android属性系统Property 9 个条目
  9. [PHP程序设计] PHP数组的遍历 7 个条目
  10. [PHP程序设计] 声明式编程范式 12 个条目
  11. [数据库技术] 数据库范式篇 5 个条目
  12. [Linux操作系统] CentOS上使用EPEL Repository 2 个条目
窗口 -- [八点]