Activity相关学习(二)

devtools/2025/2/11 8:52:31/

android1300_r83_0">Activity启动流程(基于android-13.0.0_r83)

整体流程

启动方式

Activity主要有三种方式

  • 从 Launcher 桌面上点击 App 图标启动一个App。
  • App 启动后,按 Home 键退回到 Launcher 界面,再点击 App 图标。
  • 同个应用内启动,如从 Activity1 跳转到 Activity2。
启动过程涉及到的进程
  • 要启动的Activity所在App 进程
  • SystemServer进程
  • Zygote
  • 启动目标Activity的进程,如Launcher

Activity启动过程跟系统交互主要是通过Binder,其中主要涉及两个server,一个是SystemServer中注册的ActivityTaskManagerService,另外一个是app进程中的ApplicationThread。

ActivityTaskManagerService
  • ActivityTaskManagerService(简称 ATMS)是 Android 系统中负责管理 Activity 和 Task 的核心服务之一,运行在system_server 进程中。
  • 从 Android 10(Q)开始,Google 对 Activity 和 Task 的管理机制进行了重构,将原本在 ActivityManagerService(AMS)中的部分功能拆分到了 ActivityTaskManagerService 中。
  • 负责管理 Activity 的生命周期、任务栈(Task)、多窗口模式(如分屏、自由窗口)等。
ATMS 的作用
  • Activity 生命周期管理: 负责 Activity 的启动、暂停、恢复、销毁等生命周期回调。
  • Task 管理: 管理任务栈(Task),包括 Task 的创建、销毁、重新排序等。
  • 多窗口模式: 支持分屏、画中画、自由窗口等多窗口模式的实现。
  • 启动模式(Launch Mode): 处理 Activity 的启动模式(如 standard、singleTop、singleTask、singleInstance)。
  • 任务栈导航: 管理任务栈的导航行为(如返回栈、最近任务列表)。
  • 与 WindowManagerService 协作: 与 WindowManagerService(WMS)协作,管理 Activity 的窗口显示。
ATMS 的相关架构
  • ActivityManagerService(AMS): 负责进程管理、内存管理等,与 ATMS 协作完成 Activity 的管理。
  • WindowManagerService(WMS): 负责窗口管理,与 ATMS 协作完成 Activity 的显示。
  • ActivityStack: 表示一个任务栈(Task),管理栈中的 Activity。
  • ActivityRecord: 表示一个 Activity 实例,包含 Activity 的状态信息。
ATMS 的核心功能
  1. Activity 生命周期管理
    ATMS 负责处理 Activity 的生命周期回调,当应用启动一个 Activity 时,ATMS 会调用相应的生命周期方法,并确保 Activity 的状态正确切换。包括:
  • onCreate()
  • onStart()
  • onResume()
  • onPause()
  • onStop()
  • onDestroy()
  1. Task 管理
    Task 是一个 Activity 的集合,通常表示一个用户任务(如打开一个应用)。ATMS 负责管理 Task 的创建、销毁和重新排序。
  • 当用户点击桌面图标启动应用时,ATMS 会创建一个新的 Task。
  • 当用户按下返回键时,ATMS 会销毁当前 Activity 并返回到上一个 Activity。
  1. 多窗口模式
    ATMS 支持多种多窗口模式, ATMS 负责管理多窗口模式下 Activity 的布局和生命周期。包括:
  • 分屏模式(Split-screen): 两个应用共享屏幕。
  • 画中画模式(Picture-in-Picture): 视频播放时以小窗口形式显示。
  • 自由窗口模式(Freeform): 类似于桌面操作系统的窗口模式。
  1. 启动模式(Launch Mode)

  2. 任务栈导航
    ATMS 管理任务栈的导航行为,包括:

  • 返回栈(Back Stack): 当用户按下返回键时,ATMS 会从返回栈中弹出 Activity。
  • 最近任务列表(Recent Tasks): ATMS 负责维护最近任务列表,用户可以快速切换任务。
ATMS 的实现

ATMS是通过AIDL实现的
在这里插入图片描述

