Android:上下文Context解析

Context解析

Android应用都是使用Java语言来编写的,本质上也是一个对象,那么Activity可以new吗?一个Android程序和一个Java程序,他们最大的区别在哪里?划分界限又是什么呢?其实简单点分析,Android程序不像Java程序一样,随便创建一个类,写个main()方法就能跑了,Android应用模型是基于Activity、Service、BroadcastReceiver等组件的应用设计模式,组件的运行要有一个完整的Android工程环境,在这个环境下,这些组件并不是像一个普通的Java对象new一下就能创建实例的了,而是要有它们各自的上下文环境Context。可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。

1、什么是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.
 */
public abstract class Context {
    /**
     * File creation mode: the default mode, where the created file can only
     * be accessed by the calling application (or all applications sharing the
     * same user ID).
     * @see #MODE_WORLD_READABLE
     * @see #MODE_WORLD_WRITEABLE
     */
    public static final int MODE_PRIVATE = 0x0000;
    
    public static final int MODE_WORLD_WRITEABLE = 0x0002;

    public static final int MODE_APPEND = 0x8000;

    public static final int MODE_MULTI_PROCESS = 0x0004;

    .
    .
    .
    }

以上是Android源码对Context的描述:

  • 它是应用程序环境的全局信息的接口。
  • 这是一个抽象类,由Android系统提供。
  • 它允许访问特定于应用程序的资源和类,以及调用应用程序级操作,如启动活动,广播和接收意图等。

综上:Context的第一个作用是获取应用程序的资源和类,第二个作用是调用应用程序级操作,比如启动活动、广播和接收意图等。

Context到底是什么

Context的中文翻译为:语境; 上下文; 背景; 环境,在开发中我们经常说称之为“上下文”,那么这个“上下文”到底是指什么意思呢?在语文中,我们可以理解为语境,在程序中,我们可以理解为当前对象在程序中所处的一个环境,一个与系统交互的过程。比如微信聊天,此时的“环境”是指聊天的界面以及相关的数据请求与传输,Context在加载资源、启动Activity、获取系统服务、创建View等操作都要参与。

那Context到底是什么呢?一个Activity就是一个Context,一个Service也是一个Context。Android程序员把“场景”抽象为Context类,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是一个有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service)。一个应用程序可以认为是一个工作环境,用户在这个环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可以称之为一个应用程序。

如何生动形象的理解Context:Context环境下各项组件才能被调用
上面的概念中采用了通俗的理解方式,将Context理解为“上下文”或者“场景”,如果你仍然觉得很抽象,不好理解。在这里我给出一个可能不是很恰当的比喻,希望有助于大家的理解:一个Android应用程序,可以理解为一部电影或者一部电视剧,Activity,Service,Broadcast Receiver,Content Provider这四大组件就好比是这部戏里的四个主角:胡歌,霍建华,诗诗,Baby。他们是由剧组(系统)一开始就定好了的,整部戏就是由这四位主演领衔担纲的,所以这四位主角并不是大街上随随便便拉个人(new 一个对象)都能演的。有了演员当然也得有摄像机拍摄啊,他们必须通过镜头(Context)才能将戏传递给观众,这也就正对应说四大组件(四位主角)必须工作在Context环境下(摄像机镜头)。那Button,TextView,LinearLayout这些控件呢,就好比是这部戏里的配角或者说群众演员,他们显然没有这么重用,随便一个路人甲路人乙都能演(可以new一个对象),但是他们也必须要面对镜头(工作在Context环境下),所以Button mButton=new Button(Context)是可以的。虽然不很恰当,但还是很容易理解的,希望有帮助。

2、Context作用的具体体现

有了Context这个环境,Android组件才可以正常被创建和调用,所以Context的作用如下:

TextView tv = new TextView(getContext());

ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...);

AudioManager am = (AudioManager) getContext().
getSystemService(Context.AUDIO_SERVICE);

getApplicationContext().getSharedPreferences(name, mode);

getApplicationContext().getContentResolver().query(uri, ...);

getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;

getContext().startActivity(intent);

getContext().startService(intent);

getContext().sendBroadcast(intent);

3、Context的继承结构

在这里插入图片描述
Context类,一个纯Abstract类,有ContextImpl和ContextWrapper两个实现类:

  • ContextWrapper包装类

