[Android] Window的添加过程

news/2024/4/19 20:37:59/

一、Window世家

  • frameworks/base/core/java/android/view/ViewManager.java

    属于一个接口类,实现了对view的更新,添加,移除,具体代码如下

    public interface ViewManager
    {public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view);
    }
    

    可以看到接收的参数都是view,说明Window管理的都是view,WindowManager继承了该接口,注意是继承并不是实现,后续操作window都靠实现WM

  • frameworks/base/core/java/android/view/WindowManager.java

    实现了ViewManager接口,因为WM本身就是一个接口类,所以并没有重写VM接口里面的方法,后续会通过getSystemService拿到WM,强制至WindowManagerImpl,因为WMI实现了WM接口,然后在WMI中实现了addView的具体逻辑,可以看看WMI被创建的地方如下

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {mAppToken = appToken;mAppName = appName;mHardwareAccelerated = hardwareAccelerated|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);if (wm == null) {wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);//1}mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);//2
    }
    
  • frameworks/base/core/java/android/view/Window.java

    Window是一个抽象类,它具体的实现类为PhoneWindow,Window中管理了view,例如我们常知的setContentView,findViewById,setTitle,setBackgroundDrawable

    一个activity对应了一个Window(也可以称之为PhoneWindow)

  • frameworks/base/core/java/android/app/Activity.java

    一个Activity包含了一个Window(PhoneWindow),也包含了一个WMI,主要的方法是attach方法,就是window创建的开始处,也是activity启动流程的最后一步

    final 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) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);mWindow = new PhoneWindow(this, window);//1...mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);...
    
  • frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

    PhoneWindow在Activity#attach中创建,一个activity对应一个PhoneWindow,它继承自抽象类Window,包含了DecorView,也重写了Window的setContentView方法,当内部的mContentParent(类型是ViewGroup,layout文件中的根view,例如xml中是线性布局开头)为空的时候,就会开始创建DecorView,然后开始解析activity,分为两部分,第一部分是title,第二部分是content,作为DecorView的内容,然后DecorView的content部分就是mContentParent,目前还是个空壳,没有实际内容,还没有解析布局文件

  • frameworks/base/core/java/android/view/WindowManagerGlobal.java

    属于一个单例模式,一个进程存在一个此对象,在WMI中的addView中会被调用,WMG中也有个addView,属于WMI中调用过来的,这里面的逻辑才是真正的处理view添加的逻辑,例如处理WindowManager.LayoutParamsnew ViewRootImpl(),调用ViewRootImpl.setView,接着会调用IWindowSession向WMS中的Session发起窗口添加流程

    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {...//参数检查final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;if (parentWindow != null) {parentWindow.adjustLayoutParamsForSubWindow(wparams);//1} else {...}ViewRootImpl root;View panelParentView = null;...root = new ViewRootImpl(view.getContext(), display);//2view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);//3mParams.add(wparams);}try {root.setView(view, wparams, panelParentView);//4} catch (RuntimeException e) {...}}
    
  • frameworks/base/core/java/android/view/WindowManagerImpl

    在activity#attach中,调用了mWindow.setWindowManager中被创建,是通过getSystemService获取了WM,然后强转为WMI,调用WindowManagerImpl的createLocalWindowManager(this),来创建WMI实例,这个this就是activity的PhoneWindow(说Window也行),然后这个this就作为父window(主window)

    WMI实现了WM接口,所以WMI调用了addView,拥有具体实现,会继续调用WindowManagerGlobal进行处理,WMI拥有phonewindow的引用,后续将调用addView对PhoneWindow作为参数进行传递,然后将view绑定到这个PhoneWindow上

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    
  • frameworks/base/service/core/java/com/android/server/wm/WindowState.java

二、Window添加流程

先大概过一下总体逻辑(因为添加view都是通过WM.addView,所以我们目的是分析addView,晚点再分析setContentView):

  1. Activity#attach中创建了PhoneWindow(继承了Window抽象类),拥有setContentView等属性
  2. Activity#attach中又调用了PhoneWindow.setWindowManager(实际上是Window里面的方法),来创建了WindowManagerImpl并和mWindow(PhoneWindow)进行绑定,mWindow在WMI中属于parentWindow
  3. WindowManagerImpl中又实现了WindowManager,有addView的具体实现,又继续调用的WindowManagerGlobal#addView
  4. 在WindowManagerGlobal#addView中处理了LayoutParams(只是保存到mParams,然后给view添加此params,并没有根据参数改变view行为的逻辑,没有在这里处理,设计这种行为的属于view绘制流程),然后创建了viewRootImpl
  5. 接着调用了viewRootImpl#setView(view,params,parentView)方法,view就是要添加的view,parentView就是PhoneWindow(Window),viewRootImpl的一些职责 :管理view,负责和WMS进行通信,负责view的绘制,测量,布局
  6. 在setView中只处理mView为空的情况,因为必须是根view,并重新赋值给mView(它是一个IBinder对象,因为需要将view跨进程传递到WMS中,属于W类,属于IWindow.aidl),其次调用了mWindowSession.addToDisplayAsUser方法,mWindowSession是IWindowSession类型的,它是一个Binder对象,用于进行进程间通信,IWindowSession是Client端的代理,它的Server端的实现为Session,此前包含ViewRootImpl在内的代码逻辑都是运行在本地进程的,而Session的addToDisplay方法则运行在WMS所在的进程,每个应用程序进程都会对应一个Session,WMS会用ArrayList来保存这些Session
  7. 所以我们直接看Session.java#中的addToDisplayAsUser方法,是从viewRootImpl—>mWindowSession.addToDisplayAsUser跨进程过来的,session又继续调用了WMS的addWindow方法,所以继续锁定WMS
  8. WMS#addWindow中维护了WindowState,创建了DisplayContent,然后为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WMS会将它所管理的Surface交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上

三、源码分析

主要分析WMS的public int addWindow()方法

public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility...) {...// 父类windowWindowState parentWindow = null;...final int type = attrs.type;synchronized (mGlobalLock) {if (!mDisplayReady) {throw new IllegalStateException("Display has not been initialialized");}// 获取屏幕final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);// 检查屏幕的有效性if (displayContent == null) {ProtoLog.w(WM_ERROR, "Attempted to add window to a display that does "+ "not exist: %d. Aborting.", displayId);return WindowManagerGlobal.ADD_INVALID_DISPLAY;}if (!displayContent.hasAccess(session.mUid)) {ProtoLog.w(WM_ERROR,"Attempted to add window to a display for which the application "+ "does not have access: %d.  Aborting.",displayContent.getDisplayId());return WindowManagerGlobal.ADD_INVALID_DISPLAY;}// 检查Window是否以及存在(也就是WindowState),如果存在于集合中则代表重复添加if (mWindowMap.containsKey(client.asBinder())) {ProtoLog.w(WM_ERROR, "Window %s is already added", client);return WindowManagerGlobal.ADD_DUPLICATE_ADD;}// 1000~1999属于sub窗口类型,说明这个窗口是一个子窗口,所以需要找父类窗口if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {// 通过ViewRootImpl.W中的代理对象且从mWindowMap取出WindowState// LayoutParams中保存了WindowState对应的IBinder代理parentWindow = windowForClientLocked(null, attrs.token, false);if (parentWindow == null) {ProtoLog.w(WM_ERROR, "Attempted to add window with token that is not a window: "+ "%s.  Aborting.", attrs.token);return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;}// 不能在子窗口中添加子窗口,因为父类也是子窗口if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW&& parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {ProtoLog.w(WM_ERROR, "Attempted to add window with token that is a sub-window: "+ "%s.  Aborting.", attrs.token);return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;}}// 权限检查if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {ProtoLog.w(WM_ERROR,"Attempted to add private presentation window to a non-private display.  "+ "Aborting.");return WindowManagerGlobal.ADD_PERMISSION_DENIED;}if (type == TYPE_PRESENTATION && !displayContent.getDisplay().isPublicPresentation()) {ProtoLog.w(WM_ERROR,"Attempted to add presentation window to a non-suitable display.  "+ "Aborting.");return WindowManagerGlobal.ADD_INVALID_DISPLAY;}...// 检查该window是否存在父类窗口,是否具有ActivityRecordActivityRecord activity = null;final boolean hasParent = parentWindow != null;// 获取token,有可能为空WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);// 如果有父类,则获取父类的窗口typefinal int rootType = hasParent ? parentWindow.mAttrs.type : type;**// 1~99属于应用程序窗口类型**if (rootType >= FIRST_APPLICATION_WINDOW&& rootType <= LAST_APPLICATION_WINDOW) {// 判断当前窗口是否重复添加...**// 输入法窗口,也就是:FIRST_SYSTEM_WINDOW(2000)+11**} else if (rootType == TYPE_INPUT_METHOD) {// 检查窗口是否可以被添加if (token.windowType != TYPE_INPUT_METHOD) {ProtoLog.w(WM_ERROR, "Attempted to add input method window with bad token "+ "%s.  Aborting.", attrs.token);return WindowManagerGlobal.ADD_BAD_APP_TOKEN;}} **// 壁纸窗口类型,FIRST_SYSTEM_WINDOW(2000)+13**if (rootType == TYPE_WALLPAPER) {if (token.windowType != TYPE_WALLPAPER) {ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with bad token "+ "%s.  Aborting.", attrs.token);return WindowManagerGlobal.ADD_BAD_APP_TOKEN;}}  else if (type == TYPE_TOAST) { **// toast窗口类型:FIRST_SYSTEM_WINDOW(2000)+5**// 检查窗口的有效性,是否可以被添加...if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {ProtoLog.w(WM_ERROR, "Attempted to add a toast window with bad token "+ "%s.  Aborting.", attrs.token);return WindowManagerGlobal.ADD_BAD_APP_TOKEN;}} ...// 创建WindowState对象,client(IBinder/IWindow)都可以代表WindowState对象// 和父类window(也是WindowState),client,session,WMS,attrs进行绑定// 它存有窗口的所有的状态信息,在WMS中它代表一个窗口,client就是代表了这个WindowStatefinal WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);...// 如果WindowState->WindowToken中找不到屏幕,则直接返回无效屏幕的错误if (win.getDisplayContent() == null) {ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");return WindowManagerGlobal.ADD_INVALID_DISPLAY;}final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();// 调整Toast窗口超时时间等displayPolicy.adjustWindowParamsLw(win, win.mAttrs);......// 处理Toast超时后隐藏窗口if (type == TYPE_TOAST) {if (!displayContent.canAddToastWindowForUid(callingUid)) {ProtoLog.w(WM_ERROR, "Adding more than one toast window for UID at a time.");return WindowManagerGlobal.ADD_DUPLICATE_ADD;}...if (addToastWindowRequiresToken|| (attrs.flags & FLAG_NOT_FOCUSABLE) == 0|| displayContent.mCurrentFocus == null|| displayContent.mCurrentFocus.mOwnerUid != callingUid) {mH.sendMessageDelayed(mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),win.mAttrs.hideTimeoutMilliseconds);}}...// WindowState 的绑定方法,实际上就是调用了 mSession.windowAddedLocked();// 具体作用:创建SessionSurface,然后往WMS.mSessions集合中,把当前session添加到集合中// 说明一个window对应了一个session,然后mNumWindow++,记录window的个数win.attach();// 将WindowState代理对象,保存到mWindowMap中mWindowMap.put(client.asBinder(), win);...// 将当前WindowState添加到WindowToken中win.mToken.addWindow(win);// 针对当前window是否属于导航栏窗口还是状态栏窗口,进行处理displayPolicy.addWindowLw(win, attrs);...return res;}

总结一下以上内容:

  • 先获取当前window是否具有父类window,若有则复用父类window的WindowToken,若没有则创建WindowToken
  • 然后开始检查屏幕有效性,是否存在屏幕等,以及窗口的有效性,例如父类window是子窗口类型,子window也是子窗口类型,那么就无法添加
  • 然后针对窗口类型:子窗口(10001999),输入法窗口/壁纸窗口/Toast窗口等属于系统窗口(2000~2041,每次都是2000+1进行累加,都代表不同的系统窗口,例如`FIRST_SYSTEM_WINDOW(2000)+11`就代表着输入法窗口),还有应用程序窗口(199)
  • 针对以上的窗口类型,检查窗口的有效性,是否重复添加,以及Toast窗口的超时机制
  • 接着创建WindowState对象,client(IBinder/IWindow)都可以代表WindowState对象,然后和父类window(也是WindowState),client,session,WMS,attrs进行绑定,它存有窗口的所有的状态信息,在WMS中它代表一个窗口,client就是代表了这个WindowState
  • 然后将WindowState对象保存到了mWindowMap中,Key就是它的IBinder代理,value就是WindowState,可以通过这个Map集合检查窗口是否存在,是否重复添加
  • 然后调用了WindowState的attach方法,主要作用:实际上就是调用了 mSession.windowAddedLocked();创建SessionSurface,然后往WMS.mSessions集合中,把当前session添加到集合中,说明一个window对应了一个session,然后mNumWindow++,记录window的个数
  • 将当前WindowState添加到WindowToken中

继续分析一下WindowState的构造方法

WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,PowerManagerWrapper powerManagerWrapper) {super(service);mTmpTransaction = service.mTransactionFactory.get();mSession = s;mClient = c;mAppOp = appOp;mToken = token;mActivityRecord = mToken.asActivityRecord();mOwnerUid = ownerId;mShowUserId = showUserId;mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;mWindowId = new WindowId(this);mAttrs.copyFrom(a);mLastSurfaceInsets.set(mAttrs.surfaceInsets);mViewVisibility = viewVisibility;mPolicy = mWmService.mPolicy;mContext = mWmService.mContext;DeathRecipient deathRecipient = new DeathRecipient();mPowerManagerWrapper = powerManagerWrapper;mForceSeamlesslyRotate = token.mRoundedCornerOverlay;mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(mActivityRecord != null? mActivityRecord.getInputApplicationHandle(false /* update */) : null,getDisplayId()));...// 如果当前window的类型为子窗口if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {// The multiplier here is to reserve space for multiple// windows in the same type layer.// 获取基础layermBaseLayer = mPolicy.getWindowLayerLw(parentWindow)* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;// 子窗口会依赖基础layermSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);mIsChildWindow = true;mLayoutAttached = mAttrs.type !=WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;// 父类是否是输入法窗口类型mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD|| parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;// 父类是否是壁纸窗口类型mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER;}...// 窗口动画mWinAnimator = new WindowStateAnimator(this);mWinAnimator.mAlpha = a.alpha;mRequestedWidth = 0;mRequestedHeight = 0;mLastRequestedWidth = 0;mLastRequestedHeight = 0;mLayer = 0;mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale(mAttrs.packageName, s.mUid);// 如果是子窗口类型,将当前WindowState添加到父类窗口中if (mIsChildWindow) {ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);parentWindow.addChild(this, sWindowSubLayerComparator);}}

以上内容就是将session,WMS,client,WindowToken,LayoutParams,以及父类WindowState进行绑定关联,client(IBinder/IWindow)都可以代表WindowState对象,然后判断当前窗口是否是子窗口,是否具有父类窗口,然后将当前窗口添加到父类窗口中:

parentWindow.addChild(this, sWindowSubLayerComparator);


http://www.ppmy.cn/news/1141261.html

相关文章

[NewStarCTF 2023 公开赛道] week1 Crypto

brainfuck 题目描述&#xff1a; [>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<-]>>>>>>>.>----.<-----.>-----.>-----.<<<-.>>..…

程序无法启动,提示“找不到msvcp140.dll”或“msvcp140.dll缺失报错”解决方法

大家好&#xff01;今天我来给大家分享一下msvcp140.dll丢失的解决方法。我们都知道&#xff0c;在运行一些软件或游戏时&#xff0c;经常会遇到“找不到msvcp140.dll”的错误提示&#xff0c;这会让我们非常苦恼。那么&#xff0c;这个问题该怎么解决呢&#xff1f;下面我将为…

Maven - MacOS 快速安装

配置信息 Maven 版本&#xff1a;3.6.3Maven 地址&#xff1a;Index of /dist/maven/maven-3IDEA&#xff1a;2023 Tips&#xff1a;Maven 版本最好不要超过 3.8.0&#xff0c;最新版 Maven 会不兼容一些配置信息。上面的 Maven 地址里可以选择自己想下载的版本&#xff08;这…

nginx开启https配置之后网页无法访问问题处理

背景说明 最近新购服务器部署nginx之后按照之前的方式部署前端项目并配置https之后访问页面显示:无法访问.新的服务器ECS系统和之前相同,nginx安装方式也相同,nginx配置方式也是相同.但是访问还是显示无法访问.下面简单记录一下问题处理过程. 处理过程 1.https访问之后无法访问…

rpm安装mysql8后碰到的问题

1 mysqld 无法启动 原因 已经使用了3306端口&#xff0c;修改my.cnf中端口为3308 2 修改为3308端口后&#xff0c;还是无法启动&#xff0c; 2023-10-07T02:20:10.096689Z 0 [ERROR] [MY-010262] [Server] Cant start server: Bind on TCP/IP port: Permission denied 2023…

LuatOS-SOC接口文档(air780E)-- ftp - ftp 客户端

ftp.login(adapter,ip_addr,port,username,password)# FTP客户端 参数 传入值类型 解释 int 适配器序号, 只能是socket.ETH0, socket.STA, socket.AP,如果不填,会选择平台自带的方式,然后是最后一个注册的适配器 string ip_addr 地址 string port 端口,默认21 string…

【小程序 - 基础】页面导航、页面事件、生命周期、WXS脚本_04

目录 一、页面导航 1. 什么是页面导航 2. 小程序中实现页面导航的两种方式 2.1 声明式导航 2.1.1 导航到 tabBar 页面 2.1.2 导航到非 tabBar 页面 2.1.3 后退导航 2.2 编程式导航 2.2.1 导航到 tabBar 页面 2.2.2 导航到非 tabBar 页面 2.2.3 后退导航 2.3. 导航…

PHP 将json格式数据转换成数组的方法

php将json数据转换为数组的方法非常简单&#xff0c;php自带的json_decode()就可以实现&#xff0c;但是记住参数后面加上true&#xff0c;返回的便是数组&#xff0c;如果不加返回的便是对象 //json格式数据 $data {"angle":0,"card_region":[{"x&…

docker系列6:docker安装redis

传送门 docker系列1&#xff1a;docker安装 docker系列2&#xff1a;阿里云镜像加速器 docker系列3&#xff1a;docker镜像基本命令 docker系列4&#xff1a;docker容器基本命令 docker系列5&#xff1a;docker安装nginx Docker安装redis 通过前面4节&#xff0c;对docke…

python和go相互调用的两种方法

前言 Python 和 Go 语言是两种不同的编程语言&#xff0c;它们分别有自己的优势和适用场景。在一些项目中&#xff0c;由于团队内已有的技术栈或者某一部分业务的需求&#xff0c;可能需要 Python 和 Go 相互调用,以此来提升效率和性能。 性能优势 Go 通常比 Python 更高效&…

定制自己的 Excel 界面 + 保存 Excel

文章目录 Excel 的界面自定义快速访问工具栏自定义功能区折叠或显示功能区自定义 Excel 的界面保存 Excel Excel 的界面 快速访问工具栏也可以放在功能区下方&#xff1a; 效果&#xff1a; 自定义快速访问工具栏 方法一&#xff1a; S1&#xff1a; S2&#xff1a; 方法二…

linux基础64——abrtd总结

安装与启动 # 安装abrt图形用户界面 yum install abrt-desktop# 安装abrt客户端 yum -y install abrt-cli# 接收关于 ABRT 检测到的崩溃的电子邮件通知(默认情况下&#xff0c;它会在本地计算机上向 root 用户发送通知。电子邮件目标可以在 /etc/libreport/plugins/mailx.conf…

大厂秋招真题【DP】米哈游20230924秋招T2-米小游与魔法少女-奇运

米哈游20230924秋招T2-米小游与魔法少女-奇运 题目描述与示例 题目描述 米小游都快保底了还没抽到希儿&#xff0c;好生气哦&#xff01;只能打会活动再拿点水晶。 米小游和世界第一可爱的魔法少女 TeRiRi 正在打 BOSS&#xff0c;BOSS 的血量为h&#xff0c;当 BOSS 血量小…

Kafka源码简要分析

目录 一、生产者的初始化流程 二、生产者到缓冲队列的流程 三、Sender拉取数据到Kafka流程 四、消费者初始化 五、主题订阅原理 六、消费者抓取数据原理 七、消费者组初始化 八、消费者组消费流程 九、提交offset原理 一、生产者的初始化流程 首先获取事务id和客户端…

Spring实例化源码解析之Custom Events下集(九)

上集从官网的角度讲解了基本的使用和源码的内容&#xff0c;没有深入的进行分析&#xff0c;本章将从源码的角度分析ApplicationEvent、ApplicationListener、ApplicationEventMulticaster这三者之间的关系。 initApplicationEventMulticaster 上一章后续部分给出了源码的含义…

elasticsearch基本语法

这里写自定义目录标题 elasticsearch简介基本语法索引创建索引修改索引删除索引 查询简单查询精确查询条件查询范围查询&#xff1a;聚合查询&#xff1a;排序和分页&#xff1a; 参考文献&#xff1a; elasticsearch简介 Elasticsearch 是一个开源的分布式搜索和分析引擎&…

动态内存管理函数(malloc,calloc,realloc,free)

动态内存函数 1.1malloc和free C语言提供了一个动态内存开辟的函数&#xff1a; void* malloc (size_t size); 这个函数向内存申请一块连续可用的空间&#xff0c;并返回指向这块空间的指针。 如果开辟成功&#xff0c;则返回一个指向开辟好空间的指针。如果开辟失败&#…

Python标准库分享之存储对象 (pickle包,cPickle包)

在之前对Python对象的介绍中 (面向对象的基本概念&#xff0c;面向对象的进一步拓展)&#xff0c;我提到过Python“一切皆对象”的哲学&#xff0c;在 Python中&#xff0c;无论是变量还是函数&#xff0c;都是一个对象。当Python运行时&#xff0c;对象存储在内存中&#xff0…

arm 点灯实验代码以及现象

.text .global _start _start: 1.设置GPIOE寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28 LDR R0,0x50000A28 LDR R1,[R0] ORR R1,R1,#(0x1<<4) 第4位置1 STR R1,[R0] 1.设置GPIOF寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28 LDR R…

记一次 .NET某账本软件 非托管泄露分析

一&#xff1a;背景 1. 讲故事 中秋国庆长假结束&#xff0c;哈哈&#xff0c;在老家拍了很多的短视频&#xff0c;有兴趣的可以上B站观看&#xff1a;https://space.bilibili.com/409524162 &#xff0c;今天继续给大家分享各种奇奇怪怪的.NET生产事故&#xff0c;希望能帮助…