Android学习之路(14) Context详解

news2024/11/25 8:01:12

一. 简介

在 Android 开发中、亦或是面试中都离不开四大组件的身影,而在创建或启动这些组件时,并不能直接通过 new 关键字后跟类名来创建实例对象,而是需要有它们各自的上下文环境,也就是本篇文章要讨论的 Context。

1.1 Context 概述

Context,字面意思:语境、环境、上下文,在 Android 系统中,可以理解为当前对象在应用程序中所处的工作环境。其内部定义很多访问应用程序环境中全局信息的接口,通过它可以访问到应用程序的资源有关的类,如:Resources、AssetManager、Package 及权限相关信息等。还可以通过它调用应用程序级的操作,如:启动 Activity 和 Service、发送广播等。

再来看一下官方对于 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 {...}

翻译:Context 提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被 Android 系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。

1.2 Context 体系结构

  • Context:是一个抽象类,定义一系列与系统交互的接口。
  • ContextWrapper:继承自 Context 抽象类,是 Context 类的包装类(装饰器模式),内部维护一个 Context 类型的成员变量 mBase 指向一个 ContextImpl 对象,ContextWrapper 里面的方法调用是通过 mBase 来调用 ContextImpl 里面的方法,这里用到了代理模式。
  • ContextImpl:继承自 Context 抽象类,实现了 Context 类中的抽象方法,是 Context 类的具体实现类。它为 Activity 及其它应用组件提供上下文环境,应用中使用到的 Context 的方法就是其实现的。
  • ContextThemeWrapper:继承自 ContextWrapper 类,在 ContextWrapper 的基础上增加与主题 Theme 相关的逻辑,即可以指定 Theme 的 Context 包装类,用于在 View 构造时为其提供 Theme 属性集。

ContextImpl 实现类中涉及的主要核心类是:ActivityThread、LoadedApk、PackageManager 和 ResourcesManager,这几个类都是单例的,一个应用程序进程中是共用同一个对象的。
Contextlmpl 是一种轻量级类,而 LoadedApk 是一个重量级类,Contextlmpl 中的大多数进行包操作的重量级函数实际上都是转向了 LoadedApk 对象相应的方法。

Activity 继承自 ContextThemeWrapper,Application、Service 继承自 ContextWrapper,它们直接或间接的继承自 ContextWrapper 类,因此也拥有了一个 Context 类型的成员变量 mBase 指向一个 ContextImpl 对象,ContextImpl 是 Context 类的具体实现类,所以也都拥有了 Context 提供的所有功能。

代理模式:属于结构型模式,是指为其他对象提供一种代理以控制对这个对象的访问,代理模式又分为静态代理和动态代理。
装饰器模式:又叫包装模式,也是结构型模式,是指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。

1.3 Context 作用域

注意:需要加载布局显示界面的,尽可能的使用 Activity 作为 Context 域,虽然加载布局、启动 Activity 可以使用 Application 和 Service 作为 Context 域,但是不推荐,以免报错或 UI 莫名的使用系统默认的主题 Theme 来展示。

1.4 总结

通过本小节的分析,对 Context 的概念、体系结构及作用域都有了简单的了解,那么接下来就来深入探索吧!

二. Context 详解

前面讲到 Context 的体系结构时,了解到其最终实现类有:Application、Service 和 Activity,它们都持有 ContextImpl 这个 Context 抽象类的真正实现,接下来对这三个实现类分别进行讨论分析。

2.1 Application Context

Application 是 Android 系统框架中的一个系统组件,当 Android 应用程序启动时系统会创建一个 Application 类的对象且只创建一个,用来存储系统的一些信息,即 Application 是单例的。

通常在开发过程中是不需要指定一个 Application 的,系统自动帮开发者创建,如果要创建应用自定义的 Application,只需创建一个类继承 Application 并在 AndroidManifest.xml 文件中的 application 标签中进行注册(只需给 application 标签增加 name 属性,并添加自定义的 Application 的名字即可)。

通常自定义 Application 的目的是在应用程序启动时做一些全局的初始化工作,当应用程序启动时,Application 同步创建并启动,系统会创建⼀个 PID,即进程ID,所有的 Activity 都会在此进程上运⾏,因此都可以取到这些初始化的全局变量的值,且由于 Application 对象在整个应用程序运行期间会一直存在,有开发者就会在 Application 中编写一些工具方法,全局获取使用,但是切记不要这样把 Application 当工具类使用。注意:这严重违背 Google 设计 Application 的原则,也违背设计模式中的单一职责原则。

2.1.1 自定义 Application 实例

open class TestApplication : Application() {
    // 全局 context
    companion object{
        lateinit var context: Context
    }
    override fun onCreate() {
        super.onCreate()
        context = this
        initSDKs() // 全局初始化
    }

    private fun initSDKs() {...}
}

继承 Application 并重写 onCreate() 方法,在 Application 创建的时候调用,一般用于全局初始化,如第三方 SDK 的初始化、环境的配置等等,同时可以通过 TestApplication # context 来获取 Application 类型的全局 Context 对象。

2.1.2 获取 Application 实例

class TestActivity : Activity() {
    private val TAG: String = TestActivity::class.java.simpleName
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)

        val applicationContext = TestActivity@this.getApplicationContext()
        val application = TestActivity@this.getApplication()

        Log.e(TAG, "application: $application")
        Log.e(TAG, "applicationContext: $applicationContext")
    }
}

获取 Application 的方法一般有两个:

  • Activity # getApplication() 或 Service # getApplication()
  • Context # getApplicationContext()

通过 getApplication() 和 getApplicationContext() 都可以获取到 Application,那它们区别是什么呢?

通过日志输出可以看到,它们获取到的是同一个对象,但有同学要问了,那为什么还要提供两个功能一样的方法?因为 getApplication() 方法更加直观,但只能在 Activity 和 Service 场景中调用。getApplicationContext() 方法适用范围更广,任意场景中通过 Context 对象皆可以调用此方法。

