【Android】WMS(四)WMS职责

news2024/11/25 15:50:58

WMS职责

在这里插入图片描述

WMS(Window Manager Service)是 Android 系统中的一个系统服务,它是WindowManager的管理者,负责对窗口进行管理、分配资源,以及处理用户的输入事件等问题。WMS是整个系统中非常重要的一个组成部分,可以说,没有 WMS,Android 系统就无法正常运行。

WMS 的职责主要包括以下几个方面:

窗口管理
WMS 负责对窗口的启动、添加和删除进行管理。它会跟踪每一个窗口的状态,并根据需要调整它们的大小和位置。此外,WMS 还负责窗口的层级关系,即哪个窗口在最上层或最下层。

窗口动画
WMS 的动画子系统处理窗口之间的切换效果,例如窗口打开和关闭时的动画效果,以及多个窗口同时出现时的交互效果等。这些动画效果不仅可以美化界面,还可以增强用户体验。

输入系统中转站
WMS 是输入系统的中转站,它会将用户输入的事件派发到最合适的窗口中。例如,当用户点击屏幕时,WMS 会根据焦点窗口的位置和大小等信息,决定哪个窗口应该接受这个事件。

Surface 管理
Surface 是 Android 系统中的一个重要概念,它负责窗口的绘制和显示。WMS 负责管理每个窗口的 Surface,确保每个窗口都有一个可用的 Surface,并根据需要进行分配和释放。

窗口焦点管理
WMS 还负责管理窗口的焦点。当用户与屏幕交互时,WMS 会决定哪个窗口可以接收输入事件,并将焦点从一个窗口转移到另一个窗口。这样可以确保用户的交互体验更加顺畅。

系统权限控制
作为系统服务之一,WMS 还负责处理权限相关的操作。例如,当应用程序向用户请求弹出悬浮窗权限时,WMS 就会介入其中,确保应用程序只能在得到用户的明确允许后才能使用这个权限。

多窗口模式管理
Android 7.0 及以上版本支持多窗口模式,包括分屏模式和自由窗口模式。WMS 负责分配窗口空间,以及处理多个窗口同时显示的情况。例如,在分屏模式下,WMS 会将屏幕区域分成两个部分,并同时显示两个窗口。

WMS 是 Android 系统中非常核心的一个服务,它完成了很多重要的工作,包括窗口管理、动画效果、输入事件处理、Surface 管理、窗口焦点管理、权限控制和多窗口模式管理等。开发者需要深入了解 WMS 的职责和工作原理,才能更好地设计和优化自己的应用程序。

WMS成员

mPolicy:WindowManagerPolicy

WindowManagerPolicy(WMP)类型的变量。WindowManagerPolicy是窗口管理策略的接口类,用来定义一个窗口策略所要遵循的通用规范,并提供了WindowManager所有的特定的UI行为。它的具体实现类为PhoneWindowManager,这个实现类在WMS创建时被创建。WMP允许定制窗口层级和特殊窗口类型以及关键的调度和布局。

mSessions:ArraySet<Session>

ArraySet类型的变量,元素类型为Session。它主要用于进程间通信,其他的应用程序进程想要和WMS进程进行通信就需要经过Session,并且每个应用程序进程都会对应一个Session,WMS保存这些Session用来记录所有向WMS提出窗口管理服务的客户端。

mWindowMap:WindowHashMap

WindowHashMap类型的变量,WindowHashMap继承了HashMap,它限制了HashMap的key值的类型为IBinder,value值的类型为WindowState。WindowState用于保存窗口的信息,在WMS中它用来描述一个窗口。综上得出结论,mWindowMap就是用来保存WMS中各种窗口的集合。

mFinishedStarting:ArrayList<AppWindowToken>

ArrayList类型的变量,元素类型为AppWindowToken,它是WindowToken的子类。要想理解mFinishedStarting的含义,需要先了解WindowToken是什么。

WindowToken
  • 可以理解为窗口令牌,当应用程序想要向WMS申请新创建一个窗口,则需要向WMS出示有效的WindowToken。AppWindowToken作为WindowToken的子类,主要用来描述应用程序的

  • WindowToken结构,
    应用程序中每个Activity都对应一个AppWindowToken。
    WindowToken会将相同组件(比如Acitivity)的窗口(WindowState)集合在一起,方便管理。

WindowState

WindowState表示一个窗口的所有属性,且存在于WMS端,所以它是WMS中事实上的窗口。APP端一个Window,就会在WMS端就会有一个WindowState。

mResizingWindows:ArrayList<WindowState>

