Android Context 笔记整理

本文 大部分内容摘抄自:https://lrh1993.gitbooks.io/android_interview_guide/content/android/basis/context.html

源码版本为 Android API 25


1、概述

Context 这一名词中文意思为 “上下文,背景”,在 Android 中,其作为一个包含运行时环境信息的上下文接口,它允许访问特定的应用程序的资源和类,以及整合了系统级的操作服务。

在这里插入图片描述
截图自源码

2、子类层级

Android 中的 Context 类是一个抽象类,定义一些抽象方法,以及常量。

其子类 ContextWrapper ,是一个包装类,其内部本身实现的抽象方法,没有实际的逻辑,而是通过内部持有 Context mBase 类间接调用 mBase 的对应方法来实现的(如下面源码中的 startActivity() 方法)。

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

	...
}    

在 Android 中,具体有三类 Context:Activity、Service、Application。

在这里插入图片描述

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 中的方法。

比如,Application,其在实例化的时候,就会通过 Application#attach() 来绑定一个对应的 ContextImpl 实例。

// LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    ...
    Application app = null;
    
    try {
        java.lang.ClassLoader cl = getClassLoader();
        if (!mPackageName.equals("android")) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                    "initializeJavaContextClassLoader");
            initializeJavaContextClassLoader();
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
        // 创建 ContextImpl 实例 
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        // 创建 Application 实例
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        appContext.setOuterContext(app);
    } catch (Exception e) {
       ...
    }
    ...
    return app;
}

通过 mActivityThread.mInstrumentation.newApplication() 方法,最终会来到 Instrumentation.newApplication() 方法:

// Instrumentation.java
public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    return newApplication(cl.loadClass(className), context);
}

static public Application newApplication(Class<?> clazz, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    Application app = (Application)clazz.newInstance();
    app.attach(context);
    return app;
}

// Application.java
/* package */ final void attach(Context context) {
    attachBaseContext(context);
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

可以看到,除了会新建一个 Application 实例,还会通过其 attach() 方法将之前新建且传递过来的 ContextImpl 实例给绑定。

2、不同 Context 的作用域

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

在这里插入图片描述
Application 和 Service 所不推荐的两种使用情况:

  • 如果我们用 Application Context 去启动一个 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。

    但是这里我在 Android API 25 的源码版本下,使用 Application、Service 类型的 Context,都能够正常启动一个 standard 类型的 Activity,且没有设置 FLAG_ACTIVITY_NEW_TASK,但是并没有报错,估计是高级版本的源码进行了修改,对这种情况进行相应的处理。

  • 在 Application 和 Service 中去 layout inflate 也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。

一句话总结:凡是跟UI相关的,都应该使用 Activity 做为 Context 来处理;其他的一些操作,Service, Activity, Application 等实例都可以,当然了,注意 Context 引用的持有,防止内存泄漏。

另外,在 Service 中启动一个 Dialog 是可以,但是是系统级的,这种dialog不响应home键和返回键,即强制用户必须对dialog作出操作。且涉及到 android.permission.SYSTEM_ALERT_WINOW 权限(即显示在其他应用之上),当 API Level >= 23 时需要动态申请。Application Context 同理。具体实现自己搜索。

3、补充

(1)一个应用程序有几个 Context ?

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

只有 Activity,Service 持有 Context,而 Broadcast Receiver,Content Provider 并不是 Context 的子类,他们所持有的 Context 都是其他地方传过去的,所以并不计入 Context 总数。

(2)Context 能干什么?

这个就实在是太多了,弹出 Toast、启动 Activity、启动 Service、发送广播、操作数据库等等都需要用到Context。

(3)Application Context 的生命周期伴随着应用,而 Activity Context、Service Context 则与对应的组件的生命周期有关。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值