Dialog 对应的 Context 的探究

前言

创建Dialog的时候知道在Dialog的构造方法中需要一个上下文环境,而对这个“上下文”没有具体的概念结果导致程序报错,

于是发现Dialog需要的上下文环境只能是activity。

所以接下来这篇文章将会从源码的角度来彻底的理顺这个问题。

一、Dialog创建失败

在Dialog的构造方法中传入一个Application的上下文环境。看看程序是否报错:

Dialog dialog = new Dialog(getApplication()); TextView textView = new TextView(this); textView.setText("使用Application创建Dialog"); dialog.setContentView(textView); dialog.show(); 

运行程序,程序不出意外的崩溃了,我们来看下报错信息:

Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application at android.view.ViewRootImpl.setView(ViewRootImpl.java:517) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:301) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:215) at android.view.WindowManagerImpl$CompatModeWrapper.addView(WindowManagerImpl.java:140) 

这段错误日志,有两点我们需要注意一下

程序报了一个BadTokenException异常
程序报错是在ViewRootImpl的setView方法中

我们一定很疑惑BadTokenException到底是个啥,在说明这个之前我们首先需要了解Token,在了解了Token的概念之后,再结合ViewRootImpl的setView方法,就能理解BadTokenException这个到底是什么,怎么产生的。

二、Token分析

2.1 token详解

Token直译成中文是令牌的意思,android系统中将其作为一种安全机制,其本质是一个Binder对象,在跨进程的通行中充当验证码的作用。比如:在activity的启动过程及界面绘制的过程中会涉及到ActivityManagerService,应用程序,WindowManagerService三个进程间的通信,此时Token在这3个进程中充当一个身份验证的功能,ActivityManagerService与WindowManagerService通过应用程序的activity传过来的Token来分辨到底是控制应用程序的哪个activity。具体来说就是:

  1. 在启动Activity的流程当中,首先,ActivityManagerService会创建ActivityRecord由其本身来管理,同时会为这个ActivityRecord创建一个IApplication(本质上就是一个Binder)。
  2. ActivityManagerService将这个binder对象传递给WindowManagerService,让WindowManagerService记录下这个Binder。
  3. 当ActivityManagerService这边完成数据结构的添加之后,会返回给ActivityThread一个ActivityClientRecord数据结构,中间就包含了Token这个Binder对象。
  4. ActivityThread这边拿到这个Token的Binder对象之后,就需要让WindowManagerService去在界面上添加一个对应窗口,在添加窗口传给WindowManagerService的数据中WindowManager.LayoutParams这里面就包含了Token。
  5. 最终WindowManagerService在添加窗口的时候,就需要将这个Token的Binder和之前ActivityManagerService保存在里面的Binder做比较,验证通过说明是合法的,否则,就会抛出BadTokenException这个异常。

到这里,我们就知道BadTokenException是怎么回事了,然后接下来分析为什么使用Application上下文会报BadTokenException异常,而Activity上下文则不会。
在这里插入图片描述

2.2 为什么非要一个Token

因为在WMS那边需要根据这个Token来确定Window的位置(不是说坐标),如果没有Token的话,就不知道这个窗口应该放到哪个容器上了;

因为非Activity的Context它的WindowManger没有ParentWindow,导致在WMS那边找不到对应的容器,也就是不知道要把Dialog的Window放置在何处。

还有一个原因是没有SYSTEM_ALERT_WINDOW权限(当然要加权限啦,DisplayArea.Tokens的子容器,级别比普通应用的Window高,也就是会显示在普通应用Window的前面,如果不加权限控制的话,被滥用还得了)。

在获得SYSTEM_ALERT_WINDOW权限并将Dialog的Window.type指定为SYSTEM_WINDOW之后能正常显示,是因为WMS会为SYSTEM_WINDOW类型的窗口专门创建一个WindowToken(这下就有容器了),并放置在DisplayArea.Tokens里面(这下知道放在哪里了);
在这里插入图片描述
常规的Dialog显示,是这样的。