ArrayList类型的变量,元素类型为WindowState。
mResizingWindows是用来存储正在调整大小的窗口的列表

mInputManager:InputManagerService

InputManagerService类型的变量,输入系统的管理者。InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS是窗口的管理者,因此,WMS“理所应当”的成为了输入系统的中转站.

WMS服务的启动

在这里插入图片描述

WMS的启动流程可以分为以下几个步骤:

Zygote进程启动

在Android系统中,当一个新应用程序需要创建进程时,会由Zygote进程来负责创建。因此,Zygote进程是整个Android系统的“孪生”起源,是高效启动新进程的关键。

SystemServer进程启动

在Zygote进程启动之后,它会fork出SystemServer进程,然后等待SystemServer进行初始化。

SystemServer初始化

SystemServer进程负责启动和管理包括WMS在内的所有系统服务。首先,它会调用startBootstrapServices()方法,启动一些最基本的服务,包括Zygote和WMS等。接着,SystemServer会启动ActivityManagerService,并通过ActivityManagerService启动应用程序等其他服务。

WMS创建

WMS服务随着SystemServer的启动而启动,它的具体实现类为PhoneWindowManager。WMS在创建时会执行一系列的初始化操作,包括:

(1)创建InputManagerService对象。输入事件管理器(InputManagerService)是WMS的重要部分,WMS一般通过这个类接收手机的输入事件并发送给一个特定的窗口。

(2)创建PolicyManager对象。PolicyManager是用于管理窗口样式的,主要定义了WMS如何布局窗口、绘制窗口的边框、缩放、旋转等一些与窗口样式相关的内容。

(3)创建SessionManager对象。SessionManager用于与客户端进行通信。当一个应用程序想要创建、移动或删除一个窗口时,它会通过SessionManager将请求发送给WMS服务。

WMS启动

完成初始化后,WMS会等待各种事件的发生。例如,在新的应用程序启动时,WMS会根据应用程序的要求创建新的窗口,并对已有窗口进行调度和更新。在用户打开多个应用程序并在这些应用程序之间切换时,WMS也会负责管理窗口的切换。

Window添加(WMS部分)

WMS addWindow方法返回的是addWindow的各种状态,比如添加Window成功,无效的Display等,这些状态被定义在WindowManagerGlobal中。在方法里面主要做了四个事情,如果所示:
在这里插入图片描述

窗口检查

​ 对参数进行进行检查是非常有必要的第一个步骤,大部分函数中都是这样做的,这个很好理解,毕竟如果传入的参数都是错的,后面做过多的内容都是无用功。

​ WMS#addWindow对窗口参数主要做了哪些检查。

	 int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
            appOp);

mPolicy是窗口管理策略的接口,实现类是PhoneWindowManager。在PhoneWindowManager中对窗口的type合法性做了检查。

if (!((type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW)
                || (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW)
                || (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW))) {
            return WindowManagerGlobal.ADD_INVALID_TYPE;
}

接下来的话通过DisplayId来获取窗口要添加到哪个DisplayContentshang,如果没有找到DisplayContent, 则返回错误状态。

final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);

如果窗口是子窗口类型,然后是对父窗口的信息做一些检查,如果为空或者父窗口也是子窗口类型则检查不通过,返回错误类型

if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
    			//通过token获取获取父窗口的信息
                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;
                }
  }

WindowToken相关处理

​ 在WindowToken相关处理这部分内容中,我们先通过DisplayContent尝试获取WindowToken,token为空且有父窗口,则用父窗口的token,token为空没有父窗口自己新建。

//通过DisplayContent获取到WindowToken
WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            // If this is a child window, we want to apply the same type checking rules as the
            // parent window type.
			..........
            if (token == null) {
                .........
                if (hasParent) {
                    // Use existing parent window token for child windows.
                    //有父窗口的用父窗口的Token
                    token = parentWindow.mToken;
                } else {
                    //没有父窗口自己新建一个WindowToken,WindowToken翻译过来是令牌,用于标识一组窗口。
                    final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                    token = new WindowToken(this, binder, type, false, displayContent,
                            session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
                }
            }

WindowState的创建和处理

WindowState是WMS端的事实窗口,通过new的方式新建好一个WindowState之后就进行了相关的判断,比如请求添加窗口的客户端是否死亡、窗口的DisplayContent是否失效。

 final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);
//窗口是否死亡
if (win.mDeathRecipient == null) {
                ProtoLog.w(WM_ERROR, "Adding window client %s"
                        + " that is dead, aborting.", client.asBinder());
                return WindowManagerGlobal.ADD_APP_EXITING;
            }

