View的绘制流程一 DecorView是什么时候添加到Window上的

2024-01-20 02:12

本文主要是介绍View的绘制流程一 DecorView是什么时候添加到Window上的,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

setContentView做了什么?

setContentView只是创建了一个Decorview,把我们的xml添加到Decorview里面。但是DecorView并没有添加到PhoneView里面。

比如果在onCreate,onResume去拿控件的高度是拿不到的。

Activity的onResume方法在什么时候执行呢?

ActivityThread的handleResumeActivity方法里面触发performResumeActivity方法,performResumeActivity方法里面会执行r.activity.performResume,接着走到mInstrumentation.callActivityOnResume,这个方法里面触发activity.onResume()。

handleResumeActivity(ActivityThread.java) 
--> performResumeActivity(ActivityThread.java) --> r.activity.performResume(Activity.java) --> mInstrumentation.callActivityOnResume(Activity.java) -->activity.onResume(Instrumentation.java)
 ActivityThread.java@Overridepublic void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,boolean isForward, String reason) {// If we are getting ready to gc after going to the background, well// we are back active so skip it.unscheduleGcIdler();mSomeActivitiesChanged = true;// TODO Push resumeArgs into the activity for consideration// skip below steps for double-resume and r.mFinish = true case.if (!performResumeActivity(r, finalStateRequest, reason)) {return;}........
}public boolean performResumeActivity(ActivityClientRecord r, boolean finalStateRequest,String reason) {......try {r.activity.onStateNotSaved();r.activity.mFragments.noteStateNotSaved();checkAndBlockForNetworkAccess();if (r.pendingIntents != null) {deliverNewIntents(r, r.pendingIntents);r.pendingIntents = null;}if (r.pendingResults != null) {deliverResults(r, r.pendingResults, reason);r.pendingResults = null;}r.activity.performResume(r.startsNotResumed, reason);r.state = null;r.persistentState = null;r.setState(ON_RESUME);reportTopResumedActivityChanged(r, r.isTopResumedActivity, "topWhenResuming");} ......}Activity.javafinal void performResume(boolean followedByPause, String reason) {......// mResumed is set by the instrumentationmInstrumentation.callActivityOnResume(this);......}Instrumentation.javapublic void callActivityOnResume(Activity activity) {activity.mResumed = true;activity.onResume();......}

DecorView是什么时候添加到Window上的?

ActivityThread的handleResumeActivity方法:

 @Overridepublic void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,boolean isForward, String reason) {........final Activity a = r.activity;........if (r.window == null && !a.mFinished && willBeVisible) {r.window = r.activity.getWindow();//拿到DecorViewView decor = r.window.getDecorView();decor.setVisibility(View.INVISIBLE);ViewManager wm = a.getWindowManager();WindowManager.LayoutParams l = r.window.getAttributes();a.mDecor = decor;l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;l.softInputMode |= forwardBit;......if (a.mVisibleFromClient) {if (!a.mWindowAdded) {a.mWindowAdded = true;//把DecorView与Phone关联起来wm.addView(decor, l);} else {// The activity will get a callback for this {@link LayoutParams} change// earlier. However, at that time the decor will not be set (this is set// in this method), so no action will be taken. This call ensures the// callback occurs with the decor set.a.onWindowAttributesChanged(l);}}// If the window has already been added, but during resume// we started another activity, then don't yet make the// window visible.} ........}

在handleResumeActivity方法里面,先拿到DecorView,然后调用WindowManager的addView方法,把DecorView与PhoneWindow关联起来。

WindowManager是啥?

1.WindowManager在什么时候创建?

在ActivityThread的performLaunchActivity里面执行了activity的attach方法:

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {........if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "+ r.activityInfo.name + " with config " + config);Window window = null;if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {window = r.mPendingRemoveWindow;r.mPendingRemoveWindow = null;r.mPendingRemoveWindowManager = null;}// Activity resources must be initialized with the same loaders as the// application context.appContext.getResources().addLoaders(app.getResources().getLoaders().toArray(new ResourcesLoader[0]));appContext.setOuterContext(activity);//会在这个方法中创建Activity的PhoneWindow,并绑定对应的WindowManager。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.activityConfigCallback,r.assistToken, r.shareableActivityToken);........}

 再看看Activty的attach方法:

  @UnsupportedAppUsagefinal void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor,Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {//  PhoneWindow mWindow = new PhoneWindow(this, window, activityConfigCallback);mWindow.setWindowControllerCallback(mWindowControllerCallback);// PhoneWindow 的 callback 设置为activitymWindow.setCallback(this);........//设置 PhoneWindow的WindowManagermWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);if (mParent != null) {mWindow.setContainer(mParent.getWindow());}// 获取 WindowManagerImpl 作为windowManagermWindowManager = mWindow.getWindowManager();mCurrentConfig = config;........}

 WindowManager的创建时机在执行ActivityThread的performLaunchActivity方法里面,因为调用了Activity的attach而创建出来。

2.WindowManger与WindowManagerImpl,WindowManagerGlobal关系

再来看到setWindowManager方法:

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {mAppToken = appToken;mAppName = appName;mHardwareAccelerated = hardwareAccelerated;if (wm == null) {wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);}mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);}

我们发现wm原来是WindowManagerImpl,那么在handResumeActivity方法里面的wm.addview会执行WindowManagerImpl里面的addView方法。

    @UnsupportedAppUsageprivate final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyTokens(params);mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,mContext.getUserId());}

 WindowManagerImpl的addView最终又是调用WindowManagerGlobal的addView方法。那么WindowManger,WindowManagerImpl,WindowManagerGlobal关系是怎么样的呢?

WindowManagerImpl:确定View属于哪个屏幕,哪个父窗口

WindowManagerGlobal:管理整个进程,所有的窗口信息

ViewRootImpl:WindowManagerGlobal 实际操作者,操作自己的窗口

WindowManager是一个接口类,继承自接口ViewManager,负责窗口的管理(增、删、改)。它的实现类是WindowManagerlmpl,而具体操作实际上又会交给WindowManagerGlobal来处理,它是个单例,进程唯一。WindowManagerGlobal执行addView的方法中会传入DecorView,还会初始化一个ViewRootlmpl。WindowManagerGlobal因为是单例的,它内部会有两个List来分别保存这两个对象,来统一管理。

3.ViewRootImpl是什么

接下来我们来看WindowManagerGlobal的addView方法:

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (display == null) {throw new IllegalArgumentException("display must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;/* 如果当前窗口需要被添加为另一个窗口的附属窗口(子窗口),则需要让父窗口视自己的情况,对当前窗口的布局参数(LayoutParams)进行一些修改 */if (parentWindow != null) {parentWindow.adjustLayoutParamsForSubWindow(wparams);} else {// If there's no parent, then hardware acceleration for this view is// set from the application's hardware acceleration setting.final Context context = view.getContext();if (context != null&& (context.getApplicationInfo().flags& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;}}ViewRootImpl root;View panelParentView = null;synchronized (mLock) {// Start watching for system property changes.if (mSystemPropertyUpdater == null) {mSystemPropertyUpdater = new Runnable() {@Override public void run() {synchronized (mLock) {for (int i = mRoots.size() - 1; i >= 0; --i) {mRoots.get(i).loadSystemProperties();}}}};SystemProperties.addChangeCallback(mSystemPropertyUpdater);}// WindowManager不允许同一个View被添加两次int index = findViewLocked(view, false);if (index >= 0) {if (mDyingViews.contains(view)) {// Don't wait for MSG_DIE to make it's way through root's queue.mRoots.get(index).doDie();} else {throw new IllegalStateException("View " + view+ " has already been added to the window manager.");}// The previous removeView() had not completed executing. Now it has.}// If this is a panel window, then find the window it is being// attached to for future reference.if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {final int count = mViews.size();for (int i = 0; i < count; i++) {if (mRoots.get(i).mWindow.asBinder() == wparams.token) {panelParentView = mViews.get(i);}}}// 创建一个ViewRootImpl对象,ViewRootImpl实现了一个控件树的根// 它负责与WMS进行直接的通讯,负责管理Surface,负责触发控件的测量与布局,负责触发控件的绘制,同时也是输入事件的中转站。root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);// 将下面三个参数保存到对应集合中,以供之后的查询,控件、布局参数、ViewRootImpl三者共同组成了客户端的一个窗口// 将 DecorView 添加到 mViews 中mViews.add(view);// 将 ViewRootImpl 添加到 mRoots 中mRoots.add(root);// 将 DecorView 的 LayoutParams参数 添加到 mParams 中mParams.add(wparams);// do this last because it fires off messages to start doing thingstry {/* 将作为窗口的控件设置给ViewRootImpl。这个动作将导致ViewRootImpl向WMS添加新的窗口、申请Surface以及托管控件在Surface上的重绘动作。这才是真正意义上完成了窗口的添加操作*/root.setView(view, wparams, panelParentView, userId);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.if (index >= 0) {removeViewLocked(index, true);}throw e;}}}

这个方法里面主要是创建了ViewRootImpl,把WindowManager.LayoutParams设置给DecorView了。并且设置了三个list,分别把ViewRootImpl,DecorView和WindowManager.LayoutParams给保存起来,调用ViewRootImpl的setView方法,把ViewRootImpl设置为DecorView的父布局,开启view绘制流程:

 // 创建一个ViewRootImpl对象,ViewRootImpl实现了一个控件树的根// 它负责与WMS进行直接的通讯,负责管理Surface,负责触发控件的测量与布局,负责触发控件的绘制,同时也是输入事件的中转站。root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);// 将下面三个参数保存到对应集合中,以供之后的查询,控件、布局参数、ViewRootImpl三者共同组成了客户端的一个窗口// 将 DecorView 添加到 mViews 中mViews.add(view);// 将 ViewRootImpl 添加到 mRoots 中mRoots.add(root);// 将 DecorView 的 LayoutParams参数 添加到 mParams 中mParams.add(wparams);
// do this last because it fires off messages to start doing thingstry {/* 将作为窗口的控件设置给ViewRootImpl。这个动作将导致ViewRootImpl向WMS添加新的窗口、申请Surface以及托管控件在Surface上的重绘动作。这才是真正意义上完成了窗口的添加操作*/root.setView(view, wparams, panelParentView, userId);}
 ViewRootImpl构造方法

我们进入ViewRootImpl的构造方法一探究竟:

 public ViewRootImpl(Context context, Display display, IWindowSession session,boolean useSfChoreographer) {....../* 保存当前线程到mThread。这个赋值操作体现了创建ViewRootImpl的线程如何成为UI主线程在ViewRootImpl处理来自控件树的请求时(如请求重新布局,请求重绘,改变焦点等),会检查发起请求的thread与这个mThread是否相同。倘若不同则会拒绝这个请求并抛出一个异常 */mThread = Thread.currentThread();....../* mDirty用于收集窗口中的无效区域。所谓无效区域是指由于数据或状态发生改变时而需要进行重绘的区域。举例说明,当应用程序修改了一个TextView的文字时,TextView会将自己的区域标记为无效区域,并通过invalidate()方法将这块区域收集到这里的mDirty中。当下次绘制时,TextView便可以将新的文字绘制在这块区域上 */mDirty = new Rect();mTempRect = new Rect();mVisRect = new Rect();/* mWinFrame,描述了当前窗口的位置和尺寸。与WMS中WindowState.mFrame保持着一致 */mWinFrame = new Rect();/* 创建一个W类型的实例,W是IWindow.Stub的子类。即它将在WMS中作为新窗口的ID,以及接 收来自WMS的回调 */mWindow = new W(this);....../* 创建mAttachInfo。mAttachInfo是控件系统中很重要的对象。它存储了此当前控件树所贴附 的窗口的各种有用的信息,并且会派发给控件树中的每一个控件。这些控件会将这个对象保存在自己的mAttachInfo变量中。mAttachInfo中所保存的信息有WindowSession,窗口的实例(即mWindow)ViewRootImpl实例,窗口所属的Display,窗口的Surface以及窗口在屏幕上的位置等等。所以,当需要在一个View中查询与当前窗口相关的信息时,非常值得在mAttachInfo中搜索一下 */mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,    context);......}

ViewRootImpl创建的时候,有三个地方需要注意:

1.保存当前线程到mThread

这个赋值操作体现了创建ViewRootImpl的线程如何成为UI主线程。在ViewRootImpl处理来自控件树的请求时(如请求重新布局,请求重绘,改变焦点等),会检查发起请求的thread与这个mThread是否相同,倘若不同则会拒绝这个请求并抛出一个异常。

2.创建mDirty区域

mDirty用于收集窗口中的无效区域。所谓无效区域是指由于数据或状态发生改变时而需要进行重绘
的区域。举例说明,当应用程序修改了一个TextView的文字时,TextView会将自己的区域标记为无效区域,并通过invalidate()方法将这块区域收集到这里的mDirty中。当下次绘制时,TextView便可以将新的文字绘制在这块区域上。

3.创建mAttachInfo

mAttachInfo是控件系统中很重要的对象。它存储了此当前控件树所贴附的窗口的各种有用的信息,并且会派发给控件树中的每一个控件。这些控件会将这个对象保存在自己的mAttachInfo变量中。mAttachInfo中所保存的信息有WindowSession,窗口的实例(即mWindow),ViewRootImpl实例,窗口所属的Display,窗口的Surface以及窗口在屏幕上的位置等等。所以,当需要在一个View中查询与当前窗口相关的信息时,非常值得在mAttachInfo中搜索一下。

真正把DecorView添加到PhoneWindow的地方

接着来看ViewRootImpl.setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {synchronized (this) {if (mView == null) {// mView 保存了控件树的根mView = view;......//在添加到窗口管理器之前安排第一个布局,以确保在从系统接收任何其他事件之前进行重新布局?               /* 在添加窗口之前,先通过requestLayout()方法在主线程上安排一次“遍历”。//所谓“遍历”是指ViewRootImpl中的核心方法performTraversals()。    这个方法实现了对控件树进行测量、布局、向WMS申请修改窗口属性以及重绘的所有工作。由于此“遍历”操作对于初次遍历做了一些特殊处理,而来自WMS通过mWindow发生的回调会导致一些属性发生变化?如窗口的尺寸、Insets以及窗口焦点等,从而有可能使得初次“遍历”的现场遭到破坏。因此,需要在添加窗口之前,先发送一个“遍历”消息到主线程在主线程中向主线程的Handler发送消息,如果使用得当,可以产生很精妙的效果。例如本例中可以实现如下的执行顺序:添加窗口->初次遍历->处理来自WMS的回调 */ //TODOrequestLayout();InputChannel inputChannel = null; /* 初始化mInputChannel。InputChannel是窗口接受来自InputDispatcher的输入事件的管道。注意,仅当窗口的属性inputFeatures不含有INPUT_FEATURE_NO_INPUT_CHANNEL时才会创建InputChannel,否则mInputChannel为空,从而导致此窗口无法接受任何输入事件 */if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {inputChannel = new InputChannel();}mForceDecorViewVisibility = (mWindowAttributes.privateFlags& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;try {mOrigWindowType = mWindowAttributes.type;mAttachInfo.mRecomputeGlobalAttributes = true;collectViewAttributes();adjustLayoutParamsForCompatibility(mWindowAttributes);/* 将窗口添加到WMS中。完成这个操作之后,mWindow已经被添加到指定的Display中?                    而且mInputChannel(如果不为空)已经准备好接收事件了。只是由于这个窗口没有进行过relayout(),因此它还没有有效的Surface可以进行绘制 */res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mDisplayCutout, inputChannel,mTempInsets, mTempControls);setFrame(mTmpFrame);} ....../* 如果mInputChannel不为空,则创建mInputEventReceiver,用于接收输入事件。注意第二个参数传递的是Looper.myLooper(),即mInputEventReceiver将在主线程上触发输入事件的读取与onInputEvent()。这是应用程序可以在onTouch()等事件响应中                直接进行UI操作等根本原因。*/if (inputChannel != null) {if (mInputQueueCallback != null) {mInputQueue = new InputQueue();mInputQueueCallback.onInputQueueCreated(mInputQueue);}mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());}/* ViewRootImpl将作为参数view的parent。所以,ViewRootImpl可以从控件树中任何一个控件开始,通过回溯getParent()的方法得到 */view.assignParent(this);......}

ViewRootImpl.setView方法主要是执行这几个重要步骤:

1.requestLayout()---》请求遍历控件树

2.res = mWindowSession.addToDisplayAsUser ---》将窗口添加到WMS上面

3.mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper())---》接收输入事件

4.view.assignParent(this) --》ViewRootImpl将作为DecorView的parent。所以,ViewRootImpl可以从控件树中任何一个控件开始,通过回溯getParent()的方法得到。

mWindowSession.addToDisplayAsUser ()这个方法最后会调用WindowManagerService中的addWindow()方法,这样一来,DecorView就被添加到Window上了。

回顾一下整个添加的流程

ActivityThread#handleResumeActivity —>WindowManagerImpl#addView
—>WindowManagerGlobal#addView —> ViewRootImpl#setView(调用mWindowSession.addToDisplayAsUser)

ActivityThread.handleResumeActivity 
--> wm.addView(decor, l); (WindowManagerImpl.java)--> WindowManagerGlobal.addView--> root = new ViewRootImpl(view.getContext(), display);--> mViews.add(view); // DecorViewmRoots.add(root); // ViewRootImplmParams.add(wparams); // WindowManager.LayoutParams--> root.setView(view, wparams, panelParentView, userId);-->res = mWindowSession.addToDisplayAsUser

这篇关于View的绘制流程一 DecorView是什么时候添加到Window上的的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/624492

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

【WebGPU Unleashed】1.1 绘制三角形

一部2024新的WebGPU教程,作者Shi Yan。内容很好,翻译过来与大家共享,内容上会有改动,加上自己的理解。更多精彩内容尽在 dt.sim3d.cn ,关注公众号【sky的数孪技术】,技术交流、源码下载请添加微信号:digital_twin123 在 3D 渲染领域,三角形是最基本的绘制元素。在这里,我们将学习如何绘制单个三角形。接下来我们将制作一个简单的着色器来定义三角形内的像素

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除

利用matlab bar函数绘制较为复杂的柱状图,并在图中进行适当标注

示例代码和结果如下:小疑问:如何自动选择合适的坐标位置对柱状图的数值大小进行标注?😂 clear; close all;x = 1:3;aa=[28.6321521955954 26.2453660695847 21.69102348512086.93747104431360 6.25442246899816 3.342835958564245.51365061796319 4.87

火语言RPA流程组件介绍--浏览网页

🚩【组件功能】:浏览器打开指定网址或本地html文件 配置预览 配置说明 网址URL 支持T或# 默认FLOW输入项 输入需要打开的网址URL 超时时间 支持T或# 打开网页超时时间 执行后后等待时间(ms) 支持T或# 当前组件执行完成后继续等待的时间 UserAgent 支持T或# User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器

MVC(Model-View-Controller)和MVVM(Model-View-ViewModel)

1、MVC MVC(Model-View-Controller) 是一种常用的架构模式,用于分离应用程序的逻辑、数据和展示。它通过三个核心组件(模型、视图和控制器)将应用程序的业务逻辑与用户界面隔离,促进代码的可维护性、可扩展性和模块化。在 MVC 模式中,各组件可以与多种设计模式结合使用,以增强灵活性和可维护性。以下是 MVC 各组件与常见设计模式的关系和作用: 1. Model(模型)

js window.addEventListener 是什么?

window.addEventListener 是 JavaScript 中的一个方法,用于向指定对象(在这个情况下是 window 对象,代表浏览器窗口)添加事件监听器,以便在该对象上发生特定事件时执行相应的函数(称为事件处理函数或事件监听器)。 这个方法接受三个参数: 事件类型(type):一个字符串,表示要监听的事件类型。例如,"click" 表示鼠标点击事件,"load" 表示页面加

UMI复现代码运行逻辑全流程(一)——eval_real.py(尚在更新)

一、文件夹功能解析 全文件夹如下 其中,核心文件作用为: diffusion_policy:扩散策略核心文件夹,包含了众多模型及基础库 example:标定及配置文件 scripts/scripts_real:测试脚本文件,区别在于前者倾向于单体运行,后者为整体运行 scripts_slam_pipeline:orb_slam3运行全部文件 umi:核心交互文件夹,作用在于构建真