掌握Android Context刻不容缓

版权声明:本文为博主原创文章,可以随意转载,但请附原地址或原作者名。谢谢! https://blog.csdn.net/farley119/article/details/83068405

引言

Context应该是每个Android入门开发者第一个接触到的概念,它代表当前上下文环境,可以用来实现很多功能的调用。

我们最常见的有这些

//获取资源管理器对象,进而可以访问到string,color等资源
Resources resources = context.getResources();
//启动指定的Activity
context.startActivity(new Intent(this,MainActivity.class));
//获取各种系统服务
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
//获取系统文件目录
File internalDir = context.getCacheDir();
File internalDir = context.getExternalCacheDir();

可见,正确理解context很重要。在实际开发中我们会遇到各种各样的context,但是你要知道,每一个context都是有自己的含义的,并不是都相同的哦。这点你搞错了,那么开发也就进行不下去了。

Context种类

我们一般按照Context依托的组件和用途不同来进行分类:

  • Application:Android应用中的默认单例类,在Activity或者service中可以通过getApplication()获取到这个单例。通过context.getApplicationContext()可以获取到应用全局唯一Context实例。
  • Activity/Service :这两个类都是ContextWrapper的子类,在这两个类中可以通过getBaseContext()获取到他们的Context实例,不同的Activity或者Service实例他们的Context都是相互独立的,不会复用。
  • BroadcastReceiver:和Activity以及Service不同,BroadcastReceiver本身并不是Context的子类,而是在回调函数onReceive()中由Android框架传入一个Context的实例。系统传入的这个Context实例是被阉割过的,它并不能调用registerReceiver()已经bindService()这两个函数
  • ContentProvider:同样的,ContentProvider也不是Context的子类。同样是在创建的时候系统给传一个Context实例,然后在ContentProvider中就可以通过getContext()函数来获取。如果调用者和ContentProvider处于一个相同的应用进程也就是说在同一个应用中调用ContentProvider的话,getContext()会返回应用的全局唯一Context实例。相反如果不在同一个进程,那么他们各自返回自己所在进程的Context实例。

错误使用将导致内存泄漏

错误的使用Context将导致内存泄漏,典型的例子是在实现单例模式的时候使用Context而出现的内存泄漏:

public class SingleInstance{
	private Context mContext;
	private static SingleInstance sInstance;
	private SingleInstance(Context context){
		mContext = context;
	}
	public static SingleInstance getInstance(Context context){
		if(sInstance == null){
			sInstance = new SingleInstance(context);
		}

		return sInstance;
	}
}

对于这个实例,如果在创建这个单例的时候传入的context是Activity或者Service的,那么在整个应用结束之前,这个单例会一直存在,从而到这当初传给它Context的Activity或者Service也会一直存在不会被垃圾回收,这样的话Activity或者Service中关联的其它View或者数据等等也就不会被释放,最后导致了内存泄漏。在这个单例模式中传入的正确Context必须是Application Context,因为它是应用全局唯一的。而且生命周期跟应用的声明周期是同步的。
对于这种问题,我们也可以在创建单例的时候,从根源就把这个隐患给避免掉,具体做法如下:

public class SingleInstance{
	private Context mContext;
	private static SingleInstance sInstance;
	private SingleInstance(Context context){
		mContext = context;
	}
	public static SingleInstance getInstance(Context context){
		if(sInstance == null){
			sInstance = new SingleInstance(context.getApplicationContext());
		}

		return sInstance;
	}
}

不同的Context的对比

不同的组件中的context功能是不一样的,总结起来如下:

功能 Application Activity Service BroadcastReceiver ContentProvider
显示dialog NO YES NO NO NO
启动Activity NO[1] YES NO[1] NO[1] NO[1]
实现Layout inflation NO[2] YES NO[2] NO[2] NO[2]
启动Service YES YES YES YES YES
绑定Service YES YES YES YES NO
发送Broadcast YES YES YES YES YES
注册Broadcast YES YES YES YES NO[3]
加载资源 Resource YES YES YES YES YES

NO[1]:表示对应的组件并不是不可以,而是不建议。因为这些组件会在新的Task中创建Activity而不是在原来的Task中创建
NO[2]:也是表示不建议,因为在非Activity中进行Layout Inflation,会使用系统默认的主题,而不是应用中设置的主题
NO[3]:表示在Android4.2以及以上的系统中,如果注册BroadcastReceiver是null时也是可以的,用来获取sticky广播的当前值

猜你喜欢

转载自blog.csdn.net/farley119/article/details/83068405