ApplicationThread
  • ApplicationThread 是 ActivityThread 的内部类,扮演着应用进程与系统服务(如 ActivityManagerService 和 ActivityTaskManagerService)之间的桥梁角色。
  • ApplicationThread 是一个 Binder 对象,负责接收系统服务的指令并转发给应用进程的主线程(即 ActivityThread),从而实现对应用生命周期、Activity 生命周期等的管理。
ApplicationThread 的作用
  • 接收系统服务的指令: 接收来自 ActivityManagerService(AMS)和 ActivityTaskManagerService(ATMS)的指令,例如启动 Activity、启动 Service、绑定 Service、处理广播等。
  • 转发指令到主线程: 将系统服务的指令转发到应用的主线程(即 ActivityThread),确保这些操作在主线程中执行。
  • 与应用组件交互: 与应用的 Activity、Service、BroadcastReceiver 等组件交互,触发它们的生命周期方法(如 onCreate()、onStart()、onResume() 等)。
ApplicationThread 的核心方法

这些方法都是由系统服务(如 AMS 或 ATMS)调用,然后通过 Binder 机制传递到应用进程的 ApplicationThread,最终由 ActivityThread 在主线程中执行。

  • 启动 Activity: scheduleLaunchActivity()
  • 启动 Service: scheduleCreateService()
  • 绑定 Service: scheduleBindService()
  • 处理广播: scheduleReceiver()
  • 暂停 Activity: schedulePauseActivity()
  • 销毁 Activity: scheduleDestroyActivity()
ApplicationThread 的实现

ApplicationThread是基于AIDL实现
在这里插入图片描述

ActivityTaskManagerService和ApplicationThread调用关系

在这里插入图片描述

Launcher 界面中启动Activity的流程
流程

在这里插入图片描述

源码分析
  • 点击桌面图标会触发ItemClickHandler.java的onClick()。