2.1.3 Application Context 创建过程

Application 的 Context 是在应用被创建的时候创建的,要追踪其创建需要从应用程序的启动流程出发来探索,即从点击桌面应用图标开始到应用第一个界面展示出来的过程中的某一步,具体哪一步创建的,可以参考这篇文章的详细分析-- 深度详解 Android R(11.0)Activity 启动过程。

简述一下过程:

  • ActivityThread 类作为应用初始化类,在其入口方法 main() 方法中调用 ActivityThread # attach() 方法中,然后通过 Binder 通信跨进程调用到 system_server 进程中 AMS 的 attachApplication() 方法,并将 ApplicationThread 作为参数传递过去。
  • 通过传进来的 ApplicationThread,跨进程通信调用应用进程中 ApplicationThread # bindApplication() 方法绑定 Application。
  • ApplicationThread # bindApplication() 方法中,构建 AppBindData 对象,然后通过内部类 H 发送 BIND_APPLICATION 类型的 Handler 消息,进而调用到 ActivityThread # handleBindApplication() 方法创建并绑定 Application。

2.1.4 时序图

面试题:ActivityThread 是不是一个 Thread?
ActivityThread 类是应用初始化类,它的 main() 方法是应用的入口方法,它也是我们说的“主线程”,但是 ActivityThread 本身不是一个线程,之所以称它为“主线程”,是因为它运行在主线程中。所以说 ActivityThread 是主线程的一部分,但不并能代表主线程。其作用如下:

  • ActivityThread 负责创建 Application 对象以及管理其生命周期方法调用。
  • ActivityThread 管理着四大组件的生命周期方法调用。

2.1.5 源码解析

通过过程简述与时序图可知,Application 的 Context 的创建是在 ActivityThread # handleBindApplication() 方法中创建的,跟踪查看源码进行详细解析。

2.1.5.1 ActivityThread # handleBindApplication()

ActivityThread.class (api 30)
public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {
    ......
 	@UnsupportedAppUsage
    private void handleBindApplication(AppBindData data) {
		......
        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
        updateLocaleListFromAppContext(appContext,
                mResourcesManager.getConfiguration().getLocales());
        ......
        if (ii != null) {
            ......
            final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
                    appContext.getClassLoader(), false, true, false);
            final ContextImpl instrContext = ContextImpl.createAppContext(this, pi,
                    appContext.getOpPackageName());
            try {
            	// 获取 ClassLoader 加载类文件
                final ClassLoader cl = instrContext.getClassLoader();
                // 获取 Instrumentation 类并构建实例对象
                mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
            }
			......
            final ComponentName component = new ComponentName(ii.packageName, ii.name);
            mInstrumentation.init(this, instrContext, appContext, component,
                    data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
			......
        } 
        ......
        Application app;
		......
        try {
        	// 创建 Application
            app = data.info.makeApplication(data.restrictedBackupMode, null);
			......
            mInitialApplication = app;
			......
            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            }
            ......
            try {
           		// 内部调用 Application # onCreate() 的方法
                // 故 Application # onCreate() 比 ActivityThread 的 main() 方法慢执行
                // 但是会比所有该应用 Activity 的生命周期先调用,因为此时的 Activity 还没启动
                mInstrumentation.callApplicationOnCreate(app);
            }
            ......
        }
    }
    ......
}

ActivityThread # handleBindApplication() 方法的参数 AppBindData 是 AMS 传给应用程序的启动信息,其中包含 LoadedApk、ApplicationInfo 等,然后通过 LoadedApk 实例对象创建 ContextImpl 和 Application 实例对象。

2.1.5.2 LoadedApk # makeApplication()

LoadedApk.java  (api 30)
public final class LoadedApk {
	......
   @UnsupportedAppUsage
    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
       		// 如果 mApplication 已经存在则直接返回
            return mApplication;
        }
		......
        Application app = null;
        // 获取 AndroidMenifest 中 application 标签指定的 Application 类
        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
        	// 获取 ClassLoader
            final java.lang.ClassLoader cl = getClassLoader();
            ......
            // 创建 ContextImpl 实例
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            ......
            // 利用类加载器 ClassLoader 创建 AndroidMenifest 指定的 Application 类
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
			// 将 Application 实例赋值给 ContextImpl,以便 ContextImpl 可以访问 Application
            appContext.setOuterContext(app);
        }
        ......
        mActivityThread.mAllApplications.add(app);
        // app 赋值给 mApplication,当我们调用 Context.getApplicationContext() 就是获取这个对象
        mApplication = app;
        if (instrumentation != null) {
            try {
            	// 由于 instrumentation 此时为空所以不会回调 Application 的 onCreate 方法
                instrumentation.callApplicationOnCreate(app);
            }
            ......
        }
		......
        return app;
    }
    ......
}

执行流程如下:

  • 首先判断 LoadedApk 对象中的 mApplication 是否存在,如果已经存在则直接返回。如果不存在,则先获取 AndroidMenifest 中 application 标签中 name 属性指定的 Application 类名。然后获取 ClassLoader,创建 ContextImpl 实例。
  • 通过类加载器 ClassLoader 创建 Application 类实例,并将 Application 实例赋值给 ContextImpl 的成员变量 mOuterContext,以便 ContextImpl 通过 mOuterContext 访问 Application 实例。同时将 Application 实例赋值给 mApplication,调用 Context # getApplicationContext() 方法获取的就是该实例对象。

2.1.5.3 ContextImpl 创建

ContextImpl.java (api 30)
class ContextImpl extends Context {
	......
    @UnsupportedAppUsage
    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        return createAppContext(mainThread, packageInfo, null);
    }

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
            String opPackageName) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, null,
                0, null, opPackageName);
        context.setResources(packageInfo.getResources());
        context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);
        return context;
    }
    ......
}