//DisplayContent是否为空
            if (win.getDisplayContent() == null) {
                ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
.......
//调用WMP的方法,此方法会根据窗口的Type对LayoutParams的一些成员进行修改
displayPolicy.adjustWindowParamsLw(win, win.mAttrs, callingPid, callingUid);
.......
// 将窗口添加到系统中
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
.......
// windowState保存到Map中
mWindowMap.put(client.asBinder(), win);
.......
// 绑定Token和WindowState关系
win.mToken.addWindow(win);

Display的创建和配置

Window Manager Service (WMS) 中,当需要创建一个新的 Display 时,WMS 会通过 DisplayManagerService (DMS) 提供的 createDisplay 方法向 SurfaceFlinger 发送请求,请求其创建新的 Display。具体而言,WMS 会调用 DMS 的 createDisplay 方法,该方法会返回一个 DisplayInfo 对象,这个对象包含了新增 Display 的相关信息,如 ID、尺寸、密度等。

然后,WMS 就会根据这些信息往 SurfaceFlinger 发送相应的命令,告诉它需要创建一个新的 Display,并将其配置为合适的尺寸和密度。SurfaceFlinger 接收到这个命令后,会首先创建一个新的屏幕缓冲区 (Screen Buffer),用于保存该 Display 的图像数据。然后,SurfaceFlinger 会按照指定的尺寸和密度来初始化该 Display,并分配对应的帧缓冲区 (Frame Buffer)

在初始化完成后,SurfaceFlinger 就会将该 Display 的 Surface (也就是屏幕缓冲区) 绑定到指定的硬件显示设备上,并将其设置为可见状态。此时,该 Display 就已经被成功创建并且显示了出来。

需要注意的是,SurfaceFlinger 可以同时支持多个 Display,每个 Display 对应一个 Screen Buffer 和一个 Frame Buffer,它们之间实现了双缓冲机制。这种机制可以保证在进行图像渲染和显示的过程中,不会出现屏幕闪烁或者撕裂等问题。

在创建和配置 Display 的过程中,WMS 主要负责向 DMS 和 SurfaceFlinger 发送相应的命令,而实际的创建和配置工作则是由 SurfaceFlinger 来完成的。这种分工既能确保系统的稳定性和安全性,又能实现高效的屏幕渲染和显示。

Window删除

在这里插入图片描述

1 检查线程的正确性
void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
2 ViewRootImpl相关数据删除

在WindowManagerGlobal方法中,会删除相关的一些数据,如ViewRootImpl、LayoutParams、DecorView,并将DecorView加入到死亡列表中。

void doRemoveView(ViewRootImpl root) {
        synchronized (mLock) {
            //从ViewRootImpl获取到索引值
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                //删除ViewRootImpl列表中的数据
                mRoots.remove(index);
                //删除LayoutParams列表中的数据
                mParams.remove(index);
                //删除DecorView列表中的数据
                final View view = mViews.remove(index);
                //DecorView加入到死亡列表
                mDyingViews.remove(view);
            }
            ......
        }
    ......
}
3 判断是否立即执行删除

这ViewRootImpl中die方法中,会先判断是否立即执行删除,如果立即执行则调用doDie方法,如果不是则通过Handler方法执行删除的信号,等待删除。

  boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
		//immediate 是否立即执行 为ture则立即执行
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }
		......
     
      	//通过Handler发送删除信息,等待删除
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
void doDie() {
    	//检查线程
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            //判断是否删除
            if (mRemoved) {
                return;
            }
            //防止重复调用
            mRemoved = true;
            if (mAdded) {
                //做数据清除 注销操作,调用session的remove方法
                dispatchDetachedFromWindow();
            }

            if (mAdded && !mFirst) {
                destroyHardwareRenderer();
    
                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(
                                        mWindow, null /* postDrawTransaction */);
                            }
                        } catch (RemoteException e) {
                        }
                    }
    				//销毁画布
                    destroySurface();
                }
            }
    
            mAdded = false;
        }
        //调用WindowManagerGlobal移除方法
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

在ViewRootImpl的dispatchDetachedFromWindow方法中会调用Session与WMS进行通信,然后执行移除的操作。

在WMS的removeWindow函数中,先会通过Session和Client获取到当前窗口在WMS的副本也就是WindowState,如果不为空则执行删除操作。

void removeWindow(Session session, IWindow client) {
        synchronized (mGlobalLock) {
           	//获取WindowState
            WindowState win = windowForClientLocked(session, client, false);
            if (win != null) {
                //执行删除
                win.removeIfPossible();
                return;
            }

            // Remove embedded window map if the token belongs to an embedded window
            mEmbeddedWindowController.remove(client);
        }
    }