其构造函数中必须包含一个真正的Context引用。ContextWrapper中提供了attachBaseContext()(由系统调用)方法,用于给ContextWrapper对象中指定真正的Context对象,即ContextImpl对象,调用ContextWrapper的方法都会被转向ContextImpl的方法

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实例方法
    }
}

  • ContextImpl类:上下文功能的实现类。ContextIml.java类 路径 :/frameworks/base/core/java/android/app/ContextImpl.java

说明:该Context类的实现类为ContextIml,该类实现了Context类的功能。请注意,该函数的大部分功能都是直接调用

其属性mPackageInfo去完成,这点我们后面会讲到。

源代码(部分)如下:

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context{
	//所有Application程序公用一个mPackageInfo对象
    /*package*/ ActivityThread.PackageInfo 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);
    }
}


  • ContextThemeWrapper类

一个带主题的封装类,其内部包含了与Theme相关的接口,这里所说的主题是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapper,Application同理。

ContextImpl类则真正实现了Context中的所有函数,应用程序中所调用的各种Context类的方法,其实现均来自于该类。一句话总结:Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity,Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。

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的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity,Application,Service虽都继承自ContextWrapper,但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。

那么,Context到底可以实现哪些功能呢?这个就实在是太多了,弹出Toast、启动Activity、启动Service、发送广播、操作数据库等等都需要用到Context。由于Context的具体能力是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的,但在使用场景上是有一些规则,以下表格中列出了各Context的使用场景:

在这里插入图片描述
以上表格中NO上添加了一些数字,其实这些从能力上来说是YES,但是为什么说是NO呢?下面一个一个解释:

  • NO^1:启动Activity在这些类中是可以的,但是需要创建一个新的task。不推荐。

如果我们用Application Context或Service Context去启动一个LaunchMode为standard的Activity的时候会报错,这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。

  • NO^2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。不推荐。

所以:

凡是跟UI相关的,都应使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等都可以,当然得注意Context引用的持有,防止内存泄漏。
比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

Context的数量

Context数量 = Activity数量 + Service数量 + 1(Application)

4、Context的创建时机和获取

1.Context的创建时机

(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实例
     }
     …
 }

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实例
        …
    }
    …
}

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);
    …
}

另外,通过对ContextImp的分析可知,其方法的大多数操作都是直接调用其属性mPackageInfo(该属性类型为PackageInfo)的相关方法而来。这说明ContextImp是一种轻量级类,而PackageInfo才是真正重量级的类。而一个App里的所有ContextIml实例,都对应同一个packageInfo对象。

2.Context的获取

(1)通常我们想要获取Context对象,主要有以下四种方法

  • View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象
  • Activity.getApplicationContext,获取的context来自允许在应用(进程)application中的所有Activity,当你需要用到的Context超出当前Activity的生命周期时使用
  • Activity.this返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以
  • ContextWrapper.getBaseContext()用来获取一个ContextWrapper进行装饰之前的Context,也就是ContextImpl对象,如果想获取另一个可以访问的application里面的Context时可以使用

Android获取Context(任意位置任意地方,全局上下文)

1.Activity.this的context 

(一般用法)返回当前activity的上下文,属于activity ,activity 摧毁他就摧毁

2.getApplicationContext() 

返回应用的上下文,生命周期是整个应用,应用摧毁它才摧毁

3.getBaseContext() 

返回由构造函数指定或setBaseContext()设置的上下文

 
4.getActivity() 

多用于fragment中

getApplication()、getApplicationContext()和getBaseContext()的关系?

我们在Activity中获取分别打印这个三个方法:

MyApplication myApp = (MyApplication) getApplication();  

Context appContext = getApplicationContext();  

Context baseContext = getBaseContext();

打印结果:

getApplication::::com.beidou.mvptest.MyApplication@53502fac

getApplicationContext::::com.beidou.mvptest.MyApplication@53502fac

baseContext::::android.app.ContextImpl@53505ce4

结论:

  • getApplication和getApplicationContext得到的是一个对象MyApplication。

  • getBaseContext得到的是ContextImpl。

疑问:

第一问:getApplication和getApplicationContext得到的对象是一样的,那为何设计两个方法呢?

答:两者范围不同,后者比前者适用范围更广。getApplication只适用于Activity和Service,而getApplicationContext还用于其他场景,比如BroadcastReceiver中。

