当前位置: 首页 > news >正文

View绘制流程-Window创建

前言:

View绘制流程中,主要流程是这样的:

1.用户进入页面,首先创建和绑定Window;

2.首次创建以及后续vsync信号来临时,会请求执行刷新流程;

3.刷新流程完成后,会通知SurfaceFlinger读取数据以及刷新页面。

本篇就是大流程中的第一个环节,重点讲解进入页面后,Window是如何创建以及绑定到系统侧的。

本文的流程主要分为以下三大块:

1.APP侧window和布局的创建流程;

2.APP侧window是如何绑定ViewRootImpl以及注册到系统侧的;

3.系统侧接收到window后,是如何处理的。

一.APP侧Window和View创建

1.1 创建Window

Activity启动时,会经历performLaunchActivity和handleResumeActivity的流程,而window的创建以及decorView的创建,就是在launch的过程中。

我们首先看一下ActivityThread.performLaunchActivity中的代码:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {...//Activity的创建Activity activity = null;activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);//Activity的关联activity.attach();//执行Activity的onCreate流程mInstrumentation.callActivityOnCreate()...
}

我们看一下activity.attach中实现的相关内容:

//android.app.Activity
final void attach(){//1mWindow = new PhoneWindow();//3mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),...);mWindow.setColorMode(info.colorMode);
}//com.android.internal.policy.PhoneWindow.java
public PhoneWindow(@UiContext Context context) {public PhoneWindow(@UiContext Context context) {super(context);mLayoutInflater = LayoutInflater.from(context);}
}

主要执行了以下的逻辑:

1.创建了Activity所绑定的Window,成员名为mWindow,类型为PhoneWindow。

2.在PhoneWindow中,mLayoutInflater赋值。我们的布局就是通过mLayoutInflater对象去解析的。

3.给Window对象绑定WindowManager,这个WindowManager实际上是WindowManagerImpl。

所以此时,Activity中的mWindow,以及PhoneWindow中的mWindowManager和mLayoutInflater都已经有值了。

1.2 DecorView和ContentParent创建

接下来,我们看下callActivityOnCreate的流程。Activity.onCreate流程没有什么有关window的逻辑,但是一般我们都会在onCreate中调用setContentView,这个方法中却大有玄机,我们一起看一下:

