1.3Context设计思想
目录介绍
- 01.Content的思考
- 02.Activity可以new创建吗
- 03.Context到底是什么
- 04.生动形象的理解Context
- 05.Context能干什么
- 06.Context作用域
- 07.如何获取Context
- 08.正确使用Context
- 09.App进程中Context个数
- 10.Application的Context分析
- 11.Activity的Context分析
01.Content的思考
Context
是什么?Context
是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。
Context
有哪些类型,分别作用是什么?Context
下有两个子类,ContextWrapper
是上下文功能的封装类,而ContextImpl
则是上下文功能的实现类。ContextWrapper
又有三个直接的子类,ContextThemeWrapper
、Service
和Application
。其中,ContextThemeWrapper
是一个带主题的封装类,而它有一个直接子类就是Activity
,所以Activity
和Service
以及Application
的Context是不一样的,只有Activity需要主题,Service不需要主题。
Context
下有哪些子类,主要是干什么的?- Context一共有三种类型,分别是
Application
、Activity
和Service
。 - 这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体
Context
的功能则是由ContextImpl
类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。 - 不过有几种场景比较特殊,比如启动
Activity
,还有弹出Dialog
。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity
的基础之上,也就是以此形成的返回栈。而Dialog
则必须在一个Activity
上面弹出(除非是系统级别吐司),因此在这种场景下,我们只能使用Activity
类型的Context
,否则将会出错。
- Context一共有三种类型,分别是
02.Activity可以new创建吗
- Activity mActivity =new Activity()
- 不能
- 作为
Android
开发者,不知道你有没有思考过这个问题,Activity
可以new
吗?- Android的应用程序开发采用JAVA语言,Activity本质上也是一个对象,那上面的写法有什么问题呢?
- 估计很多人说不清道不明。Android程序不像Java程序一样,随便创建一个类,写个main()方法就能运行。
Android
应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境。- 在这个环境下,
Activity
、Service
等系统组件才能够正常工作,而这些组件并不能采用普通的Java对象创建方式,new一下就能创建实例了,而是要有它们各自的上下文环境,也就是我们这里讨论的Context。 - 可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。
03.Context到底是什么
3.1 Context上下文
Context
的中文翻译为:语境;上下文;背景;环境。- 在开发中我们经常说称之为“上下文”,那么这个“上下文”到底是指什么意思呢?
- 在语文中,我们可以理解为语境,在程序中,我们可以理解为当前对象在程序中所处的一个环境,一个与系统交互的过程。比如微信聊天,此时的“环境”是指聊天的界面以及相关的数据请求与传输,Context在加载资源、启动Activity、获取系统服务、创建View等操作都要参与。
- 那
Context
到底是什么呢?- 一个
Activity
就是一个Context
,一个Service
也是一个Context
。Android程序员把“场景”抽象为Context类,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是一个有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service)。 - 一个应用程序可以认为是一个工作环境,用户在这个环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可以称之为一个应用程序。
- 一个
Context
是一个抽象类,那么肯定有他的实现类咯,我们在Context的源码中通过IDE可以查看到他的子类最终可以得到如下关系图:
3.2 Context两个子类
Context
类本身是一个纯abstract
类,它有两个具体的实现子类:- ContextImpl和ContextWrapper。
- 其中
ContextWrapper
类,如其名所言,这只是一个包装而已- ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。
- ContextThemeWrapper类,如其名所言,其内部包含了与主题(Theme)相关的接口这里所说的主题就是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapper,Application同理。
ContextImpl
类则真正实现了Context
中的所有函数- 应用程序中所调用的各种Context类的方法,其实现均来自于该类。一句话总结:**Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。
- **Activity,Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建
ContextImpl
对象,由ContextImpl
实现Context
中的方法。
04.生动形象的理解Context
- 将
Context
理解为“上下文”或者“场景”,如果你仍然觉得很抽象,不好理解。- 在这里我给出一个可能不是很恰当的比喻,希望有助于大家的理解:一个Android应用程序,可以理解为一部电影或者一部电视剧,Activity,Service,Broadcast Receiver,Content Provider这四大组件就好比是这部戏里的四个主角:胡歌,霍建华,诗诗,Baby。
- 他们是由剧组(系统)一开始就定好了的,整部戏就是由这四位主演领衔担纲的,所以这四位主角并不是大街上随随便便拉个人(new 一个对象)都能演的。
- 有了演员当然也得有摄像机拍摄啊,他们必须通过镜头(Context)才能将戏传递给观众,这也就正对应说四大组件(四位主角)必须工作在Context环境下(摄像机镜头)。
- 那Button,TextView,LinearLayout这些控件呢,就好比是这部戏里的配角或者说群众演员,他们显然没有这么重用,随便一个路人甲路人乙都能演(可以new一个对象),但是他们也必须要面对镜头(工作在Context环境下),所以
Button mButton=new Button(Context)
是可以的。 - 虽然不很恰当,但还是很容易理解的,希望有帮助。
05.Context能干什么
Context
到底可以实现哪些功能呢?- 这个就实在是太多了,弹出
Toast
、启动Activity
、启动Service
、发送广播、操作数据库等等都需要用到Context
。
TextView tv = new TextView(getContext()); ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...); AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);getApplicationContext().getSharedPreferences(name, mode); getApplicationContext().getContentResolver().query(uri, ...); getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8; getContext().startActivity(intent); getContext().startService(intent); getContext().sendBroadcast(intent);
- 这个就实在是太多了,弹出
06.Context作用域
- 虽然Context神通广大,但并不是随便拿到一个Context实例就可以为所欲为,它的使用还是有一些规则限制的。由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。
- 出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
- 从上图我们可以发现Activity所持有的Context的作用域最广,无所不能。因为Activity继承自ContextThemeWrapper,而Application和Service继承自ContextWrapper,很显然ContextThemeWrapper在ContextWrapper的基础上又做了一些操作使得Activity变得更强大,这里我就不再贴源码给大家分析了,有兴趣的童鞋可以自己查查源码。上图中的YES和NO我也不再做过多的解释了。
- 这里我说一下上图中Application和Service所不推荐的两种使用情况。
- 1.如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错
android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
- 这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用,Service同Application。
- 2.在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。
- 1.如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错
- 一句话总结:
- 凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。
07.如何获取Context
- 通常我们想要获取Context对象,主要有以下四种方法
- 1.View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
- 2.Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
- 3.ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
- 4.Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。
- getApplication()和getApplicationContext()
- 上面说到获取当前Application对象用getApplicationContext,不知道你有没有联想到getApplication(),这两个方法有什么区别?相信这个问题会难倒不少开发者。
- 程序是不会骗人的,我们通过上面的代码,打印得出两者的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,因为前面已经说过了,Application本身就是一个Context,所以这里获取getApplicationContext()得到的结果就是Application本身的实例。
- 那么问题来了,既然这两个方法得到的结果都是相同的,那么Android为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了。
public class MyReceiver extends BroadcastReceiver{ @Override public void onReceive(Contextcontext,Intentintent){ Application myApp= (Application)context.getApplicationContext(); } }
08.正确使用Context
- 一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:
- 1.当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
- 2.不要让生命周期长于Activity的对象持有到Activity的引用。
- 3.尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
09.App进程中Context个数
- App进程中Context个数
- Application是Context,Activity是Context,Service也是Context,所以有一个经典的问题是一个App中一共有多少个Context?
- 应该是Application + N个Activity + N个Service。
10.Application的Context分析
- 首先就从
ActivityThread.main
方法开始看一下Application
的创建流程。public static void main(String[] args) { ActivityThread thread = new ActivityThread(); thread.attach(false); }
- 这里我们发现在方法体中我们创建了一个
ActivityThread
对象并执行了attach
方法:- 这里看一下重点实现,我们可以发现在方法体中调用了
ActivityManagerNative.getDefault().attachApplication(mAppThread)
,这里就是一个Binder
进程间通讯,其实上执行的是ActivityManagerService.attachApplication
方法
private void attach(boolean system) { if (!system) { RuntimeInit.setApplicationObject(mAppThread.asBinder()); final IActivityManager mgr = ActivityManagerNative.getDefault(); try { mgr.attachApplication(mAppThread); } catch (RemoteException ex) { } } }
- 这里看一下重点实现,我们可以发现在方法体中调用了
- 看一下
ActivityManagerService.attachApplication
方法的具体实现。@Override public final void attachApplication(IApplicationThread thread) { synchronized (this) { int callingPid = Binder.getCallingPid(); final long origId = Binder.clearCallingIdentity(); attachApplicationLocked(thread, callingPid); } }
- 然后这里面又调用了
attachApplicationLocked
方法:private final boolean attachApplicationLocked(IApplicationThread thread, int pid) { thread.bindApplication(processName, appInfo, providers, app.instrumentationClass, profilerInfo, app.instrumentationArguments, app.instrumentationWatcher, }
- 来看一下
ApplicationThread
的bindApplication
的实现:public final void bindApplication() { AppBindData data = new AppBindData(); data.processName = processName; data.appInfo = appInfo; sendMessage(H.BIND_APPLICATION, data); }
- 最后调用了
ActivityThread.sendMessage()
做了什么private void sendMessage(int what, Object obj) { sendMessage(what, obj, 0, 0, false); }
- 看一下其
sendMessage
的重载方法:private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) { Message msg = Message.obtain(); msg.what = what; msg.obj = obj; msg.arg1 = arg1; msg.arg2 = arg2; if (async) { msg.setAsynchronous(true); } mH.sendMessage(msg); }
- 可以发现这里调用了
mH
的sendMessage
方法- 最后通过
Handler
的异步消息机制被mH的handleMessage
方法处理,然后根据Message.what
选择处理分支,最终调用了ActivityThread
的handleBindApplication
方法。 - 这个方法的方法体比较长,我们挑重点的看,可以看到方法体中系统通过反射机制创建了
Instrumentation
对象,并执行了init
方法,执行了Insrtumentation
对象的初始化。
private void handleBindApplication(AppBindData data) { ... // 创建Instrumentation if (data.instrumentationName != null) { try { java.lang.ClassLoader cl = instrContext.getClassLoader(); mInstrumentation = (Instrumentation) cl.loadClass(data.instrumentationName.getClassName()).newInstance(); } catch (Exception e) { } mInstrumentation.init(this, instrContext, appContext, new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher, data.instrumentationUiAutomationConnection); } else { mInstrumentation = new Instrumentation(); } }
- 最后通过
- 然后我们调用了
LockedApk.makeApplication
方法创建了Application
对象,我们来看一下其具体的实现逻辑:- 可以发现这里也是以反射的机制创建了
Application
对象,并创建了一个ContextImpl
对象,并将Application
与ContextImpl
建立关联。。。
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { try { //将`Application`与`ContextImpl`建立关联 ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { } mActivityThread.mAllApplications.add(app); mApplication = app; if (instrumentation != null) { try { //执行application的OnCreate生命周期 instrumentation.callApplicationOnCreate(app); } catch (Exception e) { } } return app; }
- 可以发现这里也是以反射的机制创建了
- 继续回到我们的
ActivityThread
的handleBindApplication
方法,然后定位到LockedApk.makeApplication
- 在创建了
Application
对象之后我们调用了Instrumentation
的onCreate
方法,然后调用了Instrumentation
的callApplicationOnCreate
方法,我们来看一下其具体实现:
public void callApplicationOnCreate(Application app) { app.onCreate(); }
- 原来Application的onCreate生命周期方法是在这里回调滴啊。这样我们整个Application的创建执行流程就讲解完了。
- 在创建了
- 总结:
- 应用进程启动 --> 创建Instrumentation --> 创建Application对象 --> 创建Application相关的ContextImpl对象;
- ActivityThread.main方法--> ActivityManagerService.bindApplication方法 --> ActivityThread.handleBindApplication --> 创建Instrumentation,创建Application;
- 每个应用进程对应一个Instrumentation,对应一个Application;
- Instrumentation与Application都是通过java反射机制创建;
- Application创建过程中会同时创建一个ContextImpl对象,并建立关联;
11.Activity的Context分析
Activity
的具体创建过程是在ActivityThread
的performLaunchActivity
,来看一下- 这里简要说明一下,
Activity
也是系统通过反射机制创建的,然后我们通过LockedApk.makeApplication
创建一个Application
- 通过查看源码我们知道若这时候
LockedApk
中的mApplication
不为空则直接返回当前的mApplication
又因为当我们创建应用进程的时候Application
已经被创建,所以当创建Activity
的时候这时候Application
肯定不为空,所以这时候返回的就是应用进程创建的时候创建的Application
,这也从侧面说明了一个应用进程对应着一个Application。 - 然后我们通过
createBaseContextForActivity
创建了一个ContextImpl对象。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); } catch (Exception e) { } try { Application app = r.packageInfo.makeApplication(false, mInstrumentation); ... if (activity != null) { Context appContext = createBaseContextForActivity(r, activity); //调用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
方法的源码- 可以发现这里创建了一个
ContextImpl
对象,并通过ContextImpl
的setOuterContext
方法,让该ContextImpl
持有了Activity
的引用
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) { ContextImpl appContext = ContextImpl.createActivityContext( this, r.packageInfo, displayId, r.overrideConfig); appContext.setOuterContext(activity); Context baseContext = appContext; return baseContext; }
- 可以发现这里创建了一个
- 继续往下看,调用了
activity.attach
方法,查看一下该方法的实现逻辑:final void attach(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) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this); mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }
- 除了一下初始化操作之外,还调用了
attachBaseContext
方法,让Activity
持有了ContextImpl
的引用,这样就相当于Activity
与ContextImpl
对象相互持有了对方的引用,并且Activity
是继承与Context
。
- 除了一下初始化操作之外,还调用了
- 总结:
Activity
中创建ContextImpl
对象的具体实现在ActivityThread
的performLauncherActivity
方法中;Activity
的创建伴随着ContextImpl
的创建,二者相互持有对方的引用;- 创建
Activity
--> 创建Activity
相关ContextImpl
对象; - 创建应用进程 --> 创建
Application
--> 创建Application
相关ContextImpl
对象;
其他介绍
01.关于博客汇总链接
02.关于我的博客
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yczbj/activities
- 简书:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
- 开源中国:https://my.oschina.net/zbj1618/blog
- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 邮箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e