创建一个 ContextImpl 实例对象,同时给 ContextImpl 赋值访问系统资源相关的“权限”对象 – ActivityThread、LoadedApk 等。

2.1.5.4 Application 创建

回头继续看 LoadedApk # makeApplication() 的 Application 类实例的创建,代码如下:

Instrumentation.java (api 30)
public class Instrumentation {
	......
    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
		// 获取 LoadedApk 的 AppComponentFactory,然后通过 ClassLoader 
		// 加载 Application 类,并创建类的实例对象
        Application app = getFactory(context.getPackageName())
                .instantiateApplication(cl, className);
		// 将 ContextImpl 实例绑定到 Application 实例对象的 mBase 成员变量
        app.attach(context);
        return app;
    }

    private AppComponentFactory getFactory(String pkg) {
        ......
        LoadedApk apk = mThread.peekPackageInfo(pkg, true);
        // This is in the case of starting up "android".
        if (apk == null) apk = mThread.getSystemContext().mPackageInfo;
        return apk.getAppFactory();
    }
    ......
}

AppComponentFactory.java (api 30)
public class AppComponentFactory {
	......
    public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
            @NonNull String className)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		// 通过 ClassLoader 加载 Application 类,并创建类的实例对象
        return (Application) cl.loadClass(className).newInstance();
    }
    ......
}

获取 LoadedApk 的 AppComponentFactory,然后通过 ClassLoader 加载 Application 类,并创建类的实例对象。接下来将 ContextImpl 实例赋值给创建的 Application 实例对象的 mBase 成员变量。

2.1.5.5 Application 绑定 ContextImpl

Application.java (api 30)
public class Application extends ContextWrapper implements ComponentCallbacks2 {
	......
    @UnsupportedAppUsage
    /* package */ final void attach(Context context) {
    	// 这里 context 为 ContextImpl 实例对象
    	// 调用 ContextWrapper # attachBaseContext() 方法
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }
    ......
}

ContextWrapper.java (api 30)
public class ContextWrapper extends Context {
	......
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        // 将 ContextImpl 实例对象赋值给成员变量 mBase
        mBase = base;
    }
    ......
}

在 Application 的 attach() 方法中调用 ContextWrapper 的 attachBaseContext() 方法,将 ContextImpl 实例对象赋值给其成员变量 mBase。

2.2 Service Context

Service 是 Android 系统框架中四大组件的其中之一,它是一种可以在后台执行长时间运行操作而没有用户界面的应用组件。可由其他应用组件启动(如:Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity)已销毁也不受影响,接下来分析其 Context 实例的由来。

2.2.1 Service Context 创建过程

Service 的 Context 是在 Service 被其他应用组件启动的时候创建的(如:Activity 中启动),这里不详细分析 Service 的启动过程,也不是本篇文章的重点。具体可以参考这篇文章的详细分析-- 深度详解 Android R(11.0)Service 启动过程。

简述一下过程:

  • 通常都是调用 Context # startService() 方法启动 Service,通过上面的分析可知,这里将调用其实现类 ContextImpl # startService() 方法,然后通过 Binder 通信跨进程调用到 system_server 进程中 AMS 的 startService() 方法,在系统进程中经过一系列调用后,流程走到 ApplicationThread 的 scheduleCreateService() 方法,在方法中将 AMS 进程中创建的 ServiceInfo 等封装成 CreateServiceData 对象。
  • ApplicationThread # scheduleCreateService() 方法中将 CreateServiceData 实例对象通过内部类 H 发送 CREATE_SERVICE 类型的 Handler 消息,进而调用到 ActivityThread # handleCreateService() 方法创建 Service,同时创建 Service 的 Context。

2.2.2 时序图

2.2.3 源码解析

通过过程简述与时序图可知,Service 的 Context 的创建是在 ActivityThread # handleCreateService() 方法中创建的,跟踪查看源码进行详细解析。

2.2.3.1 ActivityThread # handleCreateService()

ActivityThread.class (api 30)
public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {
    ......
    @UnsupportedAppUsage
    private void handleCreateService(CreateServiceData data) {
		......
		// 获取 LoadedApk 实例对象,用于获取类加载器 ClassLoader 等
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
  			......
  			// 创建 ContextImpl 实例,即 Service 的上下文环境 Context[参见 2.1.5.3 节]
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            // 创建 Application 实例[参见 2.1.5.2 节]
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            // 获取 ClassLoader 实例对象
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            // 加载 Service 类并创建实例对象
            service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);
			......
            context.getResources().addLoaders(
                    app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
			// 将 Service 实例赋值给 ContextImpl,以便 ContextImpl 可以访问 Service
            context.setOuterContext(service);
            // 将 ContextImpl 实例绑定到 Service 实例对象的 mBase 成员变量
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
			// 回调 Service # onCreate() 方法
            service.onCreate();
            // 将 service 对象加入到 mService 集合中,key 值为 data.token
            mServices.put(data.token, service);
            try {
            	// 跨进程调用 AMS # serviceDoneExecuting() 方法通知 AMS,Service 已经启动完毕
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } 
        ......
    }
    ......
}

执行流程如下:

  • 获取 LoadedApk 实例对象,用于获取类加载器 ClassLoader 等,然后创建一个 ContextImpl 实例对象,即 Service 的上下文环境,同时给 ContextImpl 赋值访问系统资源相关的“权限”对象 – ActivityThread、LoadedApk 等。
  • 获取类加载器 ClassLoader 实例对象,并使用它加载 Service 类并创建实例对象,将创建的 Service 实例赋值给 ContextImpl 的成员变量 mOuterContext,以便 ContextImpl 通过 mOuterContext 访问 Service 实例。
  • 调用 Service # attach() 方法将 ContextImpl 实例绑定到 Service 实例对象的 mBase 成员变量,然后回调 Service # onCreate() 方法,最后通过跨进程调用 AMS # serviceDoneExecuting() 方法通知 AMS,Service 已启动完毕。