//android.app.Window.java
public void setContentView(@LayoutRes int layoutResID) {//1getWindow().setContentView(layoutResID);
}//com.android.internal.policy.PhoneWindow
public void setContentView(int layoutResID) {if (mContentParent == null) {//2installDecor();}...//3mLayoutInflater.inflate(layoutResID, mContentParent);
}private void installDecor() {if(mDecor == null){mDecor = generateDecor(-1);}if (mContentParent == null) {mContentParent = generateLayout(mDecor);}
}

主要执行了以下的逻辑:

1.调用Activity中所持有window去加载layout。

2.首次的时候,通过installDecor方法去创建根布局DecorView以及容器布局mContentParent。mContentParent是Activity上所有的View的父容器。

3.通过mLayoutInflater对象去解析生成布局对象,并且关联到mContentParent上。

具体的解析逻辑不是本文的核心,这里就不去细讲了。

1.3 小结

至此,Activity的创建和其onCreate的流程已经结束,此时Activity中的成员变量mWindowManager和mWindow对象已经完成了赋值,总结一下,如下图所示:

 

二.APP侧Window注册

2.1 Activity和ActivityClientRecord中成员变量赋值

在第一章中,Activity所对应的window及其中的布局创建完成了,所以下一步,就是需要把这个window向系统做一个绑定,这个流程,主要是在Activity的onResume周期中执行的。

首先,我们仍然看一下resume周期所对应的代码,如下:

//ActivityThread.java
public void handleResumeActivity(ActivityClientRecord r, ...) {final Activity a = r.activity;...if (r.window == null && !a.mFinished && willBeVisible) {r.window = r.activity.getWindow();...//1a.mDecor = decor;//2l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;//3wm.addView(decor, l);}
}

主要执行了以下的逻辑:

1.把window中的decor赋值给Activity中的mDecor;

2.设置Window.LayoutParams的type类型为WindowManager.LayoutParams.TYPE_BASE_APPLICATION;在安卓中,type决定window图层优先级,值越大优先级越高,部分图层优先级如下:

public static final int TYPE_BASE_APPLICATION   = 1;//默认Activity对应的图层
public static final int FIRST_SYSTEM_WINDOW     = 2000;//系统弹窗的图层
public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;//Toast的图层
public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;//悬浮窗的图层等级
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;//同上,用于替代上面那个

3.通过windowManager添加decor。这里wm的对象,实际上是WindowManagerImpl,而其中的addView方法中,又交给了WindowManagerGlobal来处理,相关代码如下:

//WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {mGlobal.addView(view, params,...);
}

2.2 WindowManagerGlobal装载Window

接下来,我们看一下WindowManagerGlobal.addView()中的逻辑。

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {ViewRootImpl root;View panelParentView = null;...//1if (windowlessSession == null) {root = new ViewRootImpl(view.getContext(), display);} else {root = new ViewRootImpl(view.getContext(), display, windowlessSession);}view.setLayoutParams(wparams);//2mViews.add(view);mRoots.add(root);mParams.add(wparams);//3root.setView(view, wparams, panelParentView, userId);   
}

主要执行了以下的逻辑:

1.创建ViewRootImpl,ViewRootImpl的角色是页面刷新显示流程的执行者。

2.WindowManagerGlobal的角色是维护客户端所有的页面的,所以自然而然的,其中就维护了很多集合。比如存储所有根布局的mView对象等等,这里就是往集合中注册的。

@UnsupportedAppUsage
private final ArrayList<View> mViews = new ArrayList<View>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
@UnsupportedAppUsage
private final ArrayList<WindowManager.LayoutParams> mParams =new ArrayList<WindowManager.LayoutParams>();

3.上面说到,ViewRootImpl是流程的具体执行者,那么window的绑定自然也是交给其来处理。所以这里通过ViewRootImpl.setView方法来负责。

2.3 ViewRootImpl负责视图的绑定

接下来,我们就看下ViewRootImpl的setView()方法。

ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {...if (mView == null) {mView = view;...//1requestLayout();//2InputChannel inputChannel = null;if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {inputChannel = new InputChannel();}//3res = mWindowSession.addToDisplayAsUser(...);}//mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());
}

这一块的逻辑也是较为清晰的:

首先,通过requestLayout方法尝试进行首次View绘制的完整流程,虽然这时window还没有绑定上,但是并不影响View流程的开始,毕竟View流程中,只有最后的绘制流程才需要和SurfaceFlinger进行交互。

然后,生成InputChannel对象,这个对象类似于一个回调,通过后面的binder接口传递给系统侧。后面window上的点击事件,就会通过InputChannel回调通知到应用侧。后面把inputChannel绑定到WindowInputEventReceiver中,所以APP侧点击事件的来源,就是其中的onInputEvent方法。

最后,把相关的对象传递给系统侧,完成注册。传递的内容如下:

int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,in int viewVisibility, in int layerStackId, in int userId,in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,out InsetsState insetsState, out InsetsSourceControl[] activeControls);

int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, in int userId, in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel, out InsetsState insetsState, out InsetsSourceControl[] activeControls);

整个流程如下图所示:

给系统侧传递的数据列表如下:

对象类型

成员变量名

解释

IWindow

window

对应ViewRootImpl中的IWindow.Stub,传递的一个binder对象

WindowManager.LayoutParams

attrs

window对应的layoutParams属性

int

viewVisibility

根布局的显示状态

int

layerStackId

displayId,显示区域的唯一ID

int

userId

应用的userId

InsetsVisibilities

requestedVisibilities

InputChannel

outInputChannel

事件分发流程中,传递的通道

InsetsState

insetsState

InsetsSourceControl

activeControls

三.系统侧Window绑定

介绍系统侧的流程前,我们先对系统侧的几个核心类简单介绍下,因为大多数的读者对于系统侧的了解较少。

3.1 核心类介绍

类名

功能介绍

com.android.server.wm.Session

一个session对应一个应用进程,负责应用和系统之间的窗口注册/移除,SurfaceSession注册等等。

SurfaceSession

这个类注册是native的实现。负责维护应用和surfaceFlinger之间的连接。所以,APP刷新时是直接通知SF,并不需要经过system_server。

