Context
参考 Android 8.0 源码
Context简介
Context是一个抽象类,它有两个具体的实现子类:ContextImpl和ContextWrapper。
ContextWrapper类,是一个包装而已,在ContextWrapper构造函数中必须包含一个Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。ContextWrapper类有好几个子类,主要的有ContextThemeWrapper、Application、Service三个。ContextThemeWrapper类,其内部包含了与主题Theme相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity才需要主题,Service因为是没有界面的后台场景所以不需要主题的。所以Service直接继承自ContextWrapper,而Activity则继承自ContextThemeWrapper类,Application也是不需要主题的,所以也是直接继承自ContextWrapper。
ContextImpl类则是真正实现了Context中的所有函数,应用程序中所调用的各种Context类的方法,其实均来自于该类。
Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity、Application、Service虽然都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们最初初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。
public abstract class Context {
//代码省略;
}
public class ContextWrapper extends Context {
//代码省略;
}
class ContextImpl extends Context {
//代码省略;
}
public class ContextThemeWrapper extends ContextWrapper {
//代码省略;
}
public class Application extends ContextWrapper implements ComponentCallbacks2 {
//代码省略;
}
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
//代码省略;
}
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
//代码省略;
}
Context使用技巧
获取全局Context
我们在很多地方都是需要使用Context的,在显示Toast的时候,在启动活动的时候,在发送广播的时候,在操作数据库的时候等等。所以我们一般会设置一个全局Context,这样会方便我们获取到Context。
public class MyApplication extends Application {
private static Context mContext;
@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
}
public static Context getContext(){
return mContext;
}
}
一个应用程序中有几个Context
在应用程序中,Context的具体实现子类是Activity,Service,Application。那么Context数量 = Activity数量 + Service数量 + 1。四大组件的另外两个BroadcastReceiver和ContentProvider,并不是Context的子类,它们所持有的Context都是其他地方传过去的,所以不计入Context总数。
Context的作用域
虽然Context神通广大,但并不是随便拿到一个Context实例就可以为所欲为,它的使用还是有一些规则限制的。由于Context的具体实例是由ContextImpl类去实现,因此在绝大多数场景下,Activity、Service、Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity、还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础上(主Activity例外),也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
Context作用域 | Application | Activity | Service |
---|---|---|---|
显示Dialog | No | Yes | No |
启动Activity | 不推荐 | Yes | 不推荐 |
layout inflate | 不推荐 | Yes | 不推荐 |
启动Service | Yes | Yes | Yes |
发送BroadcastReceiver | Yes | Yes | Yes |
注册BroadcastReceiver | Yes | Yes | Yes |
加载资源 | Yes | Yes | Yes |
Activity持有的Context的作用域最广。因为Activity继承自ContextThemeWrapper,而Application和Service继承自ContextWrapper,很显然ContextThemeWrapper在ContextWrapper的基础上又做了一些操作使得Activity变得更强大。
在这里讲解一下上图中Application和Service所不推荐的两种使用情况。
不推荐使用Application中的Context去启动一个LaunchMode为standard的Activity:
因为非Activity的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就会为它创建一个新的任务栈,而此时Activity是以SingleTask模式启动的。所有这种用Application启动Activity的方式并不推荐使用,Service中场景同Application。在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式,可能不会被使用,所以这种方式不推荐使用。
凡是和UI相关的,都应该使用Activity作为Context来处理。其他的一些操作,Service、Activity、Application等实例都可以。当然,需要注意Context引用的持有,防止内存泄漏。
如何获取Context
通常我们想要获取Context对象,主要有以下四种方法:
- View.getContext():返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
- Activity.getApplicationContext():获取当前Activity所在的应用进程的ApplicationContext对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
- ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法。但是这个方法在实际开发中使用并不多,也不建议使用。
- Activity.this:返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicatinContext也可以。
getApplication()和getApplicationContext()的区别
其实这两个方法获取到的是同一个对象。但是这两个方法在作用域上面有比较大的区别。getApplication()只有在Activity和Service中才能调用到。那么在其他一些场景中,比如BroadcastReceiver中也想获取Application的实例,这时就可以借助getApplicationContext()方法了。
Context引起的内存泄漏
Context如果使用不当,会引起内存泄漏问题。下面列举两种错误的引用方式:
1 . 错误的单例模式
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
这是一个非线程安全的单例,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity。假如Activity A去调用getInstance()方法获取instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并一直持有,即使Activity A被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。
2 . View持有Activity引用
public class MainActivity extends Activity {
private static Drawable mDrawable;
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
ImageView iv = new ImageView(this);
mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
iv.setImageDrawable(mDrawable);
}
}
有一个静态的Drawable对象当ImageView设置这个Drawable时,ImageView会将自身设置到Drawable的callback上去,因为View是实现了Drawable.Callback接口, 这样当Drawable需要刷新的时候,可以调用.这个Callback,然后通知View重新绘制该Drawable. 所以引用的正确顺序应该Drawable->View->Context,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。
在ImageView中updateDrawable该方法中 有一个d.setCallback(this),ImageView将自身设置到Drawable的callback上去,当Drawable需要刷新的时候,可以调用.这个Callback,然后通知View重新绘制该Drawable.
public void setImageDrawable(@Nullable Drawable drawable) {
if (mDrawable != drawable) {
mResource = 0;
mUri = null;
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(drawable);//1
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();//2
}
}
使用context的时候,还有以下几个方面来防止内存泄漏:
1 . 不要让生命周期长的对象引用Activity的context,即保证引用Activity的对象要与Activity本身生命周期是一样的;
2 . 对于生命周期长的对象,可以使用Application的context;
3 . 避免非静态内部类,尽量使用静态类,避免生命周期问题,注意内部类对外部对象引用导致的生命周期的变化;
正确使用Context
一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们可以根据以下方式来使用:
1 . 当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context;
2 . 不要让生命周期长于Activity的对象持有Activity引用;
3 . 尽量不要在Activity中使用非静态内部类,因为静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
----------------------last line for now--------------------