Context知识详解
Context知识详解
建议配合context知识架构图食用。
一、什么是Context
贴一个官方解释:
Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.
上面的意思:context是一个应用程序环境的全局信息的接口。这是一个抽象类,其实现由Android系统提供。它允许访问特定于应用程序的资源和类,以及对应用程序级操作(如启动活动,广播和接收意图等)的调用。
这个解释可能听起来比较抽象,我的理解是一些Android组件(如activity、service)的运行需要一定的“环境”,就好像我们工作一般都是在办公室 ,休息则是在家里,我们都是处在一定的“环境”下去工作、学习、休息的,Android组件也是类似,它们不能脱离“环境”去运转,而这个“环境”在Android中就是context。
二、Context子类以及其继承关系
先贴个图
由图我们可以看出context有两个子类ContextImpl和ContextWrapper。
ContextWrapper
我们先来看下ContextWrapper。
*/***
* * Proxying implementation of Context that simply delegates all of its calls to*
* * another Context. Can be subclassed to modify behavior without changing*
* * the original Context.*
* */*
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
*/***
* * Set the base context for this ContextWrapper. All calls will then be*
* * delegated to the base context. Throws*
* * IllegalStateException if a base context has already been set.*
* * *
* ****@param***base The new base context for this wrapper.*
* */*
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException(“Base context already set”);
}
mBase = base;
}
*/***
* ****@return***the base context as set by the constructor or setBaseContext*
* */*
public Context getBaseContext() {
return mBase;
}
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources() {
return mBase.getResources();
}
@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
@Override
public Looper getMainLooper() {
return mBase.getMainLooper();
}
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
@Override
public void setTheme(int resid) {
mBase.setTheme(resid);
}
*/*****@hide****/*
@Override
public int getThemeResId() {
return mBase.getThemeResId();
}
@Override
public Resources.Theme getTheme() {
return mBase.getTheme();
}
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
@Override
public void sendBroadcast(Intent intent) {
mBase.sendBroadcast(intent);
}
//...
}
该类直接继承自Context,并实现了Context定义的抽象方法。不过我们看源码发现其实它并未实质的去实现Context定义的操作只是通过mBase调用对应的方法去执行。这个mBase也是一个Context类型的变量,它的赋值是通过attachBaseContext赋值的。我们还知道service和application都是ContextWrapper子类,所以service和application都是Context。
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
//...
}
public class Application extends ContextWrapper implements ComponentCallbacks2 {
//...
}
ContextWrapper还有一个子类ContextThemeWrapper。
public class ContextThemeWrapper extends ContextWrapper {
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
super(base);
mThemeResource = themeResId;
}
public ContextThemeWrapper(Context base, Resources.Theme theme) {
super(base);
mTheme = theme;
}
@Override
public Resources getResources() {
return getResourcesInternal();
}
private Resources getResourcesInternal() {
if (mResources == null) {
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else if (Build.VERSION.SDK_INT >= 17) {
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
@Override
public void setTheme(int resid) {
if (mThemeResource != resid) {
mThemeResource = resid;
initializeTheme();
}
}
public int getThemeResId() {
return mThemeResource;
}
@Override
public Resources.Theme getTheme() {
if (mTheme != null) {
return mTheme;
}
if (mThemeResource == 0) {
mThemeResource = R.style.Theme_AppCompat_Light;
}
initializeTheme();
return mTheme;
}
private void initializeTheme() {
final boolean first = mTheme == null;
if (first) {
mTheme = getResources().newTheme();
Resources.Theme theme = getBaseContext().getTheme();
if (theme != null) {
mTheme.setTo(theme);
}
}
onApplyThemeResource(mTheme, mThemeResource, first);
}
//...
}
可以看出ContextThemeWrapper主要是包含了主题Theme相关的接口,即android:theme属性指定的。而activity则是继承自ContextThemeWrapper。
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
//...
}
ContextImpl
由ContextWrapper源码我们知道实际上它并没有实现Context定义的相关操作。那么Context的真实实现类到底是谁呢 答案就是ContextImpl。它是Android系统提供的唯一的Context真实 实现类。
class ContextImpl extends Context {
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
@Override
public void sendBroadcast(Intent intent) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return registerReceiver(receiver, filter, null, null);
}
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}
//...
}
由源码看出ContextImpl确是是真实的实现了Context。
三、一个应用Context个数
通过上面Context子类继承关系的分析,一个应用Context个数显而易见。
APP Context总数 = Application(1) + Activity个数+ Service个数;
四、不同的Context之间差异
我们知道Application的生命周期跟应用的生命周期是相同的,所以Application的Context生命周期与应用程序完全相同。同理
Activity或者Service的Context与他们各自类生命周期相同。
由此可知Context使用不当会引起内存泄漏,我们在使用Context时必须要注意其生命周期。
尽量使用 Application 的 Context
不要让生命周期长于 Activity 的对象持有其的引用
尽量不要在 Activity 中使用非静态内部类,因为非静态内部类会隐式持有外部类示例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
五、不同Context的应用场景
大家注意看到有一些NO上添加了一些数字,其实这些从能力上来说是YES,但是为什么说是NO呢?下面一个一个解释:
数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。
数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)
注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个context用于使用。
以上参考https://blog.csdn.net/lmj623565791/article/details/40481055
由表格我们可以归纳出这样一个结论:操作涉及UI的应该使用Activity做为Context,不涉及UI的Service,Activity,Application等实例都可以。
六、不同Context实例化过程
Activity 中Context实例化过程
在Activity的启动过程中,activity的创建是在ActivityThread.
performLaunchActivity方法中完成的。
//ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r,Intent customIntent){
//...
ContextImpl appContext=createBaseContextForActivity(r);//1、创建ContextImpl实例
Activity activity=null;
try{
java.lang.ClassLoader cl=appContext.getClassLoader();
//...
activity=mInstrumentation.newActivity(
cl,component.getClassName(),r.intent);//2、创建Activity
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if(r.state!=null){
r.state.setClassLoader(cl);
}
}catch(Exception e){
if(!mInstrumentation.onException(activity,e)){
throw new RuntimeException(
"Unable to instantiate activity "+component
+": "+e.toString(),e);
}
}
try{
Application app=r.packageInfo.makeApplication(false,mInstrumentation);
if(activity!=null){
appContext.setOuterContext(activity);//3、调用setOuterContext
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,window,r.configCallback);//4、调用attach
}
//...
}
首先通过createBaseContextForActivity创建ContextImpl实例,那我们看下具体是如何创建的
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
final int displayId;
try {
displayId = ActivityManager.getService().getActivityDisplayId(r.token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
//...
return appContext;
}
可以看出是调用createActivityContext,那来看下createActivityContext
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
//...
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader);
//...
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
}
可以看到是调用了ContextImpl得一个构造函数创建的ContextImpl实例然后还给该实例设置了setResources,至此ContextImpl创建完成。但是我们注意到在创建了ContextImpl实例(appContext)之后又调用了setOuterContext
并把当前activity传入,这又是为什么呢? 看下源码
private Context mOuterContext;
final void setOuterContext(Context context) {
mOuterContext = context;
}
setOuterContext只是简单的把传入的activity赋值给了mOuterContext,这是ContextImpl类中定义的一个变量。通过这个操作ContextImpl就可以持有activity的引用。
setOuterContext之后又调用了activity.attach并把appContext传入。
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,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
//...
}
//Activity.java
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
newBase.setAutofillClient(this);
}
Activity的attach我们只关注跟context有关的 那就是调用attachBaseContext,在这个函数内部调用了super.attachBaseContext。我们知道Activity继承自 ContextThemeWrapper, ContextThemeWrapper
继承自 ContextWrapper,所以最终会调用ContextWrapper.attachBaseContext,到这里,ContextWrapper类就可以将它的功能交给ContextImpl类来具体实现。
//ContextWrapper.java
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
Service中Context实例化过程
private void handleCreateService(CreateServiceData data){
//...
Service service=null;
try{
java.lang.ClassLoader cl=packageInfo.getClassLoader();
service=(Service)cl.loadClass(data.info.name).newInstance();//1、创建service
}catch(Exception e){
if(!mInstrumentation.onException(service,e)){
throw new RuntimeException(
"Unable to instantiate service "+data.info.name
+": "+e.toString(),e);
}
}
try{
if(localLOGV)Slog.v(TAG,"Creating service "+data.info.name);
ContextImpl context=ContextImpl.createAppContext(this,packageInfo);//2、创建ContextImpl实例
context.setOuterContext(service);//3、设置OuterContext
Application app=packageInfo.makeApplication(false,mInstrumentation);
service.attach(context,this,data.info.name,data.token,app,
ActivityManager.getService()); //4、调用attach
service.onCreate();
mServices.put(data.token,service);
try{
ActivityManager.getService().serviceDoneExecuting(
data.token,SERVICE_DONE_EXECUTING_ANON,0,0);
}catch(RemoteException e){
throw e.rethrowFromSystemServer();
}
}catch(Exception e){
if(!mInstrumentation.onException(service,e)){
throw new RuntimeException(
"Unable to create service "+data.info.name
+": "+e.toString(),e);
}
}
}
我们看到Service中Context实例的创建流程跟Activity基本是一样的,首先创建Service实例然后创建ContextImpl实例,之后调用setOuterContext最后是attach。
Service中ContextImpl实例是通过函数createAppContext创建的,其内部则是通过ContextImpl的构造函数来创建实例。
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null);
context.setResources(packageInfo.getResources());
return context;
}
setOuterContext操作跟Activity是一样的都是把引用赋值给mOuterContext。
最后就是attch了,下面是service的attach,可以看到它也是调用attachBaseContext,下面的流程跟Activity是一样的最终都是ContextWrapper类将它的功能交给ContextImpl类来具体实现。
public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {
attachBaseContext(context);//调用attachBaseContext
mThread = thread; // NOTE: unused - remove?
mClassName = className;
mToken = token;
mApplication = application;
mActivityManager = (IActivityManager)activityManager;
mStartCompatibility = getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.ECLAIR;
}
Application中的Context的实例化过程
Application 的创建是在LoadedApk.makeApplication中。
//LoadedApk.Java
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
//...
Application app = null;
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);//1、创建ContextImpl实例
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);//2、创建application
appContext.setOuterContext(app);//3、设置mOuterContext
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
//...
}
可以看到Application中是先创建了ContextImpl实例然后创建Application实例最后调用了setOuterContext。看上去跟Service和Activity相比缺少了attach,而我们知道attach是ContextWrapper类将它的功能交给ContextImpl类来具体实现的过程,Application缺少attach那它是如何实现ContextWrapper的代理过程的呢? 其实Application是有attach的 它在newApplication创建Application的过程中调用的。
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return newApplication(cl.loadClass(className), context);
}
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);//调用application的attach方法
return app;
}
final void attach(Context context) {
attachBaseContext(context); //调用attachBaseContext
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
嗯,这样看application和Service还有Activity的流程基本上是一致的。
至此Application、Service、Activity中context的实例化过程都已分析完了。
七、其他
无侵入式获取全局Context
使用一个ContentProvider,ContentProvider的onCreate()方法调用时,调用getContext()即可获取到Context,再静态变量保存,后续直接获取即可。
public class AppContextProvider extends ContentProvider {
static Context mContext;
@Override
public boolean onCreate() {
//mContext保存为静态变量
mContext = getContext();
return false;
}
//...
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.app.contextprovider">
<application>
<!-- 全局Context提供者 -->
<provider
android:name=".AppContextProvider"
android:authorities="${applicationId}.contextprovider"
android:exported="false" />
</application>
</manifest>
getApplication和getApplicationContext的区别
首先来看getApplication方法,它只有在Activity和Service中有实现
Activity
/** Return the application that owns this activity. */
public final Application getApplication() {
return mApplication;
}
Service
/** Return the application that owns this service. */
public final Application getApplication() {
return mApplication;
}
Activity和Service中getApplication返回的是一个application对象。
getApplicationContext是ContextWrapper提供的方法,由源码可知它调用的是mBase的getApplicationContext()。此处的mBase实际是一个ContextImpl,所以我们看下ContextImpl的getApplicationContext(),可以看到返回的是mPackageInfo.getApplication()(此处的mPackageInfo包含当前应用的包信息、比如包名、应用的安装目录等信息,一般不为空)。
//ContextWrapper
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
//ContextImpl
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
我们知道一个应用只有一个Application所以getApplication和getApplicationContext 实际上都是返回当前应用的Application,它们是同一个对象。这两个函数的区别就是getApplication只能在Activity和Service中调用,而getApplicationContext 的使用范围则要大一些,比如在广播中想要获取全局的Context则需要使用getApplicationContext 而不是getApplication。
以上就是Context相关知识点的整理解析。
本文所有源码基于Android-8.0.0_r1
Android
Context知识详解的更多相关文章
- Intent知识详解
Intent知识详解 一.什么是Intent 贴一个官方解释: An intent is an abstract description of an operation to be performed ...
- RabbitMQ基础知识详解
什么是MQ? MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取队列中 ...
- <context:component-scan>详解 转发 https://www.cnblogs.com/fightingcoding/p/component-scan.html
<context:component-scan>详解 默认情况下,<context:component-scan>查找使用构造型(stereotype)注解所标注的类,如@ ...
- Cisco路由技术基础知识详解
第一部分 请写出568A的线序(接触网络第一天就应该会的,只要你掐过,想都能想出来) .网卡MAC地址长度是( )个二进制位(16进制与2进制的换算关系,只是换种方式问,不用你拿笔去算) A.12 ...
- L009文件属性知识详解小节
本堂课分为5部分内容 1.linux下重要目录详解 2.PATH变量路径内容 3.linux系统中文件类型介绍 4.linux系统中文件属性详细介绍 5.linux系统文件属性inode与block知 ...
- RabbitMQ,Apache的ActiveMQ,阿里RocketMQ,Kafka,ZeroMQ,MetaMQ,Redis也可实现消息队列,RabbitMQ的应用场景以及基本原理介绍,RabbitMQ基础知识详解,RabbitMQ布曙
消息队列及常见消息队列介绍 2017-10-10 09:35操作系统/客户端/人脸识别 一.消息队列(MQ)概述 消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以 ...
- 浏览器对象模型(BOM)是什么?(体系结构+知识详解)(图片:结构)
浏览器对象模型(BOM)是什么?(体系结构+知识详解)(图片:结构) 一.总结 1.BOM操作所有和浏览器相关的东西:网页文档dom,历史记录,浏览器屏幕,浏览器信息,文档的地址url,页面的框架集. ...
- Golang Context 包详解
Golang Context 包详解 0. 引言 在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务.同时,这个 goroutine 也 ...
- Python字符串切片操作知识详解
Python字符串切片操作知识详解 这篇文章主要介绍了Python中字符串切片操作 的相关资料,需要的朋友可以参考下 一:取字符串中第几个字符 print "Hello"[0] 表 ...
随机推荐
- MySQL5.6.36 自动化安装脚本
背景 很好的朋友邱启明同学,擅长MySQL,目前任职某大型互联网业MySQL DBA,要来一套MySQL自动安装的Shell脚本,贴出来保存一些. 此版本为 MySQL 5.6.365 ###### ...
- python1:基础数据类型(上)
https://www.geekdigging.com/2019/10/13/2870915864/ 1.数字 在python的数字有4钟数据类型,分别是: int(有符号整型) long(长整型) ...
- 技术谈 | SDN 和 NFV 之间的爱与恨
部分开发者经常混淆 SDN 和 NFV,无法看清他们的关系.今天,小编搬出华为技术专家的一篇大稿,给大家掰扯掰扯:SDN 和 NFV 究竟是什么关系. ----文/闫长江 什么是 SDN 回到基本的概 ...
- shell 脚本运行 hive sql
#!/b START=$(date +%s); datebegin=`date -d "$1" "+%Y%m%d"` dateend=`date -d &quo ...
- Xcode中.a文件引起的错误
一. TARGETS -> Build Settings-> Search Paths下 1. Library Search Paths 删除不存在的路径,保留.a文件的路径(此 ...
- 使用 Zookeeper 的 Api 实现服务订阅
服务提供者 服务提供者中拷贝 Constants 服务提供者中拷贝 UserService 连接 Zk 集群 /*** * 连接ZooKeeper集群 */ public ZooKeeper conn ...
- 小程序使用wxs解决wxml保留2位小数问题
1.出现溢出表现 从图中可以看到数字超出了很长长度.代码里面是如下这样的.为什么在0.35出现?或者一些相成的计算出现? 而 0.34却不会.(wap.0834jl.com) 0.41 也会出现,好像 ...
- 基于iCamera测试mt9m034 1280X960 高动态相机模块小结
基于iCamera测试mt9m034 高动态相机模块小结 首先看看此模块的特性 mt9m034 高动态 CMOS模块 1280*960像素 5.48 V/lux-sec >115db 摄像头模块 ...
- 【全栈修炼】OAuth2 修炼宝典
一.OAuth 概念 开放授权(OAuth)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. -- 维基百 ...
- 为什么HashMap的加载因子是0.75?
说在前面 在HashMap中,默认创建的数组长度是16,也就是哈希桶个数为16,当添加key-value的时候,会先计算出他们的哈希值(h = hash),然后用return h & (l ...