WindowManagerService

顾名思义,用于所有应用的窗口管理。这里只是维护窗口的关系,并负责具体的渲染流程。

3.2 应用进程绑定唯一的IWindowSession

上一章有讲到,一个应用会有一个维护所有的视图的容器WindowManagerGlobal,那么它其中,一定有一个负责和系统侧通信的对象,这个对象就是IWindowSession。相关代码如下:

//WindowManagerGlobal.java
public static IWindowSession getWindowSession() {if (sWindowSession == null) {IWindowManager windowManager = getWindowManagerService();sWindowSession = windowManager.openSession(...);}
}//WindowManagerService.java
@Override
public IWindowSession openSession(IWindowSessionCallback callback) {return new Session(this, callback);
}

也就是说,WindowManagerGlobal中只会持有一个sWindowSession对象,而WindowManagerGlobal对应一个应用的进程,所以IWindowSession是绑定唯一一个应用进程的。IWindowSession是一个binder的引用,其在系统侧的具体实现是Session。上面的addToDisplayAsUser方法,就是通过IWindowSession中提供的binder方法。

3.3 把window注册到系统侧

接下来我们就看一下第二中讲到的addToDisplayAsUser()方法,它负责把应用侧的Window向系统侧注册。我们看一下其在系统侧的实现:

//Session.java
class Session extends IWindowSession.Stub{public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls) {return mService.addWindow(this, ...);}
}

逻辑很简单,直接交给WindowServiceManger的addWindow方法去处理,接下来我们就看下这个方法:

//WindowManagerService.java
public int addWindow(Session session, ...) {WindowState parentWindow = null;...//1final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);...//2WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);...if (token == null) {if( hasParent ){token = parentWindow.mToken;} else if (){token = new WindowToken.Builder(this, binder, type)} else {token = new WindowToken.Builder(this, binder, type)}...}...//3final WindowState win = new WindowState(this, session, );...if  (openInputChannels) {win.openInputChannel(outInputChannel);}...//4win.openInputChannel(outInputChannel);...//5win.attach();win.initAppOpsState();...win.mToken.addWindow(win);
}

首先,根据displayId找到归属的DisplayContent,DisplayContent的作用是用于跟踪一系列的WindowState;

然后,如果当前的window存在parent,则去查询其parent的WindowToken。WindowToken顾名思义,用于识别WindowState;

接下来,生成WindowState,这里的WindowState和APP侧的Window是对应的,WindowState就是在系统侧window的描述并负责和window进行通讯;

然后,绑定事件输入,这里的outInputChannel就是APP侧传递过来的。

最后,通过attch()方法完成绑定,我们重点看一下这个方法:

//WindowState.java
void attach() {if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);mSession.windowAddedLocked();
}//Session.java
void windowAddedLocked() {if (mPackageName == null) {mPackageName = wpc.mInfo.packageName;}if (mSurfaceSession == null) {mSurfaceSession = new SurfaceSession();...mService.mSessions.add(this);}mNumWindow++;
}

简单来说,一个应用首次完成window.attch()的时候,初始化mPackageName和mSurfaceSession()。

而mSurfaceSession对应的就是显示在前台的区域,它初始化后,对应的就是native创建surface以及后续和surfaceFlinger交互的流程了,这个我们后面的文章来讲解。

最后使用mNumWindow记录Window的数量。

3.4 小结

我们仍然做一个小的总结,window注册在系统侧的实现。其实就是接受一个客户端传递过来的binder引用对象IWindow,然后生成一个唯一的对应对象WindowState。并且在应用进程级别生成一个SurfaceSession去维护应用的ViewRootImpl和surfaceFlinger的关系。流程图如下:

 

四.总结

最后,我们做一下总结,整个window的注册流程主要分为三块大块:

1.create流程主要是各种对象的初始化。流程中完成客户端window的创建以及mDecor,mContentParent等相关成员变量的初始化;

2.resume流程主要是window关系的维护。所以创建视图处理类ViewRootImpl,并且使用其把window向系统侧申请绑定;

3.系统收到后主要是生成window在系统侧的对象并记录。所以分别创建Window组的对象WindowToken和Window的系统侧对象WindowState并保存。