win.removeIfPossible方法和它的名字一样,并不是直接执行删除操作,而是进行多个条件判断过滤,满足其中一个条件就会return,推迟删除操作。比如V正在运行一个动画,这是就会推迟删除操作知道动画完成。然后调用removeImmediately方法。

void removeImmediately() {
        super.removeImmediately();
        //已经删除
        if (mRemoved) {
            // Nothing to do.
            ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                    "WS.removeImmediately: %s Already removed...", this);
            return;
        }
        //移除标记
        mRemoved = true;
        ......
    
        final DisplayContent dc = getDisplayContent();
        ......
        //policy做移除操作
        dc.getDisplayPolicy().removeWindowLw(this);
        //关闭输入事件渠道
        disposeInputChannel();
        
        mWinAnimator.destroyDeferredSurfaceLocked();
        mWinAnimator.destroySurfaceLocked();
        //Session集合冲移除WindowState
        mSession.windowRemovedLocked();
        .....
      	//集中处理清除工作
        mWmService.postWindowRemoveCleanupLocked(this);
    }

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

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

相关文章

昨晚技术交流群“炸了”,论搞技术的网络工程师究竟能有多严谨?

昨日在IELAB网络实验室学习交流群中发生了一场“激烈”的争论&#xff0c;原本是同往常一样的答题领红包活动&#xff0c;同学对答案产生了异议&#xff0c;从而和Summer赵老师在群里进行了沟通交流&#xff0c;具体是什么情况呢&#xff1f; IELAB每日答题活动 于是乎&#xf…

JMeter 测试笔记(一):认识JMeter

引言&#xff1a; 在现代互联网时代&#xff0c;应用程序的性能已经成为了一个非常重要的问题&#xff0c;并且对于许多公司的生存和发展都起着至关重要的作用。 而JMeter作为一个免费且开源的性能测试工具&#xff0c;可以帮助我们进行各种类型的性能测试&#xff0c;如接口…

阿里巴巴最新开源:Java工程师面试笔记(30万字精华总结 + 面试1300问)吊打面试官绰绰有余

前言 作为一个 Java 程序员&#xff0c;你平时总是陷在业务开发里&#xff0c;每天噼里啪啦忙敲着代码&#xff0c;上到系统开发&#xff0c;下到 Bug 修改&#xff0c;你感觉自己无所不能。然而偶尔的一次聚会&#xff0c;你听说和自己一起出道的同学早已经年薪 50 万&#x…

网卡中的Ring buffer -- 解决 rx_resource_errors 丢包

1、软硬件环境 硬件&#xff1a; 飞腾E2000Q 平台 软件&#xff1a; linux 4.19.246 2、问题现象 网卡在高速收包的过程中&#xff0c;出现 rx error , 细查是 rx_resource_errors 如下&#xff1a; rootE2000-Ubuntu:~# ifconfig eth1 eth1: flags4163<UP,BROADCAST,RU…

Amazon SageMaker:探索AI绘画云端部署新方案

目录 1 从艺术实验到AI绘画2 什么是Amazon SageMaker&#xff1f;3 云端部署AI绘画应用3.1 模型构建与部署3.2 AI绘画测试(文生图) 4 亚马逊云科技中国峰会 1 从艺术实验到AI绘画 在过去&#xff0c;人们只希望基于已有的给定数据做一些预测和拟合&#xff0c;因此判别式模型得…

RFID资产盘点在企业的应用

随着新技术的发展&#xff0c;企业在不断地进行转型&#xff0c;企业为了更好地发展&#xff0c;对企业内部的管理工作越来越重视。在管理中&#xff0c;如何利用先进的科学技术来提高管理效率&#xff0c;成为了企业发展的关键。在资产管理上&#xff0c; RFID技术在资产盘点和…

TCP三次握手建立连接和四次挥手断开连接

TCP的连接与断开 TCP 通过三次握手建立连接&#xff0c;以建立确保数据传输的参数&#xff0c;连接的双方都将初始化与 TCP 连接相关的许多 TCP 状态变量。该连接是一条逻辑连接&#xff0c;其共同状态仅保留在二个通信端系统的TCP程序中。 TCP连接的组成包括&#xff1a;一台…

上门服务app开发|上门服务系统定制打开家政服务的大门

上门服务app可以为用户带来更加便捷的服务体验&#xff0c;不需要用户出门寻找服务商&#xff0c;只需要通过小程序下单即可享受到上门服务&#xff0c;省去了用户的时间和精力。同时上门服务app也为服务商提供了一个更广阔的市场&#xff0c;他们可以通过上门服务系统向更多的…