2.2.3.2 Service 绑定 ContextImpl

Service.java (api 30)
public abstract class Service extends ContextWrapper implements ComponentCallbacks2,
        ContentCaptureManager.ContentCaptureClient {
	......
    @UnsupportedAppUsage
    public final void attach(
            Context context,
            ActivityThread thread, String className, IBinder token,
            Application application, Object activityManager) {
		// 继续调用 Service # attachBaseContext() 方法
        attachBaseContext(context);
        ......
        setContentCaptureOptions(application.getContentCaptureOptions());
    }
    
    @Override
    protected void attachBaseContext(Context newBase) {
    	// 调用父类 ContextWrapper # attachBaseContext() 方法
        super.attachBaseContext(newBase);
        if (newBase != null) {
            newBase.setContentCaptureOptions(getContentCaptureOptions());
        }
    }
    ......
}

ContextWrapper.java (api 30)
public class ContextWrapper extends Context {
	......
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        // 将 ContextImpl 实例对象赋值给成员变量 mBase
        mBase = base;
    }
    ......
}

在 Service 的 attach() 方法中继续调用 Service # attachBaseContext() 方法,然后继续调用父类 ContextWrapper # attachBaseContext() 方法将 ContextImpl 实例对象赋值给成员变量 mBase。

2.3 Activity Context

Activity 是 Android 系统框架中四大组件中使用频率最多的,是用来给用户展示内容的界面,并与用户直接进行交互的组件。日常开发中 Activity 的 Context 会被经常用到,如通过 Context 启动新的 Activity、启动 Service 及注册广播接收器等,下面一起来看一下 Activity 的 Context 实例的由来。

2.3.1 Activity Context 创建过程

Activity 的 Context 是在 Activity 组件启动的时候创建的,这里不详细分析 Activity 的启动过程,也不是本篇文章的重点,感兴趣的同学可以参考这篇文章的详细分析-- 深度详解 Android R(11.0)Activity 启动过程。

简述一下过程:

  • 通常是调用 Activity # startActivity() 方法来启动 Activity,然后通过 Binder 通信跨进程调用到 system_server 进程中 ATMS 的 startActivity() 方法,在系统进程中经过一系列调用后,流程走到 ApplicationThread 的 scheduleTransaction() 方法。
  • ApplicationThread # scheduleTransaction() 方法中根据生命周期状态,来调度启动 Activity 的事务 LaunchActivityItem,在 LaunchActivityItem # execute() 方法中调用到 ActivityThread # handleLaunchActivity() 方法来创建并启动 Activity,同时创建 Activity 的 Context。

Android P(9.0) 开始 Activity 启动及生命周期有关的逻辑,被解耦成多个 Transaction 事务(如:LaunchActivityItem、ResumeActivityItem 等),通过 ClientLifecycleManager 来调度事务的执行。

2.3.2 时序图

2.3.3 源码解析

通过过程简述与时序图可知,Activity 的 Context 的创建是在 ActivityThread # handleLaunchActivity() 方法中创建的,跟踪查看源码进行详细解析。

2.3.3.1 ActivityThread # handleLaunchActivity()

ActivityThread.java (api 30)
public final class ActivityThread extends ClientTransactionHandler {
	......
    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
        ......
        final Activity a = performLaunchActivity(r, customIntent);
        ......
        return a;
    }
    ......
}

继续调用 ActivityThread # performLaunchActivity() 执行 Activity 的启动,继续跟踪启动流程。

2.3.3.2 ActivityThread # performLaunchActivity()

ActivityThread.java (api 30)
public final class ActivityThread extends ClientTransactionHandler {
	......
     /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
        	// 获取 LoadedApk 实例对象,用于获取类加载器 ClassLoader 等
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }
    	......
		// 创建 ContextImpl 实例,即 Activity 的上下文环境 Context
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
        	// 通过类加载器 ClassLoader 加载并新建 Activity 的实例
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ......
        } catch (Exception e) {
            ......
        }
        try {
        	// 创建 Application,注意 r.packageInfo 是前面获取的 LoadedApk 实例对象
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
			......
            if (activity != null) {
                ......
                appContext.getResources().addLoaders(
                        app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
                // 将 Activity 实例赋值给 ContextImpl,以便 ContextImpl 可以访问 Activity
                appContext.setOuterContext(activity);
                // 执行 Activity 的 attach、初始化 Window 等并把 ContextImpl 实例设置给 Activity
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
                ......
                activity.mCalled = false;
                // 执行 Activity 的 onCreate
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                ......
            }
            // 设置生命周期的状态为 ON_CREATE
            r.setState(ON_CREATE);
            synchronized (mResourcesManager) {
            	// 将包含 Activity 信息集的 r 对象,也就是 ActivityClientRecord
            	// 加入到 mActivities 中,r.token 为 key 值
                mActivities.put(r.token, r);
            }
        }
		......
        return activity;
    }
    ......
}

执行流程如下:

  • 获取 LoadedApk 实例对象,用于获取类加载器 ClassLoader 等,继续调用 ActivityThread # createBaseContextForActivity() 方法,该方法中调用 ContextImpl # createActivityContext() 方法创建 ContextImpl 实例对象,即 Activity 的上下文环境 Context。
  • 调用 Instrumentation # newActivity() 方法加载并新建 Activity 实例对象,该方法中调用 AppComponentFactory # instantiateActivity() 方法,然后通过在 ActivityThread # performLaunchActivity() 方法中获取的类加载器 ClassLoader 加载并新建 Activity 实例对象。
  • 通过 LoadApk # makeApplication() 方法创建一个 Application 对象,过程跟加载并新建 Activity 类似,用到类加载器 ClassLoader。
  • 执行 Activity # attach() 方法,通过该方法将 ContextImpl 实例设置给 Activity,除此之外,方法中还完成了 Window 实例的创建并建立自己和 Window 的关联,这样当 Window 接收到外部输入事件后就可以将事件传递给 Activity。
  • 执行 Instrumentation # callActivityOnCreate() 方法,该方法中调用 Activity # performCreate() 方法,Activity # performCreate() 方法中调用 Activity # onCreate() 方法。

