理解android中最熟悉的Context

Context的介绍

在这里插入图片描述
Context 在Android开发中几乎无处不在,对于开发来说实在是再熟悉不过了。但是你真的了解它吗?是否在使用的时候分不清楚呢?并且可能你的一不小心就会导致内存泄漏。

由于Android中存在不同类型的Context,因此作为Android开发,我们可能刚开始不知道在某个位置使用哪个上下文。所以,我们看看下面是如何正确使用Context的。

其中android主要有两种类型的上下文:

Application Context:这是一个单例,可以在activity中使用getApplicationContext()来获取,它与应用程序的生命周期相关。

Activity Context:这是Activity。例如-MainActivity。它仅是MainActivity的实例。

Context提供了关于应用环境全局信息的接口。它是一个abstract类,它的执行被Android系统提供,允许获取以应用为特征的资源和类型,是一个统领一些资源APP环境变量等的上下文(包括应用级别操作,如启动Activity,发广播,接收intent等)。abstract会有它的实现类。在源码中,我们可以通过AndroidStudio去查看它的子类,得到以下关系:
它有2个具体实现子类:ContextImpl、ContextWrapper。
在这里插入图片描述
其中ContextImpl是Context的具体是实现类,而ContextWrapper则是Context的包装类。

Activity、Application、Service都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们的初始化过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。

在app中有多少个Context

Context数量 = Activity数量 + Service数量 + 1

上面的1代表着Application的数量,因为一个应用程序中可以有多个Activity和多个Service,但是只能有一个Application。

Context 的作用域

不是随便获取一个Context实例就可以的,它的使用有一些规则和限制。因为Context的具体实例是由ContextImpl类去实现的,因此,Activity、Service、Application 3种类型的Context都是等价的。但是,需要注意的是,,有些场景,比如启动Activity、弹出Dialog等。为了安全,Android不允许Activity或者Dialog凭空出现,一个Activity的启动肯定是由另一个Activity负责的,也就是以此形成的返回栈,而Dialog 则必须是在一个Activity上弹出(系统Alert类型的Dialog除外),这种情况下, 我们只能用Activity类型的Context,否则报错。

Activity继承自ContextThemeWrapper,而Application和Service继承ContextWrapper,所以ContextThemeWrapperContextWrapper的基础上作了一些操作,使得Activity更加牛逼。

在这里插入图片描述
关于表格中提到的Application和Service不推荐的2种情况:

如果用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错:

androud,util.AndroidRuntimeException:Calling startActivity from outside of an Activity context require the FLAG_ACTIVITY_NEW_TASK flag。Is this really what you want?

翻译一下,并了解这个FLAG的都知道,此时的非Activity类型的Context并没有所谓的返回栈,因此带启动的Activity就找不到栈。它还给我们明确之处了FLAG的解决办法,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以Single Task模式启动的。所以这种用Application Context启动Activity的方式不推荐,Service同理。
在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果自定义了某些样式可能不会被使用,所以也不推荐。

注:和UI相关的,都应该使用Activity Context来处理。其他的一些操作,Service、Activity、Application等实例都是可以的。
同时要注意Context的引用持有,防止内存泄漏。可在被销毁的时候,置Context为null。

如何获取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也可以。

实现View.OnClick监听方法中,写Toast,不要用this,因为this,在onClick(View view)指的是view对象而不是Activity实例,所以在这个方法中,应该使用”当前的Activity名.this“,这是入门者比较容易混淆的地方。

getApplication()和getApplicationContext():

获取当前Application对象用getApplicationContext.但是getApplication又是什么。

Application app=(Application)getApplication();
Log.e(TAG,"getApplication is "+app);
Context context=getApplicationContext();
Log.e(TAG,"getApplicationContext is "+ context);

运行后看logcat。从打印结果可以看出它们2个的内存地址是相同的,即它们是同一个对象。 但是这2个方法在作用域上有比较大的区别。

getApplication()一看就知道是用来获取Application实例的(道理可以联想getActivity())。

但getApplication()只有在Activity和Service中才能调用的到。

对于比如BroadcastReceiver等中也想要获取Application实例,这时就需要getApplicationContext()方法。

//继承BroadcastReceiver并重写onReceive()方法
@Override
public void onReceive(Context context.Intent intent){
	Application app=(Application)context.getApplicationContext();
}

我们经常会遇到内存泄漏,比如Activity销毁了,但是Context还持有该Activity的引用,造成了内存泄漏。(经常遇到)

2种典型的错误引用方式:

错误的单例模式:

public class Singleton{
	private static Singleton instancel
	private Context context;
	private Singleton(Context context){
		this.context=context;
	}
	public static Singleton getInstance(Context context){
		if(instance == null ){
			instance=new Singleton(context);
		}
		return instance;
	}
}