第二问:ContextImpl是什么东东?
答:
①:ContextImpl是Context功能的实现类。Application和Service、Activity并不会去实现Context的功能,只是做了接口的封装,具体的功能由ContextImpl完成。

②:因为Application、Activity、Service都是直接或间接继承自ContextWrapper的,我们就直接看ContextWrapper的源码,就会发现所有ContextWrapper中方法的实现都非常统一,就是调用了mBase对象中对应当前方法名的方法。

③:那么这个mBase对象又是什么呢?我们来看第16行的attachBaseContext()方法,这个方法中传入了一个base参数,并把这个参数赋值给了mBase对象。而attachBaseContext()方法其实是由系统来调用的,它会把ContextImpl对象作为参数传递到attachBaseContext()方法当中,从而赋值给mBase对象,之后ContextWrapper中的所有方法其实都是通过这种委托的机制交由ContextImpl去具体实现的,所以说ContextImpl是上下文功能的实现类。

④:再看一下我们刚刚打印的getBaseContext()方法,在第26行。这个方法只有一行代码,就是返回了mBase对象而已,而mBase对象其实就是ContextImpl对象,因此刚才的打印结果也得到了印证。

/** 
 * 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;  
      
    /** 
     * 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 ContentResolver getContentResolver() {  
        return mBase.getContentResolver();  
    }  
  
    @Override  
    public Looper getMainLooper() {  
        return mBase.getMainLooper();  
    }  
      
    @Override  
    public Context getApplicationContext() {  
        return mBase.getApplicationContext();  
    }  
  
    @Override  
    public String getPackageName() {  
        return mBase.getPackageName();  
    }  
  
    @Override  
    public void startActivity(Intent intent) {  
        mBase.startActivity(intent);  
    }  
      
    @Override  
    public void sendBroadcast(Intent intent) {  
        mBase.sendBroadcast(intent);  
    }  
  
    @Override  
    public Intent registerReceiver(  
        BroadcastReceiver receiver, IntentFilter filter) {  
        return mBase.registerReceiver(receiver, filter);  
    }  
  
    @Override  
    public void unregisterReceiver(BroadcastReceiver receiver) {  
        mBase.unregisterReceiver(receiver);  
    }  
  
    @Override  
    public ComponentName startService(Intent service) {  
        return mBase.startService(service);  
    }  
  
    @Override  
    public boolean stopService(Intent name) {  
        return mBase.stopService(name);  
    }  
  
    @Override  
    public boolean bindService(Intent service, ServiceConnection conn,  
            int flags) {  
        return mBase.bindService(service, conn, flags);  
    }  
  
    @Override  
    public void unbindService(ServiceConnection conn) {  
        mBase.unbindService(conn);  
    }  
  
    @Override  
    public Object getSystemService(String name) {  
        return mBase.getSystemService(name);  
    }  
  
    ......  
}  

getActivity()和getContext()

  • getActivity()返回Activity,getContext()返回Context;
  • 参数是context的,可以使用getActivity() 。因为Activity间接继承了Context,但Context不是Activity;
  • this和getContext()并不是完全相同。在Activity类中可以使用this,因为Activity继承自Context,但是getContext()方法不在Activity类中。

5、Context引起的内存泄漏

在实际开发中我们需要用到工具类,一般都会采用单例模式:

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

你在某个Activity调用了getInstance()方法,直接传了个this;这样问题就来了,我们的这个类中的sInstance是一个static且强引用的,在其内部引用了一个Activity作为Context,也就是说,我们的这个Activity只要我们的项目活着,就没有办法进行内存回收。而我们的Activity的生命周期肯定没这么长,所以造成了内存泄漏。

解决办法:

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

由于Application对象的生命周期如上面分析的那样,和整个项目一样长。也就是它的生命周期和我们的单例对象一致。

6、正确使用Context

一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:

  • 当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
  • 不要让生命周期长于Activity的对象持有到Activity的引用。
  • 尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

参考

1、https://www.jianshu.com/p/94e0f9ab3f1d
2、https://www.jianshu.com/p/cc0bb2a71ee8
3、https://www.jianshu.com/p/4baf231e7084?from=timeline
4、https://blog.csdn.net/qinjuning/article/details/7310620?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-4.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-4.control
5、https://blog.csdn.net/zjb369542423/article/details/50719046?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_utm_term-1&spm=1001.2101.3001.4242

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值