Android学习之路(14) Context详解

一. 简介

在 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() {// 全局 contextcompanion object{lateinit var context: Context}override fun onCreate() {super.onCreate()context = thisinitSDKs() // 全局初始化}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.simpleNameoverride 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 ClientTransactionHandlerimplements ActivityThreadInternal {......@UnsupportedAppUsageprivate 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 {// 创建 Applicationapp = 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 {......@UnsupportedAppUsagepublic 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 {// 获取 ClassLoaderfinal 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 可以访问 ApplicationappContext.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 {......@UnsupportedAppUsagestatic 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 实例对象赋值给成员变量 mBasemBase = 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 ClientTransactionHandlerimplements ActivityThreadInternal {......@UnsupportedAppUsageprivate 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 可以访问 Servicecontext.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.tokenmServices.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 {......@UnsupportedAppUsagepublic final void attach(Context context,ActivityThread thread, String className, IBinder token,Application application, Object activityManager) {// 继续调用 Service # attachBaseContext() 方法attachBaseContext(context);......setContentCaptureOptions(application.getContentCaptureOptions());}@Overrideprotected 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 实例对象赋值给成员变量 mBasemBase = 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 {......@Overridepublic 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 的上下文环境 ContextContextImpl 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 可以访问 ActivityappContext.setOuterContext(activity);// 执行 Activity 的 attach、初始化 Window 等并把 ContextImpl 实例设置给 Activityactivity.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 的 onCreateif (r.isPersistable()) {mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);} else {mInstrumentation.callActivityOnCreate(activity, r.state);}......}// 设置生命周期的状态为 ON_CREATEr.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 {......@UnsupportedAppUsagestatic ContextImpl createActivityContext(ActivityThread mainThread,LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,Configuration overrideConfiguration) {......// 通过 LoadedApk 获取类加载器 ClassLoaderClassLoader 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 ContextThemeWrapperimplements LayoutInflater.Factory2, ...... {......@UnsupportedAppUsagefinal 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();......}......@Overrideprotected 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 {......@Overrideprotected 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 {......@Overridepublic Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {return registerReceiver(receiver, filter, null, null);}@Overridepublic Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,String broadcastPermission, Handler scheduler) {return registerReceiverInternal(receiver, getUserId(),filter, broadcastPermission, scheduler, getOuterContext(), 0);}......@UnsupportedAppUsagefinal 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 ClientTransactionHandlerimplements ActivityThreadInternal {......@UnsupportedAppUsageprivate void handleBindApplication(AppBindData data) {......Application app;......try {// 创建 Applicationapp = 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);}......}}@UnsupportedAppUsageprivate 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();}}@UnsupportedAppUsageprivate 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 作为 Contextc = 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 设置合适的上下文环境 - ContextlocalProvider.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 ContextThemeWrapperimplements LayoutInflater.Factory2,... {......@UnsupportedAppUsageprivate Application mApplication;public final Application getApplication() {return mApplication;}@UnsupportedAppUsagefinal void attach(Context context, ActivityThread aThread,...Application application, ...) {......mApplication = application;......}
}Service.class (api 30)
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {......@UnsupportedAppUsageprivate Application mApplication;public final Application getApplication() {return mApplication;}@UnsupportedAppUsagepublic 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 {......@UnsupportedAppUsagefinal @NonNull ActivityThread mMainThread;@UnsupportedAppUsagefinal @NonNull LoadedApk mPackageInfo;@Overridepublic Context getApplicationContext() {return (mPackageInfo != null) ?mPackageInfo.getApplication() : mMainThread.getApplication();}......
}

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


LoadedApk.java (api 30)
public final class LoadedApk {......@UnsupportedAppUsageprivate Application mApplication;Application getApplication() {return mApplication;}@UnsupportedAppUsagepublic 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 {......@UnsupportedAppUsageApplication mInitialApplication;@UnsupportedAppUsagepublic Application getApplication() {return mInitialApplication;}......@UnsupportedAppUsageprivate 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.mzph.cn/news/78297.shtml

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

相关文章

线性回归方程

性回归是利用数理统计中的回归分析来确定两种或两种以上变数间相互依赖的定量关系的一种统计分析方法&#xff0c;是变量间的相关关系中最重要的一部分&#xff0c;主要考查概率与统计知识&#xff0c;考察学生的阅读能力、数据处理能力及运算能力&#xff0c;题目难度中等&…

uniapp 下拉框数据回显的问题

问题 : 现在是下拉框数据回显不了, 绑定的v-model 原因 : uniui 下拉框数据绑定要是 value text 这种格式的 解决办法: 将获取到的后端数据 转换为 需要的格式 ,再进行绑定 下拉框的数据 遍历

【操作系统】进程的概念、组成、特征

概念组成 程序&#xff1a;静态的放在磁盘&#xff08;外存&#xff09;里的可执行文件&#xff08;代码&#xff09; 作业&#xff1a;代码&#xff0b;数据&#xff0b;申请&#xff08;JCB&#xff09;&#xff08;外存&#xff09; 进程&#xff1a;程序的一次执行过程。 …

电脑磁盘清理怎么做?2个方法,轻松释放内存!

“我电脑里的垃圾文件好像太多了&#xff0c;想给电脑做个全面的磁盘清理&#xff0c;我应该怎么操作呢&#xff1f;” 在计算机的使用中&#xff0c;定期进行电脑磁盘清理是至关重要的。这不仅可以帮助您释放宝贵的磁盘空间&#xff0c;还能提升系统性能和维护电脑的正常运行。…

Redis 核心数据结构理论解析

一、简述 redis是一个开源的使用C语言编写的一个kv存储系统&#xff0c;是一个速度非常快的非关系远程内存数据库。它支持包括String、List、Set、Zset、hash五种数据结构。 除此之外&#xff0c;通过复制、持久化和客户端分片等特性&#xff0c;用户可以很方便地将redis扩展…

Linux(下)

一、 对netstat的补充 1.进程管理 在杀死进程时&#xff0c;不可以杀死其他用户的进程。 查看指定进程时&#xff0c;下图的第二行 是ps -ef | grep tail 命令执行的进程 kill -9 进程号 也可以写作 kill -s 9 进程号 机器人&#xff1a; 2.查看主机状态 将进程的信息分三…

国家信息中心举办“数字政府建设暨数字安全技术”研讨会:海云安提出数字政府软件供应链安全解决方案

近日&#xff0c;由国家信息中心主办&#xff0c;复旦大学研究院承办的“数字政府建设暨数字安全技术研讨会”在义乌顺利召开。国家信息中心信息与网络安全部副主任禄凯&#xff0c;复旦大学党委常委、宣传部部长陈玉刚&#xff0c;义乌市委常委、常务副市长喻新贵为会议致辞。…

Apollo介绍和入门

文章目录 Apollo介绍配置中心介绍apollo介绍主流配置中心功能特性对比 Apollo简介 入门简单的执行流程Apollo具体的执行流程Apollo对象执行流程分步执行流程 核心概念应用&#xff0c;环境&#xff0c;集群&#xff0c;命名空间企业部署方案灰度发布全量发布 配置发布的原理发送…

使用共享 MVI 架构实现高效的 Kotlin Multiplatform Mobile (KMM) 开发

使用共享 MVI 架构实现高效的 Kotlin Multiplatform Mobile (KMM) 开发 文章中探讨了 Google 提供的应用架构指南在多平台上的实现。通过共享视图模型&#xff08;View Models&#xff09;和共享 UI 状态&#xff08;UI States&#xff09;&#xff0c;我们可以专注于在原生端…

leetcode 2. 两数相加

2023.9.14 这道题还是有点难度&#xff0c; 需要维护一个进位值&#xff0c;构造一个虚拟头节点dummy&#xff0c;用于结果的返回&#xff0c;还要构造一个当前节点cur&#xff0c;用于遍历修改新链表。 整体思路就是长度短的链表需要补0&#xff0c;然后两链表从头开始遍历相加…

GaussDB技术解读系列:运维自动驾驶探索

近日&#xff0c;在第14届中国数据库技术大会&#xff08;DTCC2023&#xff09;的GaussDB“五高两易”核心技术&#xff0c;给世界一个更优选择专场&#xff0c;华为云数据库运维研发总监李东详细解读了GaussDB运维系统自动驾驶探索和实践。 随着企业数字化转型进入深水区&…

string

目录 六、STL简介 (一)什么是STL (二)STL的版本 (三)STL六大组件 七、string (一)标准库中的string 1、string类 2、string常用的接口 1)string类对象的常见构造 2)string类对象的容量操作 3)string类对象的访问及遍历操作 4)string类对象的修改操作 5)string类非成…