2.3.3.3 ActivityThread # createBaseContextForActivity()

ActivityThread.java (api 30)
public final class ActivityThread extends ClientTransactionHandler {
	......
	private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        ......
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
		......
        return appContext;
    }
    ......
}

ContextImpl.java (api 30)
class ContextImpl extends Context {
	......
    @UnsupportedAppUsage
    static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
		......
		// 通过 LoadedApk 获取类加载器 ClassLoader
        ClassLoader classLoader = packageInfo.getClassLoader();
        ......
        // 创建 Activity 的 ContextImpl 实例对象
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null,
                activityInfo.splitName, activityToken, null, 0, classLoader, null);
        ......
        return context;
    }
    ......
}

只看主流程有关源代码,调用 ContextImpl # createActivityContext() 创建 Activity 的 Context。

至于主流程中 Activity 和 Application 的加载并新建过程感兴趣的可以跟进源码查看,主要是由类加载器 ClassLoader 加载后新建实例对象,下面主要来查看 Activity 绑定 Context 的流程。

2.3.3.4 Activity # attach()

ContextImpl.java (api 30)
public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2, ...... {
	......
	@UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,...) {
        // 将 ContextImpl 实例绑定到 Activity 实例对象的 mBase 成员变量
        attachBaseContext(context);
		......
		// 新建 PhoneWindow 实例
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ......
        mUiThread = Thread.currentThread();
        ......
        mWindowManager = mWindow.getWindowManager();
		......
    }
	......
    @Override
    protected void attachBaseContext(Context newBase) {
    	// 继续调用父类的 attachBaseContext() 方法
        super.attachBaseContext(newBase);
        if (newBase != null) {
            newBase.setAutofillClient(this);
            newBase.setContentCaptureOptions(getContentCaptureOptions());
        }
    }
    ......
}

ContextThemeWrapper.java (api 30)
public class ContextThemeWrapper extends ContextWrapper {
	......
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
    }
    ......
}

ContextWrapper.java (api 30)
public class ContextWrapper extends Context {
	......
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    ......
}

在 Activity 的 attach() 方法中继续调用 Activity # attachBaseContext() 方法,然后继续调用父类 ContextThemeWrapper # attachBaseContext() 方法,由于 ContextThemeWrapper 继承自 ContextWrapper,因此继续调用 ContextWrapper # attachBaseContext() 方法将 ContextImpl 实例对象赋值给成员变量 mBase。

2.4 总结

通过源码的深入分析可知,Application、Service 和 Activity 直接或间接的继承自 ContextWrapper 类,因此也都拥有了一个 Context 类型的成员变量 mBase 指向一个 ContextImpl 对象,ContextImpl 是 Context 类的具体实现类,所以它们也就都拥有了 Context 提供的获取应用环境全局信息的接口功能。

三. Context 补充知识

前面的章节分析了 Application、Service 和 Activity 等组件的创建以及绑定 ContextImpl 实例对象的流程,有同学会问了,四大组件中的 BroadcastReceiver 和 ContentProvider 创建的过程中没有绑定 ContextImpl 实例对象吗?

其实它们也有绑定的,只不过不是自身继承自 Context,其 Context 实例是需要通过前面所述三者来提供,大致来看一下源码。

3.1 BroadcastReceiver 获取 Context 实例

开发中,通常是通过调用 Context # registerReceiver() 方法来注册广播接收器,这里的 Context 根据注册广播接收器时的场景可以是前面所述三者的任意一个来提供,这里以 Activity 场景中注册为例,调用 Context # registerReceiver() 方法注册,由上面的分析可知,此时会调用到 Context 的实现类 ContextImpl # registerReceiver() 方法中,代码如下:

ContextImpl.java (api 30)
class ContextImpl extends Context {
	......
    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        return registerReceiver(receiver, filter, null, null);
    }
    
    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
            String broadcastPermission, Handler scheduler) {
        return registerReceiverInternal(receiver, getUserId(),
                filter, broadcastPermission, scheduler, getOuterContext(), 0);
    }
    ......
    @UnsupportedAppUsage
    final Context getOuterContext() {
        return mOuterContext;
    }
    ......
}

注册广播接收器时,继续调用 ContextImpl # registerReceiverInternal() 方法并传入当前所处的上下文环境 - 即 Context,这里通过 ContextImpl # getOuterContext() 获取该 Context 实例,这个方法是不是看着很熟悉,在前面 2.1.5.2 、2.2.3.1 及 2.3.3.2 小节中,通过 ContextImpl # setOuterContext() 方法为其赋值的,这也验证了上面的解析,Context 实例的获取是根据注册广播接收器时所处的场景来决定到底获取的是前面所述三者中的哪一个。

3.2 ContentProvider 获取 Context 实例

ContentProvider 是四大组件中被使用频率最低的一个,通常用来做跨进程共享数据,它是伴随着应用程序的启动由系统创建的,但它本身不属于 Context 体系结构,因此创建 ContentProvider 实例时所用的 Context 实例需要由别处获得。既然这样那就先看看在应用程序启动过程中的哪里创建的 ContentProvider 实例?

3.2.1 时序图

应用程序启动的流程这里不做详细解读,可以参考这篇文章的详细分析-- 深度详解 Android R(11.0)Activity 启动过程。应用程序在创建并绑定 Application 后,通过 ActivityThread # installContentProviders() 方法来创建并绑定 Context 的实例,一起探索源码来验证一下。

3.2.2 源码分析

3.2.2.1 ActivityThread # handleBindApplication()


