Context在开发Android应用的过程中扮演着非常重要的角色,比如启动一个Activity需要使用context.startActivity方法,将一个xml文件转换为一个View对象也需要使用Context对象,可以这么说,离开了这个类,Android开发寸步难行,对于这样一个类,我们又对他了解多少呢。我就说说我的感受吧,在刚开始学习Android开发时,感觉使用Context的地方一直就是传入一个Activity对象,久而久之感觉只要是Context的地方就传入一个Activity就行了,那么我们现在就来详细的分析一下Context和Activity的关系吧!
在开始本文之前我们先放置一个问题在这里:
我们平时在获取项目资源时使用context.getResources()的时候为什么放回的是同一个值,明明是使用不同的Activity调用getResources返回结果却是一样的。
Context本身是一个纯的abstract类,ContextWrapper是对Context的一个包装而已,它的内部包含了一个Context对象,其实对ContextWrapper的方法调用最终都是调用其中的Context对象完成的,至于ContextThremeWrapper,很明显和Theme有关,所以Activity从ContextThemmWrapper继承,而Service从ContextWrapper继承,ContextImpl是唯一一个真正实现了Context中方法的类。
从上面的继承关系来看,每一个Activity就是一个Context,每一个Service就是一个Context,这也就是为什么使用Context的地方可以被Activity或者Service替换了。
根据前面所说,由于实现了Context的只有ContextImpl类,Activity和Service本没有真正的实现,他们只是内部包含了一个真实的Context对象而已,也就是在在创建Activity或者Service的时候肯定要创建爱你一个ContextImpl对象,并赋值到Activity中的Context类型变量中。那我们就来看看Andorid源码中有哪些地方创建了ContextImpl.
据统计Android中创建ContextImpl的地方一共有7处:
在PackageInfo.makeApplication()中
在performLaunchActivity()中
在handleCreateBackupAgent()中
在handleCreateService()中
2次在hanldBinderAppplication()中
在attach()方法中
由于创建ContextImpl的基本原理类似,所以这里只会分析几个比较有代表性的地方:
1、 Application对应的Context
在应用程序启动时,都会创建一个Application对象,所以辗转调用到handleBindApplication()方法。
private final void handleBindApplication(AppBindData data) { mBoundApplication = data; mConfiguration = new Configuration(data.config); .... data.info = getPackageInfoNoCheck(data.appInfo); ... Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; .... }
其中data.info是LoadedApk类型的,到getPackageInfoNoCheck中看看源码
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) { return getPackageInfo(ai, null, false, true); }
里面其实调用的是getPackageInfo,继续跟进:
if (includeCode) { ref = mPackages.get(aInfo.packageName); } else { ref = mResourcePackages.get(aInfo.packageName); } LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null ? mBoundApplication.processName : null) + ")"); packageInfo = new LoadedApk(this, aInfo, this, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0); if (includeCode) { mPackages.put(aInfo.packageName, new WeakReference<LoadedApk>(packageInfo)); } else { mResourcePackages.put(aInfo.packageName, new WeakReference<LoadedApk>(packageInfo)); }
由于includeCode传入的是true,所以首先从mPackages中获取,如果没有,则new一个出来,并放入mPackages里面去,注意,这里的mPackages是ActivityThread中的属性。
下面继续分析一下LoadedApk这个类中的makeApplication函数
try { java.lang.ClassLoader cl = getClassLoader(); //创建一个ContextImpl对象 ContextImpl appContext = new ContextImpl(); appContext.init(this, null, mActivityThread); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { if (!mActivityThread.mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to instantiate application " + appClass + ": " + e.toString(), e); } } 这里创建了一个ContextImpl对象,并调用了它的init方法,现在进入init方法。 mPackageInfo = packageInfo; mResources = mPackageInfo.getResources(mainThread);
对mPackageInof和mResources两个变量初始化
回到makeApplication中,创建了一个Application对象,并将appContext传进去,其实就是将appContext传递给ContextWrapper中的Context类型变量(Application也是继承ContextWrapper)
2、Activity中的Context
在创建一个Activity时,经过辗转调用,会执行handleLaunchActivity(),然后调用performLaunchActivity(),该方法创建ContextImpl代码如下:
r.packageInfo= getPackageInfo(aInfo.applicationInfo,
Context.CONTEXT_INCLUDE_CODE);
ContextImplappContext = new ContextImpl();
appContext.init(r.packageInfo,r.token, this);
appContext.setOuterContext(activity);
activity.attach(appContext,this, getInstrumentation(), r.token,
r.ident, app, r.intent,r.activityInfo, title, r.parent,
r.embeddedID,r.lastNonConfigurationInstance,
r.lastNonConfigurationChildInstances, config);
由于getPackageInfo函数之前已经分析过了,稍微有点区别,但是大致流程是差不多的,所以此处的appContext执行init之后,其中的mPackages变量和mResources变量时一样的,activity通过attach函数将该appContext赋值到ContextWrapper中的Context类型变量
3、Service中的Context
同样 在创建一个Service时,经过辗转调用会调用到scheduleCreateService方法,之后会巧用handleCreateService
LoadedApkpackageInfo = getPackageInfoNoCheck(
data.info.applicationInfo);
ContextImplcontext = new ContextImpl();
context.init(packageInfo, null,this);
Application app =packageInfo.makeApplication(false, mInstrumentation);
context.setOuterContext(service);
service.attach(context, this,data.info.name, data.token, app,
ActivityManagerNative.getDefault());
其思路和上面两个基本一样,在此就不再详述。
在此总结一下:
(1)Context是一个抽象类,ContextWrapper是对Context的封装,它包含一个Context类型的变量,ContextWrapper的功能函数内部其实都是调用里面的Context类型变量完成的。Application,Service,Activity等都是直接或者间接继承自ContextWrapper,但是并没有真正的实现其中的功能,Application,Service,Activity中关于Context的功能都是通过其内部的Context类型变量完成的,而这个变量的真实对象必定是ContextImpl,所以没创建一个Application,Activity,Servcice便会创建一个ContextImpl,并且这些ContextImpl中的mPackages和mResources变量都是一样的,所以不管使用Acitivty还是Service调用getResources得到相同的结果
(2)在一个apk中,Context的数量等于Activity个数+Service个数+1.
最近在做一个项目,主要是跑后台的,界面就是弹几个浮动在窗口之上的对话框与用户交互,在起初时,为了调试方便就建立了一个Activity,在Activity中启动后台Service。后来项目将近结束时,需要捕捉开机以及有网络的广播来开启服务,随之加入Receiver,删除Activity!心想这个是不是很完美,perfect! 但是,但是!无论如何,我的service总是起不来,连Receiver也没有收到广播,这就怪了,为啥呢? 随即写了2个小demo,一个是只有一个Receiver捕捉有网络改变的常驻广播,另外一个是在第一个的基础上增加了一个Activity。试验证明,第一种情况是收不到广播的! 第二种情况是可以收到广播的。所以android程序中,不能只有一个Receiver组件,必须还要Activity。据说这是google对android应用程序安全的考虑,防止流氓软件潜水消耗资源,正所谓禁止潜水也!
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。
http://blog.csdn.net/lzhq1982/article/details/12905779
前一篇文章介绍了游戏开始场景的制作,可还没有任何交互,按理说,我的设计是点击界面然后直接到游戏场景,但看到了雨松的这篇文章--Unity3D研究院之异步加载游戏场景与异步加载游戏资源进度条后,决定尝试下用Loading界面异步加载游戏。
先看一下我的Loading界面:
像我这种小demo,从开始场景到游戏场景其实用Application.LoadLevel就可以了,但对于一个正常的游戏,新场景的资源加载往往会使你的游戏卡上一段时间,用户体验上就跟游戏死了一样,这时候Loading界面加上后台异步加载场景是你不错的。下面看看我是怎么做的。
1、首先是Loading场景的制作
这个相对简单,如上图,就是用了NGUI的Texture而已,需要注意的就是给它加了个Stretch,使屏幕分辨率自适应,不知道怎么弄的看我前一篇文章,这里不介绍NGUI怎么做的了。
2、异步加载游戏场景
最简单的Application.LoadLevel("SceneName")这种方式加载场景是同步的,如果用这种方法,新场景资源小还好,如果资源量多,那在加载资源时让你的游戏卡死,直到新场景的资源全部加载完成,这种方式对用户体验显然是不好的,最传统的做法都是开个线程,一个线程用来处理数据加载,一个线程用进度条或是动画的方式提示玩家游戏并未卡死,而是等待。但是Unity3d没有多线程的概念,不过unity也给我们提供了StartCoroutine(协同程序)和LoadLevelAsync(异步加载关卡)后台加载场景的方法。
StartCoroutine为什么叫协同程序呢,所谓协同,就是当你在StartCoroutine的函数体里处理一段代码时,利用yield语句等待执行结果,这期间不影响主程序的继续执行,可以协同工作。而LoadLevelAsync则允许你在后台加载新资源和场景,所以再利用协同,你就可以前台用loading条或动画提示玩家游戏未卡死,同时后台协同处理加载的事宜。想具体了解StartCoroutine可以看这里:MonoBehaviour.StartCoroutine
开始协同程序,想具体了解LoadLevelAsync可以看这里:Application.LoadLevelAsync 异步加载关卡。
3、利用单例类记录场景名称
有关单例脚本和单例类我前面的文章介绍过,这里就不说了,为什么要用到单例类呢,因为Loading脚本要知道下一个加载的是什么场景,而这是上一个场景告诉它的,场景切换数据就丢了,而单例类的数据会一直保留,所以要用单例类。看一下代码:
public class Global { public string loadName; private static Global instance; public static Global GetInstance() { if (instance == null) instance = new Global(); return instance; } }这里就一个变量loadName,负责记录场景名称。
4、A场景过渡到Loading场景
我前一篇文章讲了开始游戏场景的制作,但缺切换场景的过渡,假如它是A场景,我用触摸屏幕的方法让它过渡到Loading场景,然后再通过协同程序等待加载资源完成后自动过渡到B场景。先看A场景的代码:
public class OnPress : MonoBehaviour { // Use this for initialization public UICamera nguiCamera; void Start () { } // Update is called once per frame void Update () { if (Input.GetMouseButton(0)) { Ray ray = nguiCamera.camera.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { Global.GetInstance().loadName = "GameScene"; Application.LoadLevel("LoadingScene"); } } } }有关触摸的代码我不解释了,重要的就两句,一句“Global.GetInstance().loadName = "GameScene";”,这是告诉Loading场景下一个要加载的场景是GameScene,然后“Application.LoadLevel("LoadingScene");”开始切换场景到LoadingScene。
5、LoadingScene协同程序,后台加载新场景。
先看代码:
public class Loading : MonoBehaviour { void Start () { StartCoroutine(loadScene()); } IEnumerator loadScene() { AsyncOperation async = Application.LoadLevelAsync(Global.GetInstance().loadName); yield return async; } }
这是最简单的协同程序了,只是后台处理加载新场景而已,没有前台的事,这一篇先不讲前台的工作。代码很简单,不解释。因为Global.GetInstance().loadName = "GameScene",所以加载完就自动跳到GameScene这个场景了。
6、要切换场景,有个必要的工作没做。那就是在Unity里注册场景。
选File->Build Settings...,上面有个Scenes In Build,你要把你用到的场景都加到这个框里,程序才能正常调用,否则程序写了也无效。右下角有个Add Current的按钮,这是加载当前场景用的,你也可以直接把所有场景拖到这里。后面的数字是关卡ID,0是第一个要加载的场景。我的截图如下:
这样就可以实现最简单的Loading场景协同异步加载场景了,但本篇的协同异步其实没意义,因为我们的前台没做任何事情,下一篇让我们的前台别闲着,顺便看一下U3D如何利用XML加载游戏Tips。