整体流程图如下:

 

五.扩展性问题

1.如果onCreate中不调用setContentView,那么会执行后面的流程吗?

答:会的,即使不调用setContentView,只是不会有ContentView,但是DecorView仍然会创建和绑定的,只不过这时候展示的会是黑屏。

相关文章:

View绘制流程-Window创建

前言&#xff1a; View绘制流程中&#xff0c;主要流程是这样的&#xff1a; 1.用户进入页面&#xff0c;首先创建和绑定Window&#xff1b; 2.首次创建以及后续vsync信号来临时&#xff0c;会请求执行刷新流程&#xff1b; 3.刷新流程完成后&#xff0c;会通知SurfaceFlin…...

Jenkins build包时虽然单元测试失败了,但是仍然可以成功build包(最终结束时build success)

1.尝试方案1&#xff1a; 尽管单元测试失败&#xff0c;Jenkins Maven仍然可以获得成功-Java 学习之路 将 -Dmaven.test.failure.ignorefalse 添加到 MAVEN_OPTS artifactoryMaven {goals "-U clean install -Dmaven.test.skipfalse -DallowSnapshotstrue -Dmaven.te…...

【vue3】基础知识点-setup语法糖

学习vue3&#xff0c;都会从基础知识点学起。了解setup函数&#xff0c;ref&#xff0c;recative&#xff0c;watch、comptued、pinia等如何使用 今天说vue3组合式api&#xff0c;setup函数 在学习过程中一开始接触到的是这样的&#xff0c;定义数据且都要通过return返回 <…...

idol!! 2023牛客暑期多校训练营6 C

登录—专业IT笔试面试备考平台_牛客网 题目大意&#xff1a;定义n!!等于与n的奇偶性相同的所有小于等于n的数的阶乘之和&#xff0c;问n!!的末尾有多少0 1<n<1e18 思路&#xff1a;因为末尾0的来源是2*5&#xff0c;而2的个数明显比5的个数多得多&#xff0c;所以末尾…...

深入理解Jdk5引入的Java泛型:类型安全与灵活性并存

深入理解Jdk5引入的Java泛型&#xff1a;类型安全与灵活性并存 ​ 在Java的中&#xff0c;有一个强大的工具&#xff0c;它可以让你在编写代码时既保持类型安全&#xff0c;又享受灵活性。**这个工具就是——泛型&#xff08;Generics&#xff09;。**本文将引导你深入了解Java…...

idea在控制台中输出文字显示乱码

VM options中加入下面这行 -Dfile.encodingutf-8...

hacksudo3 通关详解

环境配置 一开始桥接错网卡了 搞了半天 改回来就行了 信息收集 漏洞发现 扫个目录 大概看了一眼没什么有用的信息 然后对着login.php跑了一下弱口令 sqlmap 都没跑出来 那么利用点应该不在这 考虑到之前有过dirsearch字典太小扫不到东西的经历 换个gobuster扫一下 先看看g…...

CentOS 虚拟机磁盘扩容(非常实用)

新手村的选手在刚开始安装使用 CentOS 虚拟机时&#xff0c;很多选项都会按照推荐操作来&#xff0c;比如&#xff1a;磁盘推荐大小为 20 GB&#xff1b;但随着后面的使用&#xff0c;总会因为“磁盘根目录不足”原因&#xff0c;而导致软件无法安装、虚拟机无法正常运行等&…...

docker案例复现

$uri导致的CRLF注入漏洞 前期准备dockerdocker compose 漏洞配置 前期准备 docker 要完成这样的测试&#xff0c;需要我们有一定的环境&#xff0c;也就是需要大家去安装docker 更新系统软件包&#xff1a; sudo yum update 安装 Docker 的依赖软件包&#xff1a; sudo yum …...

淘宝资源采集(从零开始学习淘宝数据爬取)

1. 为什么要进行淘宝数据爬取&#xff1f; 淘宝数据爬取是指通过自动化程序从淘宝网站上获取数据的过程。这些数据可以包括商品信息、销售数据、评论等等。淘宝数据爬取可以帮助您了解市场趋势、优化您的产品选择以及提高销售额。 淘宝作为全球的电商平台&#xff0c;每天都有…...

【C语言】预处理详解