ActivityThread.class (api 30)
public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {
    ......
 	@UnsupportedAppUsage
    private void handleBindApplication(AppBindData data) {
        ......
        Application app;
		......
        try {
        	// 创建 Application
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            ......
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                	// 创建 ContentProvider 实例,注意入参是 Application 实例
                    installContentProviders(app, data.providers);
                }
            }
            ......
            try {
           		// 内部调用 Application # onCreate() 的方法
                mInstrumentation.callApplicationOnCreate(app);
            }
            ......
        }
    }
    
    @UnsupportedAppUsage
    private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();

        for (ProviderInfo cpi : providers) {
            ......
            // 继续调用 installProvider() 方法创建 ContentProvider 实例
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }
        try {
        	// 将 ContentProvider 列表发布到 AMS 中目的是进行缓存
        	// 其它应用进程想要获取它的 ContentProvider 的时候可以直接在缓存中遍历获取
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

	@UnsupportedAppUsage
    private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;
        if (holder == null || holder.provider == null) {
            ......
            Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            if (context.getPackageName().equals(ai.packageName)) {
            	// 包名相同即同一应用内,则使用入参 Application 作为 Context
                c = context;
            }
			......// 根据不同使用场景,获取对应场景下的 Context 实例
            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
                ......
                // 内部通过 ClassLoader 加载并新建 ContentProvider 实例对象
                localProvider = packageInfo.getAppFactory()
                        .instantiateProvider(cl, info.name);
                provider = localProvider.getIContentProvider();
                ......
                // XXX Need to create the correct context for this provider.
                // 为 ContentProvider 设置合适的上下文环境 - Context
                localProvider.attachInfo(c, info);
            }
            ......
        } 
        ......
    }
    ......
}

在 ActivityThread # handleBindApplication() 方法中,调用 ActivityThread # installContentProviders() 方法并传入创建好的 Application 实例对象,继续调用 ActivityThread # installProvider() 方法来创建 ContentProvider 实例对象,创建过程跟上面分析的差不多,通过类加载器 ClassLoader 加载并新建 ContentProvider 实例对象,最后调用 ContentProvider # attachInfo() 方法为 ContentProvider 设置合适的上下文环境 - Context。

3.2.2.2 ContentProvider # attachInfo()

ContentProvider.class (api 30)
public abstract class ContentProvider implements ContentInterface, ComponentCallbacks2 {
    ......
    public void attachInfo(Context context, ProviderInfo info) {
        attachInfo(context, info, false);
    }

    private void attachInfo(Context context, ProviderInfo info, boolean testing) {
        mNoPerms = testing;
        mCallingPackage = new ThreadLocal<>();
        // 这里只允许设置一次,因此 ContentProvider 创建交付使用后,客户端不能再更改它
        if (mContext == null) {
            mContext = context;
            ......
            // 回调 ContentProvider # onCreate() 方法
            ContentProvider.this.onCreate();
        }
    }
    ......
}

ContentProvider # attachInfo() 方法中将 Context 实例对象赋值给 ContentProvider 的成员变量 mContext,这样 ContentProvider 就可以使用 Context 提供的获取应用环境全局信息的接口功能,而这个 Context 也正是一开始 ActivityThread # handleBindApplication() 方法中传进来的 Application 实例对象(注意:成员变量 mContext 只允许设置一次)。

注意:在 ContentProvider # installProvider() 方法中会根据不同使用场景,获取对应场景下的 Context 实例对象,我们这里是分析的是同一个应用程序内,所以 ContentProvider 的成员变量 mContext 被赋值为传进来的 Application 实例对象。如果跨进程或者通过 Intent # setPackage() 指定了其它应用的包名等,则需要获取对应场景下的 Context 实例对象。

3.3 总结

本节内容补充了四大组件中 BroadcastReceiver 和 ContentProvider 是如何获取到 Context 实例对象的,它们虽是系统组件,但不是 Context 体系结构中的一员,但身为系统组件,它们同样需要用到 Context 所提供的获取应用环境全局信息的接口功能,因此抱着深入学习的态度,还是细细的把源码流程研读了一遍,梳理其创建及获取 Context 实例对象的流程。

四. 总结

结合本文的讲解和源码解析,这里来看一下那些面试中问到过的问题,加深一下理解。

问题一:Android 系统中一个应用程序中 Context 的个数?

通过本文的详解可知,在 Context 体系结构中,Application 、Activity 和 Service 在创建实例对象的同时,都会创建一个 ContextImpl 实例对象,并赋值给它们父类 ContextWrapper 的成员变量 mBase,由于子类对象拥有父类对象中所有的属性和方法,因此在 Application 、Activity 和 Service 实例对象中可以通过成员变量 mBase 获取 ContextImpl 实例对象。也就是说每个 Activity 和 Service 都有一个 Context,而每个应用程序中 Application 由于是唯一的,所以 Android 系统中一个应用程序中 Context 的个数 = Activity 的个数 + Service 的个数 + 1。

问题二:Context 会导致内存泄露吗?

一般 Context 导致的内存泄漏,几乎都是当 Context 销毁的时候,却因为被引用导致 GC 销毁失败,而 Application 的 Context 对象可以理解为随着应用进程存在的,所以这里总结给出使用 Context 时的一些建议:

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

问题三:getContext(),getBaseContxet(),getApplication() 及 getApplicationContext() 的区别?

在文章中已经解析过,getApplication() 和 getApplicationContext() 这俩个方法获取到的是同一个实例对象,只是使用场景范围的不同。getApplication() 方法更加直观,但只能在 Activity 和 Service 场景中调用。getApplicationContext() 方法适用范围更广,任意场景中通过 Context 对象皆可以调用此方法。那为何是同一个对象呢?简单看一下源码:

Activity.class (api 30)
public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,... {
	......
	@UnsupportedAppUsage
    private Application mApplication;
    
    public final Application getApplication() {
        return mApplication;
    }
    @UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread,...
            Application application, ...) {
        ......
        mApplication = application;
		......
    }
}

Service.class (api 30)
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
	......
	@UnsupportedAppUsage
    private Application mApplication;
    
    public final Application getApplication() {
        return mApplication;
    }
    @UnsupportedAppUsage
    public final void attach(
            Context context,...
            Application application, Object activityManager) {
        ......
        mApplication = application;
        ......
    }
}

首先看到 getApplication() 方法返回的是 Activity 和 Service 调用 attach() 方法时传入的 Application 实例对象。还记得文章前面的分析不,Activity # attach() 方法的调用,参见 2.3.3.2 ActivityThread # performLaunchActivity(),而 Service # attach() 方法的调用,参见 2.2.3.1 ActivityThread # handleCreateService() ,在这两个方法中传给 attach() 方法的 Application 实例对象都是通过 LoadedApk # makeApplication() 方法来创建获取的。

再来看一下 getApplicationContext() 方法的返回值,虽然调用到 ContextWrapper,但最终还是委托给实现类 ContextImpl 中实现的,源码如下:

ContextImpl.java (api 30)
class ContextImpl extends Context {
	......
	@UnsupportedAppUsage
    final @NonNull ActivityThread mMainThread;
    @UnsupportedAppUsage
    final @NonNull LoadedApk mPackageInfo;
    
    @Override
    public Context getApplicationContext() {
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : mMainThread.getApplication();
    }
    ......
}

这里根据 mPackageInfo 是否为空,分别调用了mPackageInfo # getApplication() 方法和mMainThread # getApplication() 方法,那就来看看这两个方法,首先在 LoadedApk 中看一下 getApplication() 方法的返回值,代码如下:


LoadedApk.java (api 30)
public final class LoadedApk {
	......
	@UnsupportedAppUsage
    private Application mApplication;

    Application getApplication() {
        return mApplication;
    }

	@UnsupportedAppUsage
    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }
		......
        Application app = null;
		......
        try {
        	......
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        }
        ......
        mApplication = app;
		......
        return app;
    }
    ......
}

首先判断 LoadedApk 中的 mApplication 是否为空 (保证对象的单例),不为空直接返回,如果为空的话新建了一个 Application 实例对象然后赋值给 mApplication。接着在 ActivityThread 中看一下 getApplication() 方法的返回值,代码如下:

ActivityThread.java (api 30)
public final class ActivityThread extends ClientTransactionHandler {
	......
    @UnsupportedAppUsage
    Application mInitialApplication;
    
    @UnsupportedAppUsage
    public Application getApplication() {
        return mInitialApplication;
    }
    ......
    @UnsupportedAppUsage
    private void handleBindApplication(AppBindData data) {
		......
        Application app;
        try {
        	// data.info 是 LoadedApk 类
            app = data.info.makeApplication(data.restrictedBackupMode, null);
			......
            mInitialApplication = app;
			......
        }
	......
}

ActivityThread # getApplication() 方法返回的 mInitialApplication 也是 LoadedApk # makeApplication() 方法返回的,所以可以得出 getApplicationContext() 方法在上述两种情况下返回的是同一个 Application 实例对象。

由于 getApplication() 方法返回的 Application 实例对象也是通过 LoadedApk # makeApplication() 方法来创建获取的,所以说 getApplication() 和 getApplicationContext() 这俩个方法返回的是同一个 Application 实例对象。

getContext()、getBaseContxet() 和 getApplicationContext() 方法的区别?

首先 getBaseContxet() 方法获取的是前面分析的赋值给 mBase 的 Context 的实现类的实例对象。getApplicationContext() 方法返回的 LoadedApk # makeApplication() 方法创建的 Application 实例对象。并且 Application、Activity 和 Service 都有 getBaseContxet() 和 getApplicationContext() 这两个方法。而 getContext() 方法是在 Fragment 或 View 中用来获取其宿主对象的。

五,参考

