【42】android Context深度剖析

android程序和java程序的区别

Android程序不像Java程序一样,随便创建一个类,写个main()方法就能跑了,而是要有一个完整的Android工程环境,在这个环境下,我们有像Activity、Service、BroadcastReceiver等系统组件,而这些组件并不是像一个普通的Java对象new一下就能创建实例的了,而是要有它们各自的上下文环境,也就是我们这里讨论的Context。可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。

Context是什么?

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

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

Context的子类结构

Context提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类,Android提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动Activity,发广播,接受Intent等)。

Context类本身是一个纯abstract类,它有两个具体的实现子类:ContextImpl和ContextWrapper。其中ContextWrapper类,如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。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中的方法。

一个程序包含几个Context:

Context一共有Application、Activity和Service三种类型,因此一个应用程序中Context数量的计算公式就可以这样写:

Context数量 = Activity数量 + Service数量 + 1  

Application Context的设计:

基本上每一个应用程序都会有一个自己的Application,并让它继承自系统的Application类,然后在自己的Application类中去封装一些通用的操作。

获取Application

public class MainActivity extends Activity {  

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyApplication myApp = (MyApplication) getApplication();
        Log.d("TAG", "getApplication is " + myApp);
    }  

}  

getApplication()方法和getApplicationContext()方法的联系

public class MainActivity extends Activity {  

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyApplication myApp = (MyApplication) getApplication();
        Log.d("TAG", "getApplication is " + myApp);
        Context appContext = getApplicationContext();
        Log.d("TAG", "getApplicationContext is " + appContext);
    }  

}  

打印出的结果是一样的呀,连后面的内存地址都是相同的,看来它们是同一个对象。

为什么?

Application本身就是一个Context,所以这里获取getApplicationContext()得到的结果就是MyApplication本身的实例。

际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了,如下所示:

public class MyReceiver extends BroadcastReceiver {  

    @Override
    public void onReceive(Context context, Intent intent) {
        MyApplication myApp = (MyApplication) context.getApplicationContext();
        Log.d("TAG", "myApp is " + myApp);
    }  

}

getBaseContext()方法

除了这两个方法之外,其实还有一个getBaseContext()方法,这个baseContext又是什么东西呢?

getBaseContext()方法得到的是一个ContextImpl对象。这个ContextImpl是不是感觉有点似曾相识?回去看一下Context的继承结构图吧,ContextImpl正是上下文功能的实现类。也就是说像Application、Activity这样的类其实并不会去具体实现Context的功能,而仅仅是做了一层接口封装而已,Context的具体功能都是由ContextImpl类去完成的。

ContextWrapper类的attachBaseContext()方法,这个方法中传入了一个base参数,并把这个参数赋值给了mBase对象。而attachBaseContext()方法其实是由系统来调用的,它会把ContextImpl对象作为参数传递到attachBaseContext()方法当中,从而赋值给mBase对象,之后ContextWrapper中的所有方法其实都是通过这种委托的机制交由ContextImpl去具体实现的,所以说ContextImpl是上下文功能的实现类是非常准确的。

使用Application注意的问题

public class MyApplication extends Application {  

    public MyApplication() {
        String packageName = getPackageName();
        Log.d("TAG", "package name is " + packageName);
    }  

}

报错!

public class MyApplication extends Application {  

    @Override
    public void onCreate() {
        super.onCreate();
        String packageName = getPackageName();
        Log.d("TAG", "package name is " + packageName);
    }  

}  

ContextWrapper中有一个attachBaseContext()方法,这个方法会将传入的一个Context参数赋值给mBase对象,之后mBase对象就有值了。而我们又知道,所有Context的方法都是调用这个mBase对象的同名方法,那么也就是说如果在mBase对象还没赋值的情况下就去调用Context中的任何一个方法时,就会出现空指针异常,上面的代码就是这种情况。

Context能干什么

弹出Toast、启动Activity、启动Service、发送广播、操作数据库等等都需要用到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);

Context作用域

由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

如何获取Context:

1:View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。

2:Activity.getApplicationContext,(或者getApplication())获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。

3:ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。

4:Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。

Context的内存泄露的原因:

1.静态Ui控件引起的

静态的Drawable持有Activity的Context

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

单例引起的内存泄露

我们使用饿汉式初始化单例,AppSettings我们需要持有一个Context作为成员变量,

public class AppSettings {
    private Context mAppContext;
    private static AppSettings sInstance = new AppSettings();

    //some other codes
    public static AppSettings getInstance() {
      return sInstance;
    }

    public final void setup(Context context) {
        mAppContext = context;
    }
}

sInstance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,当我们进行屏幕旋转,默认情况下,系统会销毁当前Activity,然后当前的Activity被一个单例持有,导致垃圾回收器无法进行回收,进而产生了内存泄露。

解决的方法就是不持有Activity的引用,而是持有Application的Context引用。代码如下修改

public final void setup(Context context) {
    mAppContext = context.getApplicationContext();
}

Context如何避免内存泄露:

不要让生命周期长于Activity的对象持有到Activity的引用

尽量使用Application的Context而不是Activity的Context

尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用(具体可以查看细话Java:”失效”的private修饰符了解)。如果使用静态内部类,将外部实例引用作为弱引用持有。

欢迎入群:

公众号IT面试题汇总讨论群

如果扫描不进去,加我微信(rdst6029930)拉你。

欢迎关注《IT面试题汇总》微信订阅号。每天推送经典面试题和面试心得技巧,都是干货!

微信订阅号二维码如下:

参考博客:

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

http://www.jianshu.com/p/94e0f9ab3f1d?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=weixin_timeline&from=timeline&isappinstalled=1

http://droidyue.com/blog/2015/04/12/avoid-memory-leaks-on-context-in-android/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

时间: 2024-10-12 10:53:44

【42】android Context深度剖析的相关文章

[Android] Toast问题深度剖析(二)

欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 作者: QQ音乐技术团队 题记 Toast 作为 Android 系统中最常用的类之一,由于其方便的api设计和简洁的交互体验,被我们所广泛采用.但是,伴随着我们开发的深入,Toast 的问题也逐渐暴露出来. 本系列文章将分成两篇: 第一篇,我们将分析 Toast 所带来的问题 第二篇,将提供解决 Toast 问题的解决方案 (注:本文源码基于Android 7.0) 1.回顾 上一篇 [[Android] Toast问题深度剖析(一)]

Android 事件处理全面剖析

篇外话:先来说下今天的日期,今天是2015年02月18日也就是大年三十,大家都在欢欢喜喜的准备过大年,活动也各式各样,搓麻将.打扑克.放烟花.准备看春晚,而我却还在敲代码,我只想说身为程序员的我们,真的屌丝的不能再屌丝了.虽然很屌丝,但我在这里还是要给大家拜个年,祝大家羊年喜气洋洋.写出来的代码少 bug.产品少改需求!当然当大家看到这篇 blog 的时候已经过完了年,因为我写这篇 blog 是在家里,而我家里木有网络,所以还得等到到了工作之地才能放出来,好了,回归正题. Android 事件处

LCD深度剖析

原文出自:方杰|http://fangjie.sinaapp.com/?p=184转载请注明出处 最终效果演示:http://fangjie.sinaapp.com/?page_id=54 该项目代码已经放到github:https://github.com/JayFang1993/SinaWeibo 一.首先是ListView的adapter. 因为微博列表的Item不是规则的,比如说有些微博有转发子微博,有些没有,有些有图片,有些没有图片,所以说很不固定.这里就采用BaseAdapter,要

Android Fragment 深度解析

Android Fragment 深度解析 有过一些面试经验的人基本都深有体会,每次面试一般都会问到Fragment的知识,所以,今天我就单独把Fragment拿出来与大家分享一下. 会涉及到Fragment如何产生,什么是Fragment,Fragment生命周期,如何静态和动态使用Fragment,Fragment回 退栈,Fragment事务,以及Fragment的一些特殊用途,例如:没有布局的Fragment有何用处?Fragment如何与Activiy交 互?Fragment如何创建对

【渗透课程】第二篇下-HTTP协议的请求与响应深度剖析

[渗透课程]第二篇下-HTTP协议的请求与响应深度剖析 HTTP1.1目前支持以下7种请求方法: 常见的MIME类型如下: 第一个数字有五种可能的取值: 目录 什么是请求方法?什么是请求头? HTTP请求信息由3部分组成: 1.请求方法(GET/POST) URI 协议/版本 2.请求头(Request Header) 3.请求正文 下面我们来分析一个http请求: POST http://xg.mediportal.com.cn/health/sms/verify/telephone HTTP

Android示例程序剖析之记事本(一)

Android SDK提供了很多示例程序,从这些示例代码的阅读和试验中能够学习到很多知识.本系列就是要剖析Android记事本示例程序,用意就是一步步跟着实例进行动手操作,在实践中体会和学习Android开发.该系列共有四篇文章,本文是第一篇. 前期准备 搭建开发环境,尝试编写"Hello World”,了解Android的基本概念,熟悉Android的API(官方文档中都有,不赘述). 记事本程序运行界面 先来简单了解下程序运行的效果: 程序入口点 类似于win32程序里的WinMain函数,

Android Context 是什么?

andorid 开发(42)  版权声明:本文为博主原创文章,未经博主允许不得转载. [转载请注明出处:http://blog.csdn.net/feiduclear_up CSDN 废墟的树] PS:修该了一些有误区的地方. 引言 Context对于Android开发人员来说并不陌生,项目中我们会经常使用Context来获取APP资源,创建UI,获取系统Service服务,启动Activity,绑定Service,发送广播,获取APP信息等等.那么Context到底是什么?Context又是怎

Android LayoutInflater深度解析 给你带来全新的认识

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38171465 , 本文出自:http://blog.csdn.net/lmj623565791/article/details/38171465 1. 题外话 相信大家对LayoutInflate都不陌生,特别在ListView的Adapter的getView方法中基本都会出现,使用inflate方法去加载一个布局,用于ListView的每个Item的布局.Inflate有三个

Objective-C类成员变量深度剖析

目录 Non Fragile ivars 为什么Non Fragile ivars很关键 如何寻址类成员变量 真正的“如何寻址类成员变量” Non Fragile ivars布局调整 为什么Objective-C类不能动态添加成员变量 总结 看下面的代码,考虑Objective-C里最常见的操作之一——类成员变量访问. - (void)doSomething:(SomeClass *)obj { obj->ivar1 = 42; // 访问obj对象的public成员变量 int n = sel