剖析Android中的Context类(1501210406 叶振旭)
在开发中,我们经常与Context进行打交道,然而,我们对它内部的原理、类结构关系却是“丈二和尚,摸不着头脑”。因此,它可谓是我们最熟悉的陌生人了。下面,我就试着揭开Context神秘的面纱。
一、Android中我们经常接触到Context的场景
1)在应用Tosat中,我们经常这样写:
Toast.makeText(MainActivity.this,”You clicked a button”, Toast.LENGTH_SHORT).show();
可以看到第一个参数就是Context,也就是Toast要求的上下文,由于活动本身就是一个Context对象,因此,直接传入MainActivity.this即可。
2)想实现从一个活动中跳转到其他活动,我们经常这样写:
Intent intent=new Intent( FirstActivity.this , SecondActivity.class);
这个Intent的构造函数接收两个参数,第一个参数就是Context,即启动活动的上下文,第二个参数Class则是指定要跳转的目标活动。
3)发送一则广播时,我们经常这样写:
Intent intent=new Intent(“com.example.my_broadcast”);
context.sendBroadcast(intent);
首先构建出一个Intent对象,把要发送的广播的值传入,然后调用Context类的sendBroadcast()方法将广播发送出去,这样监听com.example.my_broadcast这条广播的广播接收器就会收到信息。
4)接收一则广播时,我们经常这样写:
Public class my_broadcastReceiver extends BroadcastReceiver
{
@Override
Public void onReceiver(Context context ,Intent intent)
{
//这里你可以进行接收到广播后的行为。如简单显示已经接收到了广播。
Toast.makeText(context, ”已经成功接收到广播了”, Toast.LENGTH_SHORT).show();
}
}
可以看到,这里onReceiver()方法的第一个参数便是context。
5)使用SharedPreferences存储数据。我们经常这样写:
SharedPreferences.Editor editor=getSharedPreferences(“data”,MODE_PRIVATE).edit();
editor.putString(“name”, “yezhenxu”);
editor.putInt(“age”, ”23”);
……
editor.commit();
通过Context类下的getSharedPreference()方法指定SharedPreference的文件名为data,并得当SharedPreferences.Editor对象,然后往editor添加数据,最后调用commit()方法进行提交,从而完成了数据存储的操作。 ……
Context 就是用于获得系统资源的,我们所说的系统资源包括设备本身的信息,也有开发者提供的信息(类、包、资源文件等等……)。
二、Context是什么
2.1 SDK中对其说明如下:
1)Interface to global information about an application environment. 它描述的是一个应用程序环境的信息,即上下文。
2)This is an abstract class whose implementation is provided by the Android system. 该类是一个抽象类(abstract class),Android提供了该抽象类的具体实现类。
3)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. 通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent信息等。
2.2 Context的作用
Context是一个抽象基类,我们通过它访问当前包的资源(getResources、getAssets)和启动其他组件(Activity、Service、Broadcast)以及得到各种服务(getSystemService),当然,通过Context能得到的不仅仅只有上述这些内容。Context提供了一个应用的运行环境,在Context的大环境里,应用才可以访问资源,才能完成和其他组件、服务的交互。
2.3 Context的类型
Context的类型有两种,一种是Activity Context,另一种是Application Context,这两种的区别就在于它们的生命周期不一样,一个是随着Activity的销毁而销毁,另一个是伴随整个Application。
2.3.1 Application Context
Application Context的生命周期是整个应用。在程序中,我们可以通过代码getApplicationContext();得到当前应用程序的application context。Application Context的生命周期为整个Application,所以如果是一些需要在整个应用期间都存在的资源,我们可以将它放进一个Application Context中,然后通过获取这个Context来使用它们。
2.3.2 Activity Context
这个Context的生命周期是和得到它的引用的Activity一样长,如果这个Activity结束了,那么,这个Context也会得到释放。它并不像我们上面的Application Context需要特意去获得,可以在一个Activity中使用this就可以获得当前Activity的Context。举个例子,如Toast.makeText(this,”clicked a button”Toast.LENGTH_LONG).show();,其中的this就是当前的Activity Context,但是,一味的使用this是很危险的,我们要注意的就是,在匿名内部类如果单纯只是使用this是会出错的,因为内部类中使用this得到的是内部类的对象引用,而不是我们要得到的外部类的引用,于是,这时候就必须使用类名.this这种方式,这种做法在按钮的事件监听中是特别要注意的。
2.3.3 Context导致的内存泄露
这里,我举两个例子来解释Context是如何导致内存泄露的。当然,泄露并不是真正意思的泄露,而是指内存不能被GC(垃圾回收机制回收),从而导致占用内存过大,发生Out of Memory,而被系统Kill的现象。只要类持有对外部类实例对象的引用,垃圾回收机制就不会回收该对象,就会出现内存泄露。
例1:我们要实现一个 ListView 展示信息的功能,为了自定义子 Item,我们需要传入 Context 来读取布局资源。
代码如下:
public class MyBaseAdapter extends BaseAdapter{
private Context mContext;
public MyBaseAdapter(Context context) {
mContext = context;
}
……
}
//设计完 BaseAdapter 以后,我们肯定要在 MainActivity 里面使用它了:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adapter = new MyBaseAdapter(this);
……
}
一般新手都会写出类似这样的代码,这样写都不会出现 Bug 对吧?事实上,这样的代码是会导致内存泄漏,为什么呢? 在这段代码中,我们将 Activity 的引用(this)传入 MyBaseAdapter,使得 MyBaseAdapter 持有对 Activity 的引用,那么相应地,我们也将持有 Activity 所获得的资源文件的引用。所以这些资源文件将无法被垃圾回收机制回收,造成内存泄漏的问题。
那么有什么解决办法呢?
我们只需要简单修改下代码:把private Context mContext;改成mContext = context.getApplicationContext(); 或者把adapter = new MyBaseAdapter(this);改成adapter = new MyBaseAdapter(this.getApplication());就可以避免内存泄漏的问题。
例2:Android官网提供的例子
在Android系统中,当我们进行了屏幕旋转,默认情况下,会销毁掉当前的Activity,并创建一个新的Activity并保持之前的状态。在这个过程中,Android系统会重新加载程序的UI视图和资源。假设我们有一个程序用到了一个很大的Bitmap图像,我们不想每次屏幕旋转时都重新加载这个Bitmap对象,最简单的办法就是将这个Bitmap对象使用static修饰。
代码如下:
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
屏幕旋转的时候系统会销毁当前的activity。但是当drawable和view关联后(label.setBackgroundDrawable(sBackground);),drawable保存了view的引用,即sBackground保存了label的引用,而label保存了activity的引用。既然drawable不能销毁,它所引用和间接引用的都不能销毁,这样系统就没有办法销毁当前的activity,于是造成了内存泄露。GC对这种类型的内存泄露是无能为力的。
为了防止内存泄露,我们应该注意以下几点:
1)避免activity中的任何对象的生命周期长过activity,避免由于对象对 activity的引用导致activity不能正常被销毁;
2)对于生命周期长的对象,可以使用application context;
3)在引用静态资源,创建静态方法,单例模式等情况下,使用生命周期更长的Application的Context才不会导致内存泄漏;
三、Context类的继承情况
1)Activity类 、Service类 、Application类本质上都是Context的子类。
2) 你可以在android studio中跟踪Activity类的继承关系:把鼠标放到红色圈Activity处,如图1;然后按快捷键Ctrl+H ,便会看到如图2,显然Activity继承自ContextThemeWrepper,间接继承自Context。 图1
图2
3)如果你想更深层次的研究各个Context子类的源代码,你可以按快捷键F4进行跟踪。
四、下面通过代码具体分析Context类的情况
4.1 Context类
部分源代码如下:
public abstract class Context
{
// 启动一个新的activity
public abstract void startActivity(Intent intent);
// 广播一个intent 给所有感兴趣的接收者,异步机制
public abstract void sendBroadcast(Intent intent);
// 请求启动一个application service
public abstract ComponentName startService(Intent service);
// 请求停止一个application service
public abstract boolean stopService(Intent service);
// 获取应用程序包的AssetManager 实例
public abstract AssetManager getAssets();
// 获取应用程序包的Resources 实例
public abstract Resources getResources();
// 获取PackageManager 实例,以查看全局package 信息
public abstract PackageManager getPackageManager();
// 返回应用程序包名
public abstract String getPackageName();
// 返回应用程序信息
public abstract ApplicationInfo getApplicationInfo();
// 根据文件名获取SharedPreferences
public abstract SharedPreferences getSharedPreferences(String name,int mode);
//获取系统服务
public abstract Object getSystemService(@ServiceName @NonNull String name);
……
}
可以看出,Context类是一个抽象类,提供了一组通用的API。
4.2 ContextImpl类
该类实现了Context类的功能。
部分源代码如下:
class ContextImpl extends Context{
//所有Application程序公用一个mPackageInfo对象
ActivityThread.PackageInfo mPackageInfo;
// Activity、Application、Service都是在ActivityThread中完成的。
//该函数的大部分功能都是直接调用其属性mPackageInfo完成的
@Override
//得到系统服务对象
public Object getSystemService(String name){
...
else if (ACTIVITY_SERVICE.equals(name)) {
return getActivityManager();
}
else if (INPUT_METHOD_SERVICE.equals(name)) {
return InputMethodManager.getInstance(this);
}
}
@Override
public void startActivity(Intent intent) {
...
//开始启动一个Activity
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1);
}
}
4.3 ContextWrapper类
该类只是对Context类的一种包装,该类的构造函数包含了一个真正的Context引用,即ContextIml对象。
部分源代码如下:
public class ContextWrapper extends Context {
Context mBase;
//该属性指向一个ContextIml实例,一般在创建Application、Service、Activity时赋值
//创建Application、Service、Activity,会调用该方法给mBase属性赋值
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
//调用mBase实例方法
}
}
4.4 ContextThemeWrapper类
该类内部包含了主题(Theme)相关的接口,即android:theme属性指定的。只有Activity需要主题,Service不需要主题,所以Service直接继承于ContextWrapper类。
部分源代码如下:
public class ContextThemeWrapper extends ContextWrapper {
//该属性指向一个ContextIml实例,一般在创建Application、Service、Activity时赋值
private Context mBase;
//mBase赋值方式同样有一下两种
public ContextThemeWrapper(Context base, int themeres) {
super(base);
mBase = base;
mThemeResource = themeres;
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
mBase = newBase;
}
}
五、Context何时被创建?
每一个应用程序在客户端都是从ActivityThread开始的,创建Context也在该类中完成。
应用程序创建Context实例有如下几种情况:
创建Application对象的时候, 而且整个App共一个Application对象;
创建Service对象的时候;
创建Activity对象的时候;
总Context实例个数:Service个数+Activity个数+1(Application对应的Context实例)
5.1创建Application对象的时机
每个应用程序在第一次启动时,都会首先创建Application对象。如果对应用程序启动一个Activity(startActivity)流程比较清楚的话,创建Application的时机在创建handleBindApplication()方法中,该函数位于 ActivityThread.java类中。
代码如下:
//创建Application时同时创建的ContextIml实例
private final void handleBindApplication(AppBindData data){
...
//创建Application对象
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
...
}
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
...
try {
java.lang.ClassLoader cl = getClassLoader();
ContextImpl appContext = new ContextImpl(); //创建一个ContextImpl对象实例
appContext.init(this, null, mActivityThread);
//初始化该ContextIml实例的相关属性
//新建一个Application对象
app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext);
appContext.setOuterContext(app); //将该Application实例传递给该ContextImpl实例
}
...
}
5.2创建Activity对象的时机
通过startActivity()或startActivityForResult()请求启动一个Activity时,如果系统检测需要新建一个Activity对象时,就会回调handleLaunchActivity()方法,该方法继而调用performLaunchActivity()方法,去创建一个Activity实例,并且回调onCreate(),onStart()方法等, 函数都位于 ActivityThread.java类。
代码如下:
//创建一个Activity实例时同时创建ContextIml实例
private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) {
...
Activity a = performLaunchActivity(r, customIntent); //启动一个Activity
}
private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent) {
...
Activity activity = null;
try {
//创建一个Activity对象实例
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
}
if (activity != null) {
ContextImpl appContext = new ContextImpl(); //创建一个Activity实例
appContext.init(r.packageInfo, r.token, this); //初始化该ContextIml实例的相关属性
appContext.setOuterContext(activity); //将该Activity信息传递给该ContextImpl实例
...
}
...
}
5.3创建Service对象的时机
通过startService或者bindService时,如果系统检测到需要新创建一个Service实例,就会回调handleCreateService()方法,完成相关数据操作。handleCreateService()函数位于 ActivityThread.java类。
代码如下:
//创建一个Service实例时同时创建ContextIml实例
private final void handleCreateService(CreateServiceData data){
...
//创建一个Service实例
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
}
...
ContextImpl context = new ContextImpl(); //创建一个ContextImpl对象实例
context.init(packageInfo, null, this); //初始化该ContextIml实例的相关属性
//获得我们之前创建的Application对象信息
Application app = packageInfo.makeApplication(false, mInstrumentation);
//将该Service信息传递给该ContextImpl实例
context.setOuterContext(service);
...
}