  1. 全面解析之Context机制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/995711.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

股票量化系统QTYX选股框架实战案例集|华为补涨龙三连板打板进,套利8个点离场-230908...

前言 “实战案例个股画像”系列和大家分享我基于QTYX选股框架&#xff0c;在实战中选股的案例&#xff0c;和大家一起见证QTYX选股框架逐步完善的过程&#xff0c;帮助大家理解QTYX的精髓。 关于QTYX的使用攻略可以查看链接&#xff1a;QTYX使用攻略 关于QTYX初衷和精髓可以查看…

《阿里大数据之路》读书笔记:第三章 数据同步

第三章 数据同步 数据同步技术含义&#xff1a;不同系统间的数据流转&#xff0c;有多种不同的应用场景。 应用场景&#xff1a; 同类型不同集群数据库之间的数据同步主数据库与备份数据库之间的数据备份主系统与子系统之间的数据更新不同地域、不同数据库类型之间的数据传输…

解决ul元素不能跟div同一行显示的办法

现象如下&#xff1a; html结构如下&#xff1a; 可以看到div和ul是同级元素。 为什么这里ul换行了呢&#xff01; 这里要敲黑板了&#xff01; 因为ul是块级元素&#xff01;也就是独占一行&#xff0c;跟div一样。 如果需要ul跟div在同一行显示&#xff0c;则要求ul前面相…

2023国赛高教社杯数学建模C题思路分析

1 赛题 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差&#xff0c; 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&#xff0c; 商超通常会根据各商品的历史销售和需 求情况每天进行补货。 由于商超销售的蔬菜…

【论文笔记】Perception, Planning, Control, and Coordination for Autonomous Vehicles

单纯作为阅读笔记&#xff0c;文章内容可能有些混乱。 文章目录 1. Introduction2. Perception3. Planning3.1. Autonomous Vehicle Planning Systems3.2. Mission Planning3.3. Behavioral Planning3.4. Motion Planning3.4.1. Combinatorial Planning3.4.2. Sampling-Based P…

python将手机模拟器截屏并发送至电脑上

电脑上安装手机模拟器&#xff0c;截手机屏幕图片&#xff0c;发送至电脑上&#xff0c;&#xff08;继而进一步操作&#xff0c;比如图文识别等&#xff09;。环境&#xff1a;python3.10.4 模拟器&#xff1a;雷电模拟器 模拟器安装路径&#xff0c;我的是&#xff1a;D:\lei…

新AI技术革命:向左走,向右走

前两天阅读到一篇博文&#xff0c;主要是讲海外创业者初创公司的产品情况&#xff0c;整理出来分享给你&#xff0c;如果你也在关注 AI 领域&#xff0c;不妨对照一下。 回归本篇内容正题。 时不时会有朋友问我&#xff1a;有没有一个工具&#xff0c;可以自动完成这系列的工作…

第13章_瑞萨MCU零基础入门系列教程之Common SPI

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

Spark【Spark SQL(二)RDD转换DataFrame、Spark SQL读写数据库 】

从 RDD 转换得到 DataFrame Saprk 提供了两种方法来实现从 RDD 转换得到 DataFrame&#xff1a; 利用反射机制推断 RDD 模式使用编程方式定义 RDD 模式 下面使用到的数据 people.txt &#xff1a; Tom, 21 Mike, 25 Andy, 18 1、利用反射机制推断 RDD 模式 在利用反射机制…

知乎热议!大学开学了,女儿去了浙江上大学,两千生活费够不够?

大家好&#xff0c;我是菜哥&#xff01; 又到了周末闲聊时间&#xff0c;这几天微博上一个热搜引起了广大网友的关注&#xff0c;那就是“大学开学了&#xff0c;女儿在浙江上大学&#xff0c;给两千生活费够不够&#xff1f;”。 这个话题引起了广泛讨论&#xff0c;很多人都…

C++中的红黑树

红黑树 搜索二叉树搜索二叉树的模拟实现平衡搜索二叉树(AVL Tree)平衡搜索二叉树的模拟实现红黑树(Red Black Tree)红黑树的模拟实现 红黑树的应用(Map 和 Set)Map和Set的封装 搜索二叉树 搜索二叉树的概念&#xff1a;二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&…

计算机网络与技术——概述

&#x1f60a;计算机网络与技术——概述 &#x1f47b;前言&#x1f94f;信息时代下计算机网络的发展&#x1f30f;互联网概述&#x1f4e1;计算机网络基本概念&#x1f4e1;互联网发展三阶段&#x1f4e1;互联网的标准化 &#x1f30f;互联网的组成&#x1f4e1;互联网的边缘部…

企业ERP和泛微OA集成场景分析

轻易云数据集成平台&#xff08;qeasy.cloud&#xff09;为企业ERP和泛微OA系统提供了强大的互通解决方案&#xff0c;特别在销售、采购和库存领域的单据审批场景中表现出色。这些场景涉及到多个业务单据的创建和审批&#xff0c;以下是一些具体的应用场景描述&#xff1a; 采购…

C++ STL教程

C 标准模板库的核心包括&#xff1a;&#xff08;1&#xff09;容器&#xff08;Containers&#xff09;&#xff1b;&#xff08;2&#xff09;算法&#xff08;Algorithms&#xff09;&#xff1b;&#xff08;3&#xff09;迭代器&#xff08;iterators&#xff09; &#…

第一章 计算机系统概述 一、操作系统的基本概念

一、操作系统的层次结构 二、定义 操作系统是计算机系统中最基本、最重要的软件之一&#xff0c;它是计算机硬件与用户之间的桥梁。它的主要功能是管理计算机系统的资源&#xff0c;包括处理器、内存、外部设备以及数据等。 操作系统的基本概念包括&#xff1a; 资源管理&…

在Postman的脚本中使用pm对象获取接口的请求参数

在Postman的脚本中使用pm对象获取接口的请求参数 1、获取在Query Params中输入的参数全局变量的引用&#xff08;以在header中引用为例&#xff09;2、获取在Body中输入的参数3、pm对象常用用法 1、获取在Query Params中输入的参数 query params页面 在tests中写脚本做后置处…

探索 AI+开源的未来:Open Source Congress@日内瓦

注&#xff1a;本文翻译源自 Linux 基金会发布的 Open Source Congress 会议官网内容&#xff0c;蓝色斜字体的段落则为作者参与会议的记录与心得。 Note: This article was translated from the official website of the Linux Foundations Open Source Congress, and the par…

【MySQL系列】MySQL的事务管理的学习(二)_ 再次理解隔离性

「前言」文章内容大致是MySQL事务管理&#xff0c;续上一篇。 「归属专栏」MySQL 「主页链接」个人主页 「笔者」枫叶先生(fy) 目录 七、再次理解隔离性7.1 数据库并发的场景有7.2 多版本并发控制&#xff08;MVCC&#xff09;7.3 三个隐藏字段列7.4 undo日志7.5 模拟MVCC7.6 R…

[Latex]公式编辑,编号、对齐【持】

导言区 \documentclass{article} \usepackage{amsmath,amssymb,amsfonts,}%math-数学公式&#xff1b;symb-数学符号&#xff1b;fonts-字号&#xff1b;环境是否进入数学模式是否接受可选参数是否占满整行是否产生编号备注align是否是是align* 不产生编号&#xff0c;其他与 …

阿里云oss上传视频测试,出现了413错误

阿里云oss上传视频测试&#xff0c;出现了413错误 &#xff08;1&#xff09;nginx抛出问题&#xff0c;请求体过大 &#xff08;2&#xff09;修改nginx配置&#xff0c;重新加载生效 client_max_body_size 1024m;在cmd下运行命令&#xff1a;nginx.exe -s reload