/*
* @param v 被点击的视图对象(如应用图标、文件夹图标等)
**/
//packages/apps/Launcher3/src/com/android/launcher3/touch/ItemClickHandler.java
private static void onClick(View v) {...if (v.getWindowToken() == null) return;//Launcher 是 Launcher 应用的主 Activity,负责管理主屏幕、应用图标、小部件等。Launcher launcher = Launcher.getLauncher(v.getContext());if (!launcher.getWorkspace().isFinishedSwitchingState()) return;//Launcher 中的每个视图(如应用图标、文件夹图标等)都关联了一个 ItemInfo 对象,用于存储该视图的元数据(如应用包名、组件名、位置信息等)。Object tag = v.getTag();//点击的是应用图标if (tag instanceof WorkspaceItemInfo) {onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);} else if (tag instanceof FolderInfo) {//点击文件夹图标if (v instanceof FolderIcon) {onClickFolderIcon(v);}} else if (tag instanceof AppInfo) {//快捷方式startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);} else if (tag instanceof LauncherAppWidgetInfo) {//点击的是小部件if (v instanceof PendingAppWidgetHostView) {onClickPendingWidget((PendingAppWidgetHostView) v, launcher);}} else if (tag instanceof ItemClickProxy) {((ItemClickProxy) tag).onItemClicked(v);}}

点击应用图标执行下面方法

/*** Event handler for an app shortcut click.** @param v The view that was clicked. Must be a tagged with a {@link WorkspaceItemInfo}.*/public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher) {...// Start activities  启动activitystartAppShortcutOrInfoActivity(v, shortcut, launcher);}
    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {...Intent intent;...//从 WorkspaceItemInfo 中获取 Intent,用于启动应用或显示应用信息。if (item instanceof WorkspaceItemInfo) {WorkspaceItemInfo si = (WorkspaceItemInfo) item;if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)&& Intent.ACTION_VIEW.equals(intent.getAction())) {// make a copy of the intent that has the package set to null// we do this because the platform sometimes disables instant// apps temporarily (triggered by the user) and fallbacks to the// web ui. This only works though if the package isn't setintent = new Intent(intent);intent.setPackage(null);}if ((si.options & WorkspaceItemInfo.FLAG_START_FOR_RESULT) != 0) {launcher.startActivityForResult(item.getIntent(), 0);InstanceId instanceId = new InstanceIdSequence().newInstanceId();launcher.logAppLaunch(launcher.getStatsLogManager(), item, instanceId);return;}}...// 启动普通应用launcher.startActivitySafely(v, intent, item);}
  • 触发Launcher.java的startActivitySafely
    //packages/apps/Launcher3/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java@Overridepublic boolean startActivitySafely(View v, Intent intent, ItemInfo item) {// Only pause is taskbar controller is not presentmHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);//调用父类即Launcher.javaboolean started = super.startActivitySafely(v, intent, item);if (getTaskbarUIController() == null && !started) {mHotseatPredictionController.setPauseUIUpdate(false);}return started;}
//packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
@Overridepublic boolean startActivitySafely(View v, Intent intent, ItemInfo item) {//检查 Launcher 是否已经处于 resumed 状态(即完全恢复并可见)。//如果 Launcher 未完全恢复,直接启动 Activity 可能会导致窗口管理器(WM)的启动动画被破坏。if (!hasBeenResumed()) {//如果 Launcher 未完全恢复,则延迟启动 Activity,直到 Launcher 下次恢复。//使用 addOnResumeCallback 方法注册一个回调,在 Launcher 恢复时执行 startActivitySafely。addOnResumeCallback(() -> startActivitySafely(v, intent, item));if (mOnDeferredActivityLaunchCallback != null) {mOnDeferredActivityLaunchCallback.run();mOnDeferredActivityLaunchCallback = null;}return true;}//调用父类ActivityContext的 startActivitySafely 方法boolean success = super.startActivitySafely(v, intent, item);//BubbleTextView 是 Launcher 中用于显示应用图标和文件夹图标的视图。//如果成功启动 Activity,并且触发视图是 BubbleTextView,则设置其按压状态为 true。if (success && v instanceof BubbleTextView) {           BubbleTextView btv = (BubbleTextView) v;btv.setStayPressed(true);//使用 addOnResumeCallback 方法注册一个回调,在 Launcher 恢复时将按压状态重置为 false。addOnResumeCallback(() -> btv.setStayPressed(false));}return success;}
  • 触发ActivityContext.java的startActivitySafely

主要包含以下几个步骤

  1. 主线程检查:确保方法在主线程中调用。
  2. 安全模式检查:在安全模式下阻止非系统应用的启动。
  3. 快捷方式启动:支持深度快捷方式的启动。
  4. 启动选项:根据视图和 ItemInfo 准备启动选项。
  5. 用户句柄:支持跨用户启动 Activity。
  6. 日志记录:记录应用启动日志。
  7. 异常处理:捕获并处理启动过程中的异常。
    //packages/apps/Launcher3/src/com/android/launcher3/views/ActivityContext.java/*** 安全地启动一个 Activity,并处理一些特殊情况(如安全模式、快捷方式启动、跨用户启动等)* @param v 触发启动操作的视图(如应用图标)。* @param intent 要启动的 Activity 的 Intent。* @param item 与视图关联的 ItemInfo 对象(如 WorkspaceItemInfo),可以为 null。* @return RunnableList 用于监听动画结束的回调列表。如果启动失败,则返回 null。*/default RunnableList startActivitySafely(View v, Intent intent, @Nullable ItemInfo item) {//确保该方法在主线程(UI 线程)中调用,避免在后台线程中启动 Activity。Preconditions.assertUIThread();Context context = (Context) this;//检查安全模式//如果设备处于安全模式,并且目标应用不是系统应用,则阻止启动并显示提示信息。if (isAppBlockedForSafeMode() && !PackageManagerHelper.isSystemApp(context, intent)) {Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();return null;}//如果 ItemInfo 是深度快捷方式(ITEM_TYPE_DEEP_SHORTCUT),并且不是占位符(isPromise() 返回 false),则标记为快捷方式启动。boolean isShortcut = (item instanceof WorkspaceItemInfo)&& item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT&& !((WorkspaceItemInfo) item).isPromise();//GO_DISABLE_WIDGETS:如果为 true,则禁用快捷方式启动,并返回 null。if (isShortcut && GO_DISABLE_WIDGETS) {return null;}//准备启动选项//如果视图 v 为 null,则调用 makeDefaultActivityOptions 方法创建默认启动选项。ActivityOptionsWrapper options = v != null ? getActivityLaunchOptions(v, item): makeDefaultActivityOptions(item != null && item.animationType == DEFAULT_NO_ICON? SPLASH_SCREEN_STYLE_SOLID_COLOR : -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */);//设置用户句柄UserHandle user = item == null ? null : item.user;Bundle optsBundle = options.toBundle();// 准备 Intent//设置 Intent 的标志位,确保目标 Activity 在新的任务栈中启动。intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if (v != null) {//用于动画过渡效果。intent.setSourceBounds(Utilities.getViewBounds(v));}//启动Activitytry {//快捷方式启动if (isShortcut) {String id = ((WorkspaceItemInfo) item).getDeepShortcutId();String packageName = intent.getPackage();((Context) this).getSystemService(LauncherApps.class).startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, user);} else if (user == null || user.equals(Process.myUserHandle())) {// Could be launching some bookkeeping activitycontext.startActivity(intent, optsBundle);//普通启动} else {//如果用户句柄不是当前用户,跨用户启动 Activity。context.getSystemService(LauncherApps.class).startMainActivity(intent.getComponent(), user, intent.getSourceBounds(), optsBundle);}...return options.onEndCallback;} catch (NullPointerException | ActivityNotFoundException | SecurityException e) {...}return null;}
Activity启动流程
  • 执行Activity的startActivity()
   //frameworks/base/core/java/android/app/Activity.java@Overridepublic void startActivity(Intent intent, @Nullable Bundle options) {getAutofillClientController().onStartActivity(intent, mIntent);if (options != null) {startActivityForResult(intent, -1, options);} else {startActivityForResult(intent, -1);}}

startActivityForResult主要包含以下几个步骤

  • 启动目标 Activity:
  • 通过 Instrumentation.execStartActivity 方法启动目标 Activity。
  • 处理返回结果:
    如果目标 Activity 立即返回结果,则通过 mMainThread.sendActivityResult 方法将结果发送给当前 Activity。
  • 标记 Activity 已启动:
    如果 requestCode 大于等于 0,则标记当前 Activity 已启动目标 Activity。
  • 取消输入并启动退出过渡:
    取消当前 Activity 的输入事件,并启动退出过渡动画。
  • 支持子 Activity:
    如果当前 Activity 有父 Activity,则通过父 Activity 启动目标 Activity。
     /** @param intent 要启动的 Activity 的 Intent。* @param requestCode 请求代码,用于标识启动请求。当目标 Activity 返回结果时,该代码会传递给 onActivityResult 方法。* @param options 启动选项(如动画、过渡效果等),可以为 null。* @see #startActivity*/public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {// 检查是否有父 Activityif (mParent == null) {//将启动选项转换为适合当前 Activity 的格式。例如,处理与启动动画相关的选项。options = transferSpringboardActivityOptions(options);//通过 Instrumentation 执行实际的 Activity 启动操作。//参数包括当前 Activity 的上下文、应用线程、Activity 的 IBinder 令牌、目标 Intent、请求代码和启动选项。Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);//如果目标 Activity 立即返回结果(如 RESULT_CANCELED),则通过主线程将结果发送给当前 Activity。//结果会传递给 onActivityResult 方法。if (ar != null) {mMainThread.sendActivityResult(mToken, mEmbeddedID, requestCode, ar.getResultCode(),ar.getResultData());}if (requestCode >= 0) {// 如果 requestCode 大于等于 0,则标记当前 Activity 已启动目标 Activity。// 这可以避免在目标 Activity 返回结果之前显示当前 Activity,从而减少闪烁。mStartedActivity = true;}//取消输入并启动退出过渡//取消当前 Activity 的输入事件(如键盘输入)。//启动退出过渡动画(如 Activity 切换动画)。cancelInputsAndStartExitTransition(options);// TODO Consider clearing/flushing other event sources and events for child windows.} else {//调用父 Activity 的方法if (options != null) {mParent.startActivityFromChild(this, intent, requestCode, options);} else {mParent.startActivityFromChild(this, intent, requestCode);}}}
Instrumentation阶段

Instrumentation 用于监控应用程序与系统的交互。它在应用程序的整个生命周期中扮演着重要角色,尤其是在 Activity 的启动、生命周期回调、以及测试框架中。

Instrumentation 的作用
  • 监控应用程序的生命周期:如 Activity 的启动、停止、销毁等。
  • 注入测试代码:在 Android 测试框架中,Instrumentation 用于注入测试代码,监控应用程序的行为。
  • 提供上下文和环境:为应用程序提供测试所需的上下文和环境。
  • 处理系统交互:如启动 Activity、发送广播、处理 Intent 等。
Instrumentation 的核心方法
  • execStartActivity 启动一个 Activity。
  • callActivityOnCreate 调用 Activity 的 onCreate 方法。
  • callActivityOnResume 调用 Activity 的 onResume 方法。
  • newActivity 创建一个新的 Activity 实例。
  • sendKeySync 同步发送一个按键事件。
  • runOnMainSync 在主线程中同步执行一个 Runnable。
execStartActivity 执行启动 Activity

execStartActivity 主要包括以下几个步骤

  • IApplicationThread:用于与系统服务通信。
  • Referrer:处理 Activity 的来源信息。
  • ActivityMonitor:监控和拦截 Activity 启动。
  • Intent 准备:确保 Intent 可以跨进程传递。
  • ActivityTaskManager:与系统服务交互,启动 Activity。
  • 异常处理:捕获并处理启动过程中的异常。
     //frameworks/base/core/java/android/app/Instrumentation.java/** @param who 启动 Activity 的上下文(通常是当前 Activity)。* @param contextThread 应用的主线程(IApplicationThread 类型)。* @param token 当前 Activity 的 IBinder 令牌。* @param target 目标 Activity(可以为 null)。* @param intent 要启动的 Activity 的 Intent。* @param requestCode 请求代码(用于 startActivityForResult)。* @param options 启动选项(如动画、过渡效果等)。** @return 返回一个 ActivityResult 对象,包含目标 Activity 的结果(如结果代码和返回数据)。如果启动失败,则返回 null。* {@hide}*/@UnsupportedAppUsagepublic ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {//获取 IApplicationThread,表示应用的主线程,用于与系统服务(如 ActivityTaskManager)通信。IApplicationThread whoThread = (IApplicationThread) contextThread;//处理 Referrer,将 Referrer 添加到 Intent 的 EXTRA_REFERRER 字段中。Uri referrer = target != null ? target.onProvideReferrer() : null;if (referrer != null) {intent.putExtra(Intent.EXTRA_REFERRER, referrer);}//处理 ActivityMonitor,用于监控和拦截 Activity 启动。if (mActivityMonitors != null) {synchronized (mSync) {final int N = mActivityMonitors.size();//遍历所有的 ActivityMonitor。for (int i=0; i<N; i++) {final ActivityMonitor am = mActivityMonitors.get(i);ActivityResult result = null;//如果 ActivityMonitor 忽略特定的 Intent,则调用 onStartActivity 方法处理。if (am.ignoreMatchingSpecificIntents()) {if (options == null) {options = ActivityOptions.makeBasic().toBundle();}result = am.onStartActivity(who, intent, options);}//如果 ActivityMonitor 匹配当前的启动请求,则根据其配置决定是否拦截启动。if (result != null) {am.mHits++;return result;} else if (am.match(who, null, intent)) {am.mHits++;if (am.isBlocking()) {return requestCode >= 0 ? am.getResult() : null;}break;}}}}try {//将 Intent 中的 EXTRA_STREAM 数据迁移到 ClipData 中。intent.migrateExtraStreamToClipData(who);//准备 Intent,确保它可以跨进程传递。intent.prepareToLeaveProcess(who);//启动 Activity//获取 ActivityTaskManager 的系统服务。//调用 ActivityTaskManager 的 startActivity 方法,启动目标 Activity。int result = ActivityTaskManager.getService().startActivity(whoThread,who.getOpPackageName(), who.getAttributionTag(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()), token,target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);//通知 Activity 启动结果。notifyStartActivityResult(result, options);//检查启动结果,如果失败则抛出异常。checkStartActivityResult(result, intent);} catch (RemoteException e) {throw new RuntimeException("Failure from system", e);}return null;}

execStartActivity执行后启动过程就会进入到服务端 SystemServer的 ATMS Binder 服务中去了。


http://www.ppmy.cn/devtools/157569.html

相关文章

redis中的list类型

可以看作一个双向链表结构&#xff0c;支持正向和反向检索&#xff0c;有序&#xff0c;元素可以重复&#xff0c;插入和删除快&#xff0c;查询速度一般 list类型常见命令&#xff1a; LPUSH key element... : 向链表左侧插入一个或多个元素 LPOP key&#xff1a;移除并返回…

工业 4G 路由器助力消防领域,守卫生命安全防线

在消防领域也在不断引入新技术以提升消防安全保障能力发展过程中。工业 4G 路由器为其数据传输、预警监控发挥着重要的通信作用。 工业 4G 路由器通过内置的 4G 模块&#xff0c;接入 4G 网络&#xff0c;将网络信号进行转换和分发。它能够适应复杂的工业环境&#xff0c;具备…

Redis存储⑤Redis五大数据类型之 List 和 Set。

目录 1. List 列表 1.1 List 列表常见命令 1.2 阻塞版本命令 1.3 List命令总结和内部编码 1.4 List典型使用场景 1.4.1 消息队列 1.4.2 分频道的消息队列 1.4.3 微博 Timeline 2. Set 集合 2.1 Set 集合常见命令 2.2 Set 集合间命令 2.3 Set命令小结和内部编码 2.…

笔灵ai写作技术浅析(五):强化学习

强化学习(Reinforcement Learning, RL)是笔灵AI写作中用于优化文本生成质量的关键技术之一。与传统的监督学习不同,强化学习通过与环境的交互,根据生成的文本质量反馈信号(如语法正确性、语义连贯性、与主题的相关性等),不断调整和优化模型参数,从而提升生成文本的质量…

深度学习 语音合成

以下将介绍几种不同方式实现深度学习语音合成的代码示例,分别是使用百度云语音合成 API、基于 PyTorch 的 Tacotron 2 和 WaveGlow 模型(本地实现)以及 OpenAI 的 TTS 服务。 方式一:使用百度云语音合成 API 1. 安装必要的库 pip install baidu-aip2. 代码实现 from ai…

无人机GPS模块概述!

一、GPS模块 原理&#xff1a;GPS模块通过接收来自卫星系统的信号&#xff0c;计算出无人机当前的位置、速度和时间等信息。它主要由接收天线、接收器、信号处理器和电源等组成。接收天线接收来自卫星的GPS信号&#xff0c;接收器将信号转换为数字信号并传输给信号处理器&…

【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-Chapter7-迭代器与生成器

七、迭代器与生成器 ECMAScript 6 规范新增了两个高级特性&#xff1a;迭代器和生成器。使用这两个特性&#xff0c;能够更清晰、高效、方便地实现迭代。 理解迭代 循环是迭代机制的基础&#xff0c;这是因为它可以指定迭代的次数&#xff0c;以及每次迭代要执行什么操作。每次…

哈佛大学“零点项目”(Project Zero)简介

哈佛大学“零点项目”&#xff08;Project Zero&#xff09;简介 起源与背景 “零点项目”&#xff08;Project Zero&#xff09;由美国哲学家纳尔逊古德曼&#xff08;Nelson Goodman&#xff09;于1967年在哈佛大学教育研究院创立。名称源于“从零开始研究艺术教育”的理念&…