帧结构的串行数据接收器——Verilog实现

用Verilog 实现一个帧结构的串行数据接收器&#xff1b; 串行数据输入为&#xff1a;NRZ数据加位时钟&#xff08;BCL&#xff09;格式&#xff0c;高位在前 帧结构为&#xff1a;8位构成一个字&#xff0c;64字构成一个帧。每帧的第一个字为同步字。同步字图案存储在可由CPU读…

9. xaml ComboBox控件

1.运行图像 2.运行源码 a.Xaml源码 <Grid Name="Grid1"><!--IsDropDownOpen="True" 默认就是打开的--><ComboBox x:Name="co

Spark集成hudi创建表报错

环境描述: hudi版本:0.13.1 spark版本:3.3.2 Hive版本:3.1.3 Hadoop版本:3.3.4 问题1: 描述:按照官方文档运行spark-sql创建spark的hudi表报错 建表语句: CREATE TABLE stg.spark_mor_test_01 (uuid string,name string,age int,ts …

useGetState自定义hooks解决useState 异步回调获取不到最新值

setState 的两种传参方式 1、直接传入新值 setState(options); const [state, setState] useState(0); setState(state 1); 2、传入回调函数 setState(callBack); const [state, setState] useState(0); setState((prevState) > prevState 1); // prevState 是改变之…

【网络教程】超越平凡:一文揭示SSH-keygen的神秘世界

SSH(Secure Shell)是一种网络协议,用于安全地连接到远程计算机。SSH-keygen 是 SSH 协议的一部分,用于生成、管理和转换身份验证密钥对。 SSH-keygen 命令的基本语法如下: ssh-keygen [选项]以下是 ssh-keygen 命令的一些常用选项和参数: -t:指定要生成的密钥类型。例如…

Python实现猎人猎物优化算法(HPO)优化Catboost分类模型(CatBoostClassifier算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 猎人猎物优化搜索算法(Hunter–prey optimizer, HPO)是由Naruei& Keynia于2022年提出的一种最新的…

基于BLIP-2的看图问答原理及实现

大型语言模型 (LLM) 最近获得了很大的关注&#xff0c;出现了许多流行的模型&#xff0c;如 GPT、OPT、BLOOM 等。 这些模型擅长学习自然语言&#xff0c;非常适合构建聊天机器人、编码助手、决策助手或翻译系统。 然而&#xff0c;他们缺乏其他模式的知识—例如&#xff0c;他…

GIS地图服务数据可视化

GIS地图服务数据可视化 OSM&#xff08;Open Street Map&#xff0c;开放街道地图&#xff09;Bing地图&#xff08;必应地图&#xff09;Google地图&#xff08;谷歌地图&#xff09; 地图服务数据可视化是根据调用的地图服务请求Web服务器端的地图数据&#xff0c;实现地图数…