本文目录 1 预定义符号 2 #define 2.1 #define 定义标识符 2.2 #define 定义宏 2.3 #define 替换规则 2.4 #和## 2.5 带副作用的宏参数 2.6 宏和函数对比 2.7 命名约定 3 #undef 4 命令行定义 5 条件编译 6 文件包含 6.1 头文件被包含的方式 6.2 嵌套文件包含 1 预定义符号 __…...

2023中国(合肥)场景创新峰会成功举办,全息网御被纳入《合肥市第二批场景能力清单》

场景作为重要的城市资源&#xff0c;在驱动科技创新、产业发展、城市治理方面发挥着重要作用。近年来&#xff0c;为促进数字技术与实体经济深度融合&#xff0c;加速前沿科技转化落地、吸引全球创新资源集聚&#xff0c;合肥市聚焦“双找”&#xff1a;为产品找场景&#xff0…...

QT QLCDNumber 使用详解

本文详细的介绍了QLCDNumber控件的各种操作&#xff0c;例如&#xff1a;新建界面、源文件、设置显示位数、设置进制、设置外观、设置小数点、设置溢出、显示事件、其它文章等等操作。 实际开发中&#xff0c;一个界面上可能包含十几个控件&#xff0c;手动调整它们的位置既费时…...

明年,HarmonyOS不再兼容Android应用!

2023年华为开发者大会&#xff0c;不知道各位老铁们是否观看了&#xff0c;一个震撼的消息就是&#xff0c;首次公开了HarmonyOS NEXT的概念&#xff0c;简而言之就是&#xff0c;这是一款专为开发者打造的预览版操作系统&#xff0c;旨在提供"纯正鸿蒙操作系统"的体…...

华为OD机试 - 人气最高的店铺(Java JS Python)

