Android中的Context(一)

在Android开发中,Context可以说是我们接触地非常多的一个概念了,也译作“上下文”,但是这个上下文到底是什么却并不好理解。

通俗的理解Context是什么:

广义上的程序开发来说,每一段程序都可能有很多外部依赖(比如说外部变量),一旦程序有了这些外部依赖,在程序脱离了这些外部依赖的时候,它是没法独立运行的,为了使得程序可以运行,你需要提供这些外部依赖,而这些依赖的集合,就是上下文。或者说,Context可以理解为提供信息或功能的容器或者环境

而之所以Context很模糊,是因为在不同的地方,Context代表着不同的含义。同阅读文章一样,上下文在不同的地方所表示的意义也是不一样的,这个需要感性的去理解。

举一个例子来说:

我们常做的阅读理解,要你答某一句话表达了鲁迅先生的什么心理。如果上下文都是省略号,仅凭一句话,要回答出问题那就只能是瞎扯了,而有了上下文,才能分析出答案是什么(程序正确执行)。

拿更具体的例子来说:

在我们点击一个Button进行页面跳转的时候,系统最起码需要保存我们是从哪个页面跳转过来的,这样才能够在点击返回的时候正确地返回。

Context在Android中使用场景:

需要注意地是,Context并不是Android特有的概念,它是计算机程序通用的概念。

可以说Context的概念贯穿了整个android体系;在Service、BroadcastReceiver、Activity等都会用到Context的相关方法。在Android的APP开发中,我们可能无时无刻不在跟Context打交道,最常见的Activity跳转,弹出Toast,访问应用的资源文件等等都用到了Context这个特殊的对象。

Android中应用程序A的Activity1调起应用程序B的Activity2,即应用A要使用应用B,Android并不是将整个程序B调起,而是直接调起Activity2,在最早学习到Activity启动模式的时候也学到过Activity的启动是由Android系统来管理的,使用者只需要提供对应的意图(在意图中需要上下文)即可。但是另一方面,Activity1和2应该是属于不同的进程,二者的地址空间是不一样的,同样A的1是无法直接使用B的2的,但是如果说,将Activity独立封装在一个上下文环境中,那么它应该就具有一定的独立运行能力,从而,它在逻辑上也就可以“直接”地被其他应用所使用,并不需要借助整个进程B才能实现使用Activity2。当然,这里仅仅是一种设想。

而在Android中,封装界面的小型上下文就是Android里的Activity,而封装服务的小型上下文就是Service。

Context类家族:

Context类位于

framework.package.android.content.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 {
    ...
    public abstract AssetManager getAssets();
    public abstract Resources getResources();
    ...

从源码注释来看,Context描述了一个应用程序环境的信息(即上下文);它是一个抽象类,Android提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动Activity,发广播,接受Intent等)。

通过继承来实现抽象类,而Context的继承示意图如下:

1.ContextImpl

Context直属子类之一,是Context API的通用实现,Context的功能实现类,为Activity等组件提供基础的上下文对象。

ContextImpl:(frameworks.base.core.java.android.app.ContextImpl)

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 *
 */
class ContextImpl extends Context {

2.ContextWrapper:

Context的直属子类之一,是Context的封装类。

Application和Service都继承自ContextWrapper。

ContextWrapper:(frameworks.base.core.java.android.content.ContextWrapper)

/**
 * 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;
    public ContextWrapper(Context base) {
        mBase = base;
    }

ContextWrapper内部包含有一个Context对象mBase,但是其真正的类型是ContextImpl,ContextWrapper的所有方法最终都是调用mBase对应的方法。

其内部方法的特征是:如果重写的是没返回值,则直接调用父类的此方法;如果重写的有返回值,则返回调用父类返回的值。

Context与ContextWrapper以及ContextImpl三者之间的关系是装饰模式,Context是抽象构件类,ContextImpl类是具体构件类,ContextWrapper类是抽象装饰类。

3.ContextThemeWrapper:

是一个带主题的Context封装类,Activity继承自ContextThemeWrapper。

/**
 * A context wrapper that allows you to modify or replace the theme of the
 * wrapped context.
 */
public class ContextThemeWrapper extends ContextWrapper {

Context的功能:

从前面的类关系图中可以看出Context类具体落实类型就三种:Activity,Service,Application三种,那么这三种类型之间在功能上有什么异同?

启动Activity

启动和停止Service

发送广播消息(Intent)

注册广播消息(Intent)接收者

可以访问APK中各种资源(如Resources和AssetManager等)

可以访问Package的相关信息

APK的各种权限管理

上图已经做了标注各自的Context能做什么,而ContentProvider和BroadCastReceiver之所以也在是因为在其内部也存在一个Context供使用。

对于No1和No2需要说明:

1.No1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。

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

由于Context的具体功能都是由ContextImpl去实现的,而Activity,Service,Application这三种Context类的绝大多数功能最终都是通过ContextImpl来实现,所以它们的能力在绝大多数情况下是通用的。

但是处于安全考虑,Android对于一个Activity的启动必须建立在另一个Activity之上,因为要形成返回栈。而对于ShowDialog也限制在必须在Activity之上弹出(系统的Dialog除外)。

APP中的Context总数:

前面已经说过Context的类型就三种:Application,Activity,Service。

那么一个应用程序有多少个Context也就显而易见的了:

Context总数 = Application(1) + Activity(n) + Service(n)

Context获取:

获取的示例如下:

//获取当前Activity的上下文,这样获取的上下文对象可以绑定Activity的生命周期
1.Activity中的this,或者Activity.this

//获取当前应用Application的上下文
2.Application.getApplicationContext()//上下文对象绑定Application的生命周期
  Application.getApplication

//从Context所属的上下文访问这个Context
3.ContextWrapper.getBaseContext()

//获取当前View所在的上下文
4.View.getContext()
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main2);
    Log.e(TAG, "                   this====="+this);
    Log.e(TAG, "     Main2Activity.this====="+Main2Activity.this);
    Log.e(TAG, "       getBaseContext()====="+getBaseContext());
    Log.e(TAG, "       getApplication()====="+getApplication());
    Log.e(TAG, "getApplicationContext()====="+getApplicationContext());
}