熟悉单例模式的都知道,这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象(单例直到APP退出后台才销毁),其中也包含了Activity。比如Activity A去getInstance()得到instance对象,传入this,常驻内存的Singleton保存了我们传入的A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC(垃圾回收)掉,这样就导致了内存泄漏。比如典型的数据库操作,存储数据,需要重复的去索取数据,用单例保持数据和拿到Activity持有context引用,因为单例可以看作是上帝,它帮我们保存数据。所以即使Activity被finish掉,还有它的引用在Singleton中。

View持有Activity引用:

public class MainActivity extend Activity{
	private static Drawable mDrawable;	
	@Override
	protected void onCreate(Bundle saveInstanceState){
		super.onCreate();
		setContentView(R.layout.activity_main);
		ImageView imageview=new ImageView(this);//通过代码动态的创建组件,而不是传统的xml配置组件,这里的ImageView持有当前Activity的引用。
		mDrawable=getResources().getDrawable(R.drawable.ic_launcher);
		imageview.setImageDrawable(mDrawable);
	}
}

上述代码中,有一个static的Drawable对象。当ImageView设置这个Drawable的时候,ImageView保存了这个mDrawable的引用,而ImageView初始化的时候又传入了this,此处的this是指MainActivity的context。因为被static修饰的mDrawable是常驻内存的(比类还要早加载)。MainActivity是它的间接引用了,当MainActivity被销毁的时候,也不能被GC掉,就造成了内存泄漏。

如何在程序中正确的使用Context:

一般Context造成的内存泄漏,几乎都是当Context销毁的时候,因为被引用导致销毁失败。而Application的Context对象可以简单的理解为伴随着进程存在的(它的生命周期也很长,毕竟APP加载的时候先加载Application,我们可以自定义Application然后继承系统的Application)。
正确使用:

1.当Applicatin的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context;
2.不要让生命周期长于Activity的对象持有Activity的引用。

3.尽量不要在Activity中使用非静态内部类。非静态内部类会隐式持有外部类实例的引用。如果使用静态内部类,将外部实例引用作为弱引用持有。

面试:

ContextImpl 实例是什么时候生成的,在 Activity 的 onCreate 里能拿到这个实例吗

先说结论,可以。我们开发的时候,经常会在 onCreate 里拿到 Application,如果用 getApplicationContext 取,最终调用的就是 ContextImpl 的 getApplicationContext 方法,如果调用的是 getApplication 方法,虽然没调用到 ContextImpl ,但是返回 Activity 的成员变量 mApplication 和 ContextImpl 的初始化时机是一样的。

再说下它的原理,Activity 真正开始启动是从 ActivityThread.performLaunchActivity 开始的,这个方法做了这些事:

通过 ClassLoader 去加载目标 Activity 的类,从而创建 对象

从 packageInfo 里获取 Application 对象

调用 createBaseContextForActivity 方法去创建 ContextImpl

调用 activity.attach ( contextImpl , application) 这个方法就把 Activity 和 Application 以及 ContextImpl 关联起来了,就是上面结论里说的时机一样

最后调用 activity.onCreate 生命周期回调

通过以上的分析,我们知道了 Activity 是先创建类,再初始化 Context ,最后调用 onCreate , 从而得出问题的答案。不仅 Activity 是这样, Application 、Service 里的 Context 初始化也都是这样的。

总结:

Context类仅仅是定义了一组抽象方法的抽象类,其内部的方法真正实现的地方都在ContextImpl类中。

ContextWrapper是Context的一个包装类,其里面所有的方法实现都是调用其内部mBase变量的方法,而mBase就是ContextImpl对象。然而ContextWrapper还有一个ContextThemeWrapper子类,该类中扩展了主题相关的方法。Application和Service是继承自ContextWrapper,而Activity是继承自ContextThemeWrapper,Activity在启动的时候系统都会加载一个主题,也就是我们平时在AndroidManifest.xml文件里面写的android:theme=”@style/AppTheme”属性!然而Service和Applicaton都和UI界面并没有卵关系!因此它们继承自ContextWrapper。所以Activity,Application,Service其实都关联着一个mBase变量,而mBase变量是ContextImpl对象的赋值,也是真正实现抽象类Context的地方。

ContextImpl 实现了抽象类Context里面的所有方法,获取资源,启动Activity,Service等。值得注意的是在ContextImpl创建的时候就会利用静态区来注册系统的各种服务,因此每个持有Context引用的类都可以通过getSystemService来轻松的获取系统服务了。比如我们平时LayoutInflater类来加载一个XML布局时,LayoutInflater布局加载器也是调用context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
ok,context终于讲解完了,以后我们就能够舒舒服服的使用context的啦!如果觉得学到了东西欢迎点个赞咯!!嘻嘻

本次参考


参考链接1

参考链接2

参考链接3

参考链接4

参考链接5

参考链接6

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值