题目描述 某购物城有m个商铺,现决定举办一场活动选出人气最高店铺。 活动共有n位市民参与,每位市民只能投一票,但1号店铺如果给该市民发放 q 元的购物补贴,该市民会改为投1号店铺。 请计算1号店铺需要最少发放多少元购物补贴才能成为人气最高店铺(即获得的票数要大于其…...

mysql sql 语句sum求和嵌套数学表达式

今天有个需求, 已减高度 高度 x 单双开(单开1 双开2) x 2,要直接写在sql语句中。 表字段 包含 高度 和 单双开字段 值是字符串 (双开 左单开 右单开) -- 已减高度 2 * 单双开 * 高度 sum( -- 求和 表达式 已减高度 2 * 单双开 * 高度 t_cloth.hegiht * 2 * (case WHEN l…...

Java课题笔记~ Servlet编程

1.Servlet编程基础 (1)什么是Servlet Servlet是基于Java语言的Web编程技术&#xff0c;部署在服务器端的Web容器里&#xff0c;获取客户端的访问请求&#xff0c;并根据请求生成响应信息返回给客户端。 创建Servlet的方式&#xff0c;有 如下图&#xff1a;一般创建Servlet都…...

修改IDEA的idea.vmoptions参数导致IDEA无法打开(ReservedCodeCacheSize)

事发原因 Maven导依赖的时候OOM&#xff0c;因此怀疑是内存太小&#xff0c;尝试修改idea.vmoptions的参数&#xff0c;然后发现IDEA重启后打不开了&#xff0c;卸载重装后也无法打开。。。 实际上如果导包爆出OOM的话应该调整下图参数&#xff0c;不过这都是后话了 解决思路…...

P1321 单词覆盖还原

题目描述 一个长度为 l l l 的字符串中被反复贴有 boy 和 girl 两单词&#xff0c;后贴上的可能覆盖已贴上的单词&#xff08;没有被覆盖的用句点表示&#xff09;&#xff0c;最终每个单词至少有一个字符没有被覆盖。问贴有几个 boy 几个 girl&#xff1f; 输入格式 一行被…...

GODOT游戏引擎简介,包含与unity性能对比测试,以及选型建议

GODOT&#xff0c;是一个免费开源的3D引擎。本文以unity作对比&#xff0c;简述两者区别和选型建议。由于是很久以前写的ppt&#xff0c;技术原因视频和部分章节丢失了。建议当做业务参考。 GODOT目前为止遇到3个比较重大的基于&#xff0c;第一个是oprea的合作奖&#xff0c;…...

Tailwind CSS 实战:基于 Kooboo 构建 AI 对话框页面(六):图片上传交互功能

在 《Tailwind CSS 实战&#xff1a;基于 Kooboo 构建 AI 对话框页面&#xff08;五&#xff09;》 中&#xff0c;完成了语音交互功能的优化。本文作为该系列教程的第六篇&#xff0c;将聚焦于图片上传功能的开发。通过集成图片上传与预览能力&#xff0c;我们将进一步完善 AI…...

C#中的依赖注入

1. 依赖注入&#xff08;Dependency Injection, DI&#xff09;概述 定义 &#xff1a;依赖注入是一种设计模式&#xff0c;允许将组件的依赖关系从内部创建转移到外部提供。这样可以降低组件之间的耦合度&#xff0c;提高代码的可测试性、可维护性和可扩展性。 核心思想 &…...

模板方法模式:优雅封装不变,灵活扩展可变

引言:代码复用与扩展的艺术 在日常开发中,我们常遇到核心流程固定但某些步骤需差异化的场景。例如: 数据库操作的通用流程(连接→执行→关闭)HTTP请求的固定步骤(构建请求→发送→解析响应)报表生成的骨架(数据获取→格式转换→输出)模板方法模式正是为解决这类问题而…...

Linux 特殊权限位详解:SetUID, SetGID, Sticky Bit

Linux 特殊权限位详解:SetUID, SetGID, Sticky Bit 在Linux权限系统中,除了基本的读、写(w)、执行(x)权限外,还有三个特殊权限位:SetUID、SetGID和Sticky Bit。这些权限位提供了更精细的权限控制机制,尤其在需要临时提升权限或管理共享资源时非常有用。 一、SetUID (s位…...

web前端开发如何适配各分辨率

在开发Web应用时&#xff0c;适配不同的显示器分辨率是确保用户体验一致性的关键。以下是一些常见的显示器分辨率。 常见的显示器分辨率 PC屏幕分辨率 1366 x 768&#xff1a;普通液晶显示器 1920 x 1080&#xff1a;高清液晶显示器 2560 x 1440&#xff1a;2K高清显示器 4096…...

最新研究揭示云端大语言模型防护机制的成效与缺陷

一项全面新研究揭露了主流云端大语言模型&#xff08;LLM&#xff09;平台安全机制存在重大漏洞与不一致性&#xff0c;对当前人工智能安全基础设施现状敲响警钟。该研究评估了三大领先生成式AI平台的内容过滤和提示注入防御效果&#xff0c;揭示了安全措施在阻止有害内容生成与…...

深入浅出多路归并:原理、实现与实战案例解析

文章目录 二路归并多路归并方法一&#xff1a;指针遍历&#xff08;多指针比较法&#xff09;方法二&#xff1a;小根堆法&#xff08;最小堆归并&#xff09; 实际场景外部排序 经典题目丑数Ⅱ方法一&#xff1a;三指针法方法二&#xff1a;优先队列法&#xff08;K路归并&…...

Java转Go日记(五十九):参数验证

1. 结构体验证 用gin框架的数据验证&#xff0c;可以不用解析数据&#xff0c;减少if else&#xff0c;会简洁许多。 package mainimport ("fmt""time""github.com/gin-gonic/gin" )//Person .. type Person struct {//不能为空并且大于10Age …...

重启路由器ip不变怎么回事?原因分析与解决方法

在日常生活中&#xff0c;我们经常会遇到网络问题&#xff0c;而重启路由器是解决网络故障的常用方法之一。然而&#xff0c;有些用户发现&#xff0c;即使重启了路由器&#xff0c;自己的IP地址却没有变化&#xff0c;这让他们感到困惑。那么&#xff0c;重启路由器IP不变是怎…...

DDD架构实战 领域层 事件驱动

目录 核心实现&#xff1a; 这种实现方式的优势&#xff1a; 在实际项目中&#xff0c;你可能需要&#xff1a; 事件驱动往往是在一个微服务内部实现的 领域时间是DDD架构中比较常见的概念 在领域层内部的一个模型更改了状态或者发生了一些行为 向外发送一些通知 这些通…...