输出日志如下:

获取Application上下文的两个方法:

前面有提到过Application也是上下文的一种,那么通过getApplication和getApplicationContext这两者所获取的上下文之间又有什么区别?

从上面的输出日志我们可以看出到两个方法返回的实质上是同一个对象,至于原因暂时先不做讨论。

getApplication():

该方法由Activity或者Service提供,返回的是拥有当前Activity或Service的Application。

//代码位置:android.app.Activity.java
/** Return the application that owns this activity. */
public final Application getApplication() {
    return mApplication;
}

getApplicationContext()

/**
* Return the context of the single, global Application object of the
* current process.  This generally should only be used if you need a
* Context whose lifecycle is separate from the current context, that is
* tied to the lifetime of the process rather than the current component.
*/
public abstract Context getApplicationContext();

该方法返回的是当前进程的全局上下文对象,这个方法使用场景是需要一个上下文与当前上下文的生命周期相分离的时候,因为这个方法获取到的上下文是与整个应用进程的生命周期相关的,而不是仅仅局限于当前上下文的生命周期。

比如说,在注册广播接收器的时候,如果使用的是Activity的上下文,这意味着,这个接收器会在该Activity被销毁时一同撤销注册,而如果使用getApplicationContext返回的上下文对象,广播接收器将会同Application联系在一起,也就不会被用户的操作所移除。

两个方法返回的都是Application上下文对象,但是区别在于两个方法的作用域是不同的,getApplication方法只能在Activity或者Service中使用,但是需要用到Application上下文的地方却不仅仅局限于Activity或者Service,比如说广播接收器。

Context使用注意:

Context如果使用不恰当很容易引起内存泄露问题。

最简单的例子比如说引用了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;
    }
}

上述代码中,我们使得了一个静态对象持有Context对象,而静态数据的生命一般是长于普通数据的,因此当Context被销毁(例如假设这里持有的是Activity的上下文对象,当Activity被销毁的时候),因为instance仍然持有Context的引用,导致Context虽然被销毁了但是却无法被GC机制回收,因为造成内存泄露问题。

而一般因为Context所造成的内存泄漏,基本上都是Context已经被销毁后,却因为被引用导致GC回收失败。但是Application的Context对象却会随着当前进程而一直存在,所以使用Context是应该注意:

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

参考博文:

https://www.jianshu.com/p/94e0f9ab3f1d

https://www.cnblogs.com/jingmengxintang/p/7889311.html

https://my.oschina.net/youranhongcha/blog/1807189

https://blog.csdn.net/qq_23547831/article/details/46481725

https://blog.csdn.net/guolin_blog/article/details/47028975

最新文章

  1. adb server is out of date. killing...
  2. $.getJSON('url',function(data){}) 中回调函数不执行
  3. MXNet符号编程
  4. 银河英雄传说 (codevs 1540) 题解
  5. Liferay JSP中常用的标签
  6. Sublime Text 中使用Git插件连接GitHub
  7. Ecmall系统自带的分页功能
  8. JAVA数据类型与DB2、Oracle、Sybase以及SQL Server对应关系
  9. Altium Designer规则
  10. 字符函数库 - cctype 和 climits 中的符号常量
  11. jquery template.js前端模板引擎
  12. jquery.validata.js 插件2
  13. applicationContext.xml最基本配置文件
  14. 第二周c语言PTA作业留
  15. redis-sentinel高可用配置(2)
  16. 关于linux上部署定时python脚本
  17. 【golang-GUI开发】QSS的使用(一)———QSS入门指南
  18. 聊一聊啥都不会的我自学Linux系统的历程
  19. 利用Python 脚本生成 .h5 文件 代码
  20. Python Django框架笔记(二):创建应用和django 管理

热门文章

  1. 使用aspx 直接生成excel
  2. java0429 wen 数据库
  3. python操作pymysql数据库
  4. idea maven指定编译参数
  5. SQL语句基本语法总结
  6. ORACLE中INSERT插入多条数据
  7. 浅谈RESTful
  8. 移动namenode、secondarynamenode和jobTracker的节点(使其成为独立节点)
  9. ES6常用语法(上)
  10. [math]本博客已经支持书写数学公式