走进科学之sudo rm -rf,为什么如此危险

关注微信公众号“网络安全学习圈”&#xff0c;回复暗号【网络安全】&#xff0c;立即领取最新网安教程全家桶。 什么是 sudo rm -rf&#xff1f; sudo rm -rf 是一个linux的命令行命令&#xff0c;用于在系统中删除文件和目录。sudo表示以管理员权限运行该命令。rm是remove…

OPEN AI 全新版本来了2.0.0永久免费在线体验构建AI开放生态

V2.0.0 更新内容 后台整体功能重构完善后台增加订阅接口服务后台会陆续扩展订阅更多AI接口服务后台增加分销推广功能OPEN AI注册增加邮箱验证绑定OPEN AI开发者插件生态构建后台分销推广额度累计支持提现现金后台支持查询官方账号key使用量和余量开放接口提供账号绑定和订阅接…

【ChatGLM】记录一次Windows部署ChatGLM-6B流程及遇到的问题

文章目录 部署环境文件下载项目文件模型配置文件模型文件 运行demo遇到的问题 部署环境 系统版本&#xff1a;Windows 10 企业版 版本号&#xff1a;20H2 系统类型&#xff1a;64 位操作系统, 基于 x64 的处理器 处理器&#xff1a;Intel Core™ i7-8700 CPU 3.20GHz 3.19 GH…

软考A计划-电子商务设计师-模拟试题卷四

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

NX/UG二次开发—Parasolid—PK_EDGE_ask_convexity

判断实体边的凸凹性 Convexity Value Example convex PK_EDGE_convexity_convex_c concave PK_EDGE_convexity_concave_c variable the convexity varies along the edge PK_EDGE_convexity_variable_c smooth flat parallel surface normals, both faces have zero f…

cli3 非父子组件传值

这里&#xff0c;App.vue中&#xff0c;引入了parent组件&#xff1b;parent组件中引入了child组件。现在要从app.vue&#xff0c;向child组件传值。 主要文件&#xff1a; 1. 通过事件总线传值 1. src ->util->bus.js // 通过util中的bus&#xff0c;完成两个组件之间…

你的Postman为什么连不了数据库?

postman本身没有数据库连接功能&#xff0c;所以用到了node.js中的xmysql实现Rest API的生成&#xff0c;利用postman进行请求&#xff0c;获取需要的数据&#xff0c;来做数据准备或断言 1.安装 安装node.js&#xff1a;要求版本大于等于7.6 首先保证你的环境上有node.js&a…

打工人,别着急摆烂,看看你到底值多少钱?

2023求职现状&#xff1a; HR&#xff1a;看简历&#xff0c;以为能造飞机&#xff0c;招进来以后发现螺丝都不会拧。 普通求职者&#xff1a;看公司招聘要求&#xff0c;以为这个岗位是造飞机的&#xff0c;没想到进去以后是拧螺丝的。 大龄求职者&#xff1a;以前我都是造飞…

1.平台介绍:FISCO-BCOS 区块链

&#xff08;1&#xff09;概念: FISCO BCOS是由国内企业主导研发、对外开源、安全可控的企业级金融联盟链底层平台。它以联盟链的实际需求为出发点&#xff0c;兼顾性能、安全、可运维性、易用性、可扩展性&#xff0c;支持多种SDK&#xff0c;并提供了可视化的中间件工具&am…

Python3+Selenium2完整的自动化测试实现之旅(六):Python单元测试模块Unittest运用

目录 引言 一、Unittest单元测试框架简介 二、首次使用Unittest模块 三、Unittest模块批量加载和管理用例 写在最后 引言 这篇文章讲述的是Python3Selenium2自动化测试实现之旅的第六篇&#xff0c;主要是介绍如何运用Python单元测试模块Unittest进行测试。 我个人觉得这篇…

【新版】系统架构设计师 - 数学与经济管理

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 数学与经济管理考点摘要最小生成树最短路径网络与最大流量线性规划动态规划预测 - 博弈论预测 - 状态转移矩阵决策 - 不确定型决策决策 - 决策树排队论数学建模数学建模 - 模型分析数学建模 - 模型…

什么是 tokens,ChatGPT里面的Tokens如何计数?

什么是 tokens&#xff0c;ChatGPT里面的Tokens如何计数&#xff1f; 什么是 tokens&#xff1f; Tokens 可以被认为是词语的片段。在 API 处理提示之前&#xff0c;输入会被分解成 tokens。这些 tokens 并不会精确地在单词的开始或结束处切分 - tokens 可以包含尾随的空格甚…