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知识详解的更多相关文章

  1. Intent知识详解

    Intent知识详解 一.什么是Intent 贴一个官方解释: An intent is an abstract description of an operation to be performed ...

  2. RabbitMQ基础知识详解

    什么是MQ? MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取队列中 ...

  3. <context:component-scan>详解 转发 https://www.cnblogs.com/fightingcoding/p/component-scan.html

    <context:component-scan>详解   默认情况下,<context:component-scan>查找使用构造型(stereotype)注解所标注的类,如@ ...

  4. Cisco路由技术基础知识详解

    第一部分 请写出568A的线序(接触网络第一天就应该会的,只要你掐过,想都能想出来) .网卡MAC地址长度是(  )个二进制位(16进制与2进制的换算关系,只是换种方式问,不用你拿笔去算) A.12  ...

  5. L009文件属性知识详解小节

    本堂课分为5部分内容 1.linux下重要目录详解 2.PATH变量路径内容 3.linux系统中文件类型介绍 4.linux系统中文件属性详细介绍 5.linux系统文件属性inode与block知 ...

  6. RabbitMQ,Apache的ActiveMQ,阿里RocketMQ,Kafka,ZeroMQ,MetaMQ,Redis也可实现消息队列,RabbitMQ的应用场景以及基本原理介绍,RabbitMQ基础知识详解,RabbitMQ布曙

    消息队列及常见消息队列介绍 2017-10-10 09:35操作系统/客户端/人脸识别 一.消息队列(MQ)概述 消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以 ...

  7. 浏览器对象模型(BOM)是什么?(体系结构+知识详解)(图片:结构)

    浏览器对象模型(BOM)是什么?(体系结构+知识详解)(图片:结构) 一.总结 1.BOM操作所有和浏览器相关的东西:网页文档dom,历史记录,浏览器屏幕,浏览器信息,文档的地址url,页面的框架集. ...

  8. Golang Context 包详解

    Golang Context 包详解 0. 引言 在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务.同时,这个 goroutine 也 ...

  9. Python字符串切片操作知识详解

    Python字符串切片操作知识详解 这篇文章主要介绍了Python中字符串切片操作 的相关资料,需要的朋友可以参考下 一:取字符串中第几个字符 print "Hello"[0] 表 ...

随机推荐

  1. CoreDNS安装及集群验证

    目录 叙述 安装 测试 一 测试 二 CoreDNS 叙述 截止到目前为止,整个集群的核心组件已经安装完成. 此时集群内部还需要 CoreDNS 组件的支持. 安装 CoreDNS 是以 Pod 的形 ...

  2. selenium处理iframe和动作链

    selenium处理iframe和动作链 iframe iframe就是一个界面里嵌套了其他界面,这个时候selenium是不能从主界面找到子界面的属性,需要先找到子界面,再去找子界面的属性 动作链( ...

  3. js点击历史记录

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  4. 华为云MySQL金融版正式商用,高可靠的金融级数据库来了

    摘要:全新三节点架构,基于深度优化的MGR组复制技术,提供金融级的数据一致性. 日前,华为云数据库MySQL 金融版正式商业化发布,MySQL金融版采用MGR技术,基于Paxos协议,采用一主两备三节 ...

  5. 基于webpack实现多html页面开发框架四 自动写入多入口,自动插入多个htmlWebpackPlugin插件

    一.解决什么问题      1.手写页面多入口,一个一个输入太麻烦,通过代码实现      2.手写多个htmlWebpackPlugin插件太麻烦,通过代码实现 二.多入口代码实现 //读取所有.j ...

  6. .Net Core的API网关Ocelot使用 (一)

    1.什么是API网关 API网关是微服务架构中的唯一入口,它提供一个单独且统一的API入口用于访问内部一个或多个API.它可以具有身份验证,监控,负载均衡,缓存,请求分片与管理,静态响应处理等.API ...

  7. postman+newman+html测试报告(接口自动化)

    1.安装node.js(Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境) 下载安装node.js,下载地址:https://nodejs.org/en/ 2.安 ...

  8. Spring源码学习笔记之基于ClassPathXmlApplicationContext进行bean标签解析

    bean 标签在spring的配置文件中, 是非常重要的一个标签, 即便现在boot项目比较流行, 但是还是有必要理解bean标签的解析流程,有助于我们进行 基于注解配置, 也知道各个标签的作用,以及 ...

  9. Docker学习-环境搭建

    ChuanGoing 2019-12-15 本篇是DevOps Docker介绍第一篇,首先说下为何另开一篇来讲解本系列. 原因有二: 1.重新复习下个人对于DevOps/Docker的学习之路 2. ...

  10. 【JS】304- KOA2框架原理解析和实现

    ); , () => {     ); 实现koa的第一步就是对以上的这个过程进行封装,为此我们需要创建application.js实现一个Application类的构造函数: ); , () ...