Android Context 笔记整理

本文详细解析了Android中Context的概念,包括其子类层级、不同作用域的用途以及生命周期。介绍了Context在Android应用中的核心作用,如何正确使用Activity、Service和Application的Context,以及避免常见错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文 大部分内容摘抄自: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 则与对应的组件的生命周期有关。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值