写点什么

8 年老 Android 开发谈;Context 都没弄明白凭什么拿高薪?

用户头像
Android架构
关注
发布于: 11 小时前
  • 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;


...}


源码中的注释是这么来解释 Context 的:Context 提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被 Android 系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类,Android 提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动 Activity,发广播,接受 Intent 等)。既然上面 Context 是一个抽象类,那么肯定有他的实现类咯,我们在 Context 的源码中通过 IDE 可以查看到他的子类最终可以得到如下关系图:



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 的理解,从上面的关系图我们已经可以得出答案了,在应用程序中 Context 的具体实现子类就是:Activity,Service,Application。那么Context数量=Activity数量+Service数量+1。当然如果你足够细心,可能会有疑问:我们常说四大组件,这里怎么只有 Activity,Service 持有 Context,那 Broadcast Receiver,Content Provider 呢?Broadcast Receiver,Content Provider 并不是 Context 的子类,他们所持有的 Context 都是其他地方传过去的,所以并不计入 Context 总数。上面的关系图也从另外一个侧面告诉我们 Context 类在整个 Android 系统中的地位是多么的崇高,因为很显然 Activity,Service,Application 都是其子类,其地位和作用不言而喻。


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



从上图我们可以发现 Activity 所持有的 Context 的作用域最广,无所不能。因为 Activity 继承自 ContextThemeWrapper,而 Application 和 Service 继承自 ContextWrapper,很显然 ContextThemeWrapper 在 ContextWrapper 的基础上又做了一些操作使得 Activity 变得更强大,这里我就不再贴源码给大家分析了,有兴趣的童鞋可以自己查查源码。上图中的 YES 和 NO 我也不再做过多的解释了,这里我说一下上图中 Application 和 Service 所不推荐的两种使用情况。


1:如果我们用 ApplicationContext 去启动一个 LaunchMode 为 standard 的 Activity 的时候会报错android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?这是因为非 Activity 类型的 Context 并没有所谓的任务栈,所以待启动的 Activity 就找不到栈了。解决这个问题的方法就是为待启动的 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动的时候就为它创建一个新的任务栈,而此时 Activity 是以 singleTask 模式启动的。所有这种用 Application 启动 Activity 的方式不推荐使用,Service 同 Application。


2:在 Application 和 Service 中去 layout inflate 也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。一句话总结:凡是跟 UI 相关的,都应该使用 Activity 做为 Context 来处理;其他的一些操作,Service,Activity,Application 等实例都可以,当然了,注意 Context 引用的持有,防止内存泄漏。

如何获取 Context

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


1:View.getContext,返回当前 View 对象的 Context 对象,通常是当前正在展示的 Activity 对象。2:Activity.getApplicationContext,获取当前 Activity 所在的(应用)进程的 Context 对象,通常我们使用 Context 对象时,要优先考虑这个全局的进程 Context。3:ContextWrapper.getBaseContext():用来获取一个 ContextWrapper 进行装饰之前的 Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。4:Activity.this 返回当前的 Activity 实例,如果是 UI 控件需要使用 Activity 作为 Context 对象,但是默认的 Toast 实际上使用 ApplicationContext 也可以。

getApplication()和 getApplicationContext()

上面说到获取当前 Application 对象用 getApplicationContext,不知道你有没有联想到 getApplication(),这两个方法有什么区别?相信这个问题会难倒不少开发者。



程序是不会骗人的,我们通过上面的代码,打印得出两者的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,因为前面已经说过了,Application 本身就是一个 Context,所以这里获取 getApplicationContext()得到的结果就是 Application 本身的实例。那么问题来了,既然这两个方法得到的结果都是相同的,那么 Android 为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取 Application 实例的,但是这个方法只有在 Activity 和 Service 中才能调用的到。那么也许在绝大多数情况下我们都是在 Activity 或者 Service 中使用 Application 的,但是如果在一些其它的场景,比如 BroadcastReceiver 中也想获得 Ap


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


plication 的实例,这时就可以借助 getApplicationContext()方法了。


publicclassMyReceiverextendsBroadcastReceiver{


@OverridepublicvoidonReceive(Contextcontext,Intentintent){ApplicationmyApp=(Application)context.getApplicationContext();


}


}

Context 引起的内存泄露

但 Context 并不能随便乱用,用的不好有可能会引起内存泄露的问题,下面就示例两种错误的引用方式。


错误的单例模式


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 被销毁掉,但因为它的引用还存在于一个 Singleton 中,就不可能被 GC 掉,这样就导致了内存泄漏。


View 持有 Activity 引用

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
8年老Android开发谈;Context都没弄明白凭什么拿高薪?