最底的那个绿色的WindowState,就是Dialog的窗口。

把Dialog的Window.type指定为SYSTEM_WINDOW之后,是这样的:
在这里插入图片描述
右边最底的那个WindowState就是SYSTEM_WINDOW类型的Dialog窗口,在层级关系上,跟隔壁的ActivityRecord是相等的。

Dialog窗口所在容器,就是刚刚说到的那个即时创建的WindowToken。

其实其他系统级别的窗口也是放置在这个WindowToken的父级容器DisplayArea.Tokens里面的,就像这样:
在这里插入图片描述

三、创建dialog流程分析

1、activity的界面最后是通过ViewRootImpl的setView方法连接WindowManagerService,从而让WindowManagerService将界面绘制到手机屏幕上。而从上面的异常日志中其实也可以看出,Dialog的界面也是通过ViewRootImpl的setView连接WindowManagerService,从而完成界面的绘制的。

我们首先来看Dialog的构造方法。不管一个参数的构造方法。两个参数的构造方法,最终都会调用到3个参数的构造方法:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean  
createContextThemeWrapper) { ...... //1.创建一个WindowManagerImpl对象 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); //2.创建一个PhoneWindow对象 final Window w = new PhoneWindow(mContext); mWindow = w; //3.使dialog能够响应用户的事件 w.setCallback(this); w.setOnWindowDismissedCallback(this); //4.为window对象设置WindowManager w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } 

这段代码可以看出dialog的创建实质上和activity界面的创建没什么两样,都需要完成一个应用窗口Window的创建,和一个应用窗口视图对象管理者WindowManagerImpl的创建。

然后Dialog同样有一个setContentView方法:

public void setContentView(@LayoutRes int layoutResID) { mWindow.setContentView(layoutResID); } 
依然是调用PhoneWindow的setContentView方法。再接着我们来看下dialog的show方法: 
public void show() { ...... //1.得到通过setView方法封装好的DecorView  mDecor = mWindow.getDecorView(); ...... //2.得到创建PhoneWindow时已经初始化的成员变量WindowManager.LayoutParams WindowManager.LayoutParams l = mWindow.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { WindowManager.LayoutParams nl = new WindowManager.LayoutParams(); nl.copyFrom(l); nl.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; l = nl; } try { //3.通过WindowManagerImpl添加DecorView到屏幕 mWindowManager.addView(mDecor, l); mShowing = true; sendShowMessage(); } finally { } } 

这段代码和activity的makeVisable方法类似,这里也不多说了,注释已经大概的写清楚了。然后调用WindowManagerImpl的addView方法:
在这里插入图片描述

@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); } 
接着调用了WindowManagerGlobal的addView方法: 
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ...... //1.将传进来的ViewGroup.LayoutParams类型的params转成  
WindowManager.LayoutParams类型的wparams  final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)  
params; //2.如果WindowManagerImpl是在activity的方法中被创建则不为空 if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { ...... } ViewRootImpl root; View panelParentView = null; synchronized (mLock) { ...... root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); //3.将视图对象view,ViewRootImpl以及wparams分别存入相应集合的对应位置 mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { //4.通过ViewRootImpl联系WindowManagerService将view绘制到屏幕上 root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } } 
//2.如果WindowManagerImpl是在activity的方法中被创建则不为空    if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { ...... } 

2、这里会首先判断一个类型为Window的parentWindow 是否为空,如果不为空会通过Window的adjustLayoutParamsForSubWindow方法调整一个类型为WindowManager.LayoutParams的变量wparams的一些属性值。应用程序请求WindowManagerService服务时会传入一个Token,其实那个Token就会通过Window的adjustLayoutParamsForSubWindow方法存放在wparams的token变量中,也就是说如果没有调用Window的adjustLayoutParamsForSubWindow方法就会导致wparams的token变量为空。然后我们接下来看一下wparams的token变量是如何赋值的:

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { CharSequence curTitle = wp.getTitle(); if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { ...... } else { if (wp.token == null) { wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; } ...... } if (wp.packageName == null) { wp.packageName = mContext.getPackageName(); } if (mHardwareAccelerated) { wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } 

这里我们可以看到这段代码首先会做一个判断如果wp.type的值有没有位于WindowManager.LayoutParams.FIRST_SUB_WINDOW与WindowManager.LayoutParams.LAST_SUB_WINDOW之间,如果没有则会给wp.token赋值。wp.type代表窗口类型,有3种级别,分别为系统级,应用级以及子窗口级。而这里是判断是否为子窗口级级别。而Dialog的WindowManager.LayoutParams.type默认是应用级的,因此会走else分支,给wp.token赋值mAppToken。至于mAppToken是什么,我们待会再来分析。

3、看WindowManagerGlobal的addView方法的,会调用ViewRootImpl的setView方法,我们来看一下,ViewRootImpl是如何连接WindowManagerService传递token的:

public void setView(View view, WindowManager.LayoutParams attrs, View  
panelParentView) { synchronized (this) { if (mView == null) { mView = view; try { ...... //1.通过binder对象mWindowSession调用WindowManagerService的接口请求 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { ...... throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } } ...... if (res < WindowManagerGlobal.ADD_OKAY) { ...... switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); //2.如果请求失败(token验证失败)则抛出BadTokenException异常 case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); case WindowManagerGlobal.ADD_APP_EXITING: throw new WindowManager.BadTokenException( "Unable to add window -- app for token " +  
attrs.token + " is exiting"); case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: throw new WindowManager.BadTokenException( "Unable to add window " + mWindow + " -- another window of this type already  
exists"); case WindowManagerGlobal.ADD_PERMISSION_DENIED: throw new WindowManager.BadTokenException( "Unable to add window " + mWindow + " -- permission denied for this window type"); case WindowManagerGlobal.ADD_INVALID_DISPLAY: throw new WindowManager.InvalidDisplayException( "Unable to add window " + mWindow + " -- the specified display can not be found"); case WindowManagerGlobal.ADD_INVALID_TYPE: throw new WindowManager.InvalidDisplayException( "Unable to add window " + mWindow + " -- the specified window type is not valid"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); } ...... } } } 

这段代码有两处需要注意:

  • 会通过一个mWindowSession的binder对象请求WindowManagerService服务,传递一个类型为WindowManager.LayoutParams的变量mWindowAttributes到WindowManagerService,mWindowAttributes里面装有代表当前activity的token对象。然后通过WindowManagerService服务创建屏幕视图。

  • 会根据请求WindowManagerService服务的返回结果判断是否请求成功,如果请求失败会抛出异常,注释的地方就是在文章开头示例抛出的异常。此时attrs.token为空。如果创建dialog的上下文环境改为activity的为什么就不为空呢?

四、分析创建Dialog的上下文Activity为何与众不同

1、上文的分析中可以看出attrs.token的赋值在Window的adjustLayoutParamsForSubWindow方法中。而Dialog默认的WindowManager.LayoutParams.type是应用级别的,因此,如果能进入这个方法内,attrs.token肯定能被赋值。现在只有一种情况,如果不是activity的上下文环境就没有进入到这个方法内。这时我们再看WindowManagerGlobal的addView方法的:

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ...... //2.如果WindowManagerImpl是在activity的方法中被创建则不为空 if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { ...... } ...... } 

从这里看出如果Window类型的parentWindow为空,就不会进入adjustLayoutParamsForSubWindow方法。从而可以得出结论如果不是activity的上下文环境WindowManagerGlobal的第四个参数parentWindow为空。紧接着我们再来分析为什么其他的上下文会导致parentWindow为空。

WindowManagerGlobal调用addView方法在WindowManagerImpl的addView方法中:

 @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); } 
WindowManagerImpl的addView方法在Dialog的首位方法中调用: 
public void show() { ...... try { mWindowManager.addView(mDecor, l); mShowing = true; sendShowMessage(); } finally { } } 

对比这两个方法。可以看出WindowManagerImpl的addView方法调用WindowManagerGlobal的addView方法是多出来了两个参数mDisplay, mParentWindow,我们只看后一个,多了一个Window类型的mParentWindow,可以一mParentWindow并不是在Dialog的show方法中赋值的。那么它在哪赋值呢?在WindowManagerImpl类中搜索mParentWindow发现它在WindowManagerImpl的两个参数的构造方法中被赋值。从这里我们可以猜测,如果是使用的activity上下文,那么在创建WindowManagerImpl实例的时候用的是两个参数的构造方法,而其他的上下文是用的一个参数的构造方法。现在问题就集中到了WindowManagerImpl是如何被创建的了。

我们再回过头来看Dialog的构造方法中WindowManagerImpl是如何创建的:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean  
createContextThemeWrapper) { ...... mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); ...... } 
然后分别查看activity的getSystemService方法,和Application的getSystemService方法: 
activity的getSystemService方法 
@Override public Object getSystemService(@ServiceName @NonNull String name) { ...... if (WINDOW_SERVICE.equals(name)) { return mWindowManager; } else if (SEARCH_SERVICE.equals(name)) { ensureSearchManager(); return mSearchManager; } return super.getSystemService(name); } 

在这个方法中直接返回了activity的mWindowManager对象,activity的mWindowManager对象在activity的attach方法中:

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) { ...... mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); ......   } 

2、我们再看Window的setWindowManager方法:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { //1.将ActivityManagerService传过来的Token保存到mAppToken中 mAppToken = appToken; //2.创建WindowManagerImpl mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); } 

这段代码两个地方需要注意,一是前ActivityManagerService传过来的Token赋值给Winow的mAppToken,这个token最后会保存到attr.token,具体操作在Window的adjustLayoutParamsForSubWindow方法中。二是调用WindowManagerImpl的createLocalWindowManager方法创建WindowManagerImpl:

public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mDisplay, parentWindow); } 

到这里就可以看出如果创建Dialog的上下文是activity,则会调用WindowManagerImpl两个参数的构造方法,从而导致parentWindow不为空。

3、Application的getSystemService方法:

由于Application是Context的子类,所以Application的getSystemService最终会调到ContextImpl的getSystemService方法

@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); } 
直接调用了SystemServiceRegistry的getSystemService方法,这个方法又会得到匿名内部类CachedServiceFetcher<WindowManager>的createService方法的返回值。 @Override public WindowManager createService(ContextImpl ctx) { return new WindowManagerImpl(ctx.getDisplay()); }}); 

从这个方法中可以看出上下文为Application时,调用的是WindowManagerImpl的一个参数的构造方法,从而parentWindow为空。

五 总结

  • 创建dialog时,如果传入构造方法不是一个activity类型的上下文,则导致WindowManagerImpl类型为Window的变量mParentWindow,从而导致WindowManagerGlobal的addView不会调用Window的adjustLayoutParamsForSubWindow方法,从而不会给attr.token赋值,导致在WindowManagerService服务中的身份验证失败,抛出BadTokenException异常。

  • Show一个普通的Dialog需要的并不是Activity本身,而是一个容器的token,我们平时会传Activity,只不过是Activity刚好对应WMS那边的一个WindowState的容器而已。

  • 在获得SYSTEM_ALERT_WINDOW权限并将Dialog的Window.type指定为SYSTEM_WINDOW之后能正常显示,是因为WMS会为SYSTEM_WINDOW类型的窗口专门创建一个WindowToken(这下就有容器了),并放置在DisplayArea.Tokens里面(这下知道放在哪里了)。

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

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

相关文章

以太网的 MAC 层

目录 1. MAC 层的硬件地址 48 位的 MAC 地址 2. MAC 帧的格式 以太网 V2 的 MAC 帧格式 无效的 MAC 帧 IEEE 802.3 MAC 与以太网 V2 MAC 帧格式的区别 1. MAC 层的硬件地址 硬件地址又称为物理地址&#xff0c;或 MAC 地址。 IEEE 802 标准为局域网规定了一种 48 位…

企业虚拟机服务器中了lockbit3.0勒索病毒怎么办,lockbit3.0勒索病毒解密处理流程

对于企业来说&#xff0c;企业的数据是企业的核心命脉&#xff0c;关乎着企业的生产与运营的所有工作。随着网络技术的不断发展&#xff0c;网络安全威胁也在不断增加。近期&#xff0c;云天数据恢复中心接到了很多企业的求助&#xff0c;企业的虚拟机服务器遭到了lockbit3.0勒…

统计学-R语言-8.3

文章目录 前言例题例题一例题二例题三例题四例题五例题六例题七 总结 前言 本篇介绍的是有关方差知识的题目介绍。 例题 例题一 &#xff08;数据&#xff1a;exercise7_3.RData&#xff09;为研究上市公司对其股价波动的关注程度&#xff0c;一家研究机构对在主板、中小板和…

2022年至2023年广东省职业院校技能大赛高职组“信息安全管理与评估”赛项样题

2022 年至 2023 年广东省职业院校技能大赛高职组“信息安全管理与评估”赛项样题 一、 第一阶段竞赛项目试题 本文件为信息安全管理与评估项目竞赛第一阶段试题&#xff0c;第一阶段内容包 括&#xff1a;网络平台搭建、网络安全设备配置与防护。 本阶段比赛时间为 180 分钟…

代码随想录算法刷题训练营day17

代码随想录算法刷题训练营day17&#xff1a;LeetCode(110)平衡二叉树 LeetCode(110)平衡二叉树 题目 代码 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(…

计算机网络——TCP协议

&#x1f4a1;TCP的可靠不在于它是否可以把数据100%传输过去&#xff0c;而是 1.发送方发去数据后&#xff0c;可以知道接收方是否收到数据&#xff1b;2.如果接收方没收到&#xff0c;可以有补救手段&#xff1b; 图1.TCP组成图 TCP的可靠性是付出代价的&#xff0c;即传输效率…

Linux CPU 负载说明

一、背景 工作中我们经常遇到CPU 负载高&#xff0c;CPU负载高意味着什么&#xff1f; CPU的负载是怎么计算的&#xff1f; top指令中的各个指标代表什么含义&#xff1f; 二、CPU 负载计算方法 在系统出现负载问题&#xff0c;通常会使用uptime和top确认负载&#xff0c;这两…

项目管理平台

技术架构&#xff1a; MySQL、Servlet、JSP 功能模块&#xff1a; 从管理员角度看: 用户登入系统后&#xff0c;可以修改管理员的密码。同时具有以下功能&#xff1a; 1、管理员可以管理具体项目信息。 2、管理员可以管理项目经费信息。 3、管理员可以管理项目资源信息。 4、…

【自然语言处理】【深度学习】文本向量化、one-hot、word embedding编码

因为文本不能够直接被模型计算&#xff0c;所以需要将其转化为向量 把文本转化为向量有两种方式&#xff1a; 转化为one-hot编码转化为word embedding 一、one-hot 编码 在one-hot编码中&#xff0c;每一个token使用一个长度为N的向量表示&#xff0c;N表示词典的数量。 即&…

VsCode CMake调试QT QString等变量不显示具体值,调试中查看qt源码 (可视化调试配置Natvis)

遇到的问题 当我们在VsCode使用CMake来调试QT程序时&#xff0c;可能会出现变量是十六进制的地址&#xff0c;而看不到具体的值。例如&#xff1a; 如何解决 这时候需要手动设置一下natvis &#xff08;资源以上传&#xff0c;可以直接下载&#xff09; 在.vscode文件下找到…

C语言——操作符详解2

目录 0.过渡0.1 不创建临时变量&#xff0c;交换两数0.2 求整数转成二进制后1的总数 1.单目表达式2. 逗号表达式3. 下标访问[ ]、函数调用( )3.1 下标访问[ ]3.2 函数调用( ) 4. 结构体成员访问操作符4.1 结构体4.1.1 结构体的申明4.1.2 结构体变量的定义和初始化 4.2 结构体成…

C++学习| QT快速入门

QT简单入门 QT Creater创建QT项目选择项目类型——不同项目类型的区别输入项目名字和路径选择合适的构建系统——不同构建系统的却别选择合适的类——QT基本类之间的关系Translation File选择构建套件——MinGW和MSVC的区别 简单案例&#xff1a;加法器设计界面——构建加法器界…

配置华为交换机生成树VBST案例

知识改变命运&#xff0c;技术就是要分享&#xff0c;有问题随时联系&#xff0c;免费答疑&#xff0c;欢迎联系 厦门微思网络​​​​​​https://www.xmws.cn 华为认证\华为HCIA-Datacom\华为HCIP-Datacom\华为HCIE-Datacom 思科认证CCNA\CCNP\CCIE 红帽认证Linux\RHCE\RHC…

HTML-框架标签、实体、全局属性和元信息

HTML 1.框架标签 <iframe name"b站" src"https://www.bilibili.com" width"500" height"300" frameborder"0"></iframe>iframe 标签的实际应用&#xff1a; 在网页中嵌入广告。与超链接或表单的 target 配合&a…

Spring Cloud 之Config详解

大家好&#xff0c;我是升仔 在微服务架构中&#xff0c;统一的配置管理是维护大规模分布式系统的关键。Spring Cloud Config为微服务提供集中化的外部配置支持&#xff0c;它可以与各种源代码管理系统集成&#xff0c;如Git、SVN等。本文将详细介绍如何搭建配置服务器、管理客…

【论文笔记】GPT,GPT-2,GPT-3

参考&#xff1a;GPT&#xff0c;GPT-2&#xff0c;GPT-3【论文精读】 GPT Transformer的解码器&#xff0c;仅已知"过去"&#xff0c;推导"未来" 论文地址&#xff1a;Improving Language Understanding by Generative Pre-Training 半监督学习&#xff1…

微信小程序开发 逐级选择地区

1.需求 微信小程序开发,逐级选择地区&#xff08;市、区县、街道、社区、网格&#xff09;&#xff0c;选择每一级然后展示下一级数据。 微信小程序逐级选择 2. 完整代码 2.1. 选择界面 2.1.1. selectArea.wxml <text bindtap"selectGrid">{{gridName}}</…

Git安装详细步骤

目录 1、双击安装包&#xff0c;点击NEXT​编辑 2、更改安装路径&#xff0c;点击NEXT 3、选择安装组件 4、选择开始菜单页 5、选择Git文件默认的编辑器 6、调整PATH环境 7、选择HTTPS后端传输 8、配置行尾符号转换 9、配置终端模拟器与Git Bash一起使用 10、配置额外…

LabVIEW工业机器人系统

介绍了ABB工业机器人与LabVIEW之间进行数据交互的解决方案。通过使用TCP/IP协议的socket通信&#xff0c;实现了机器人坐标数据的读取&#xff0c;为人机交互提供了一个更便捷、更高效的新思路。 系统主要由ABB工业机器人、基于TCP/IP协议的通信接口和LabVIEW软件组成。工业机…

XSS_Labs靶场通关笔记

每一关的方法不唯一&#xff1b;可以结合源码进行分析后构造payload&#xff1b; 通关技巧&#xff08;四步&#xff09;&#xff1a; 1.输入内容看源码变化&#xff1b; 2.找到内容插入点&#xff1b; 3.测试是否有过滤&#xff1b; 4.构造payload绕过 第一关 构造paylo…