【Android】VirtualDisplay创建流程及原理
Android VirtualDisplay创建流程及原理
- Android DisplayManager提供了createVirtualDisplay接口,用于创建虚拟屏。虚拟屏可用于录屏(网上很多资料说这个功能),分屏幕(比如一块很长的屏幕,通过虚拟屏分出不同的区域)等等。
 
创建VirtualDisplay
- DisplayManager中的函数原型如下。后两个Hide的API,只有平台的应用才可以使用。
 
// frameworks/base/core/java/android/hardware/display/DisplayManager.java
public VirtualDisplay createVirtualDisplay(@NonNull String name,int width, int height, int densityDpi, @Nullable Surface surface, int flags) {
}public VirtualDisplay createVirtualDisplay(@NonNull String name,int width, int height, int densityDpi, @Nullable Surface surface, int flags,@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
}
/** @hide */
public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,@NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface,int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,@Nullable String uniqueId) {
}
/** @hide */
public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,@NonNull VirtualDisplayConfig virtualDisplayConfig,@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
}
 
- 补充一点,MediaProjection中也提供了 createVirtualDisplay这个接口,实际上也是通过调用DisplayManager实现的功能。
 
// frameworks/base/media/java/android/media/projection/MediaProjection.javapublic VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {DisplayManager dm = mContext.getSystemService(DisplayManager.class);// 调用DisplayManager的接口return dm.createVirtualDisplay(this, virtualDisplayConfig, callback, handler);}
 
- 创建VirtualDisplay时,需要传入Surface。**VirtualDisplay上要绘制的内容,实际是通过传入的Surface显示出来的。**比如在主屏(根据物理屏,分配逻辑Display)上创建了一个SurfaceView,通过把这个SurfaceView传给VirtualDisplay。那么VirtualDisplay的 内容,实际上是在主屏的SurfaceView上显示的。下面是一段Android原生的例子。
 
// frameworks/base/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
private Display createVirtualDisplay() {final String displayName = "NavVirtualDisplay";final DisplayInfo displayInfo = new DisplayInfo();mContext.getDisplay().getDisplayInfo(displayInfo);final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);// 创建ImageReader,通过它得到一张SurfacemReader = ImageReader.newInstance(displayInfo.logicalWidth,displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2);assertNotNull("ImageReader must not be null", mReader);// 创建虚拟屏,传入Surface。mVirtualDisplay = displayManager.createVirtualDisplay(displayName, displayInfo.logicalWidth,displayInfo.logicalHeight, displayInfo.logicalDensityDpi, mReader.getSurface(),0 /*flags*/);assertNotNull("virtual display must not be null", mVirtualDisplay);waitForDisplayReady(mVirtualDisplay.getDisplay().getDisplayId());return mVirtualDisplay.getDisplay();
}
 
- 上面的例子中创建虚拟屏,返回Display(实际上是VirtualDislay)对象。有了Display对象,我们就可以将View绑定到这个虚拟的Display上了(绑定网上方法比较多可自行搜索)。关于Surface的创建,有很多种方法,比如通过SurfaceContron+Buffer这种方式也可以。
 - VituralDisplay创建时,需要提供flag。其值定义如下,可通过 “或”将flag组合。
 
 public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1 << 0;public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 1 << 1;public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 1 << 2;public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 1 << 3;public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 1 << 4;public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5;public static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6;public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;public static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8;public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9;public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10;public static final int VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP = 1 << 11;
 
- DisplayManager公开的接口中,有VirtualDisplay.Callback ,提供了其状态的回调。
 
    public static abstract class Callback {/*** Called when the virtual display video projection has been* paused by the system or when the surface has been detached* by the application by calling setSurface(null).* The surface will not receive any more buffers while paused.*/public void onPaused() { }/*** Called when the virtual display video projection has been* resumed after having been paused.*/public void onResumed() { }/*** Called when the virtual display video projection has been* stopped by the system.  It will no longer receive frames* and it will never be resumed.  It is still the responsibility* of the application to release() the virtual display.*/public void onStopped() { }}
 
VirtualDisplay原理
- 关于VirtualDisplay的实现原理,主要从AndroidFramework角度进行分析。

 
// /frameworks/base/core/java/android/hardware/display/DisplayManager.java
public VirtualDisplay createVirtualDisplay(@NonNull String name,int width, int height, int densityDpi, @Nullable Surface surface, int flags) {return createVirtualDisplay(name, width, height, densityDpi, surface, flags, null, null);
}public VirtualDisplay createVirtualDisplay(@NonNull String name,int width, int height, int densityDpi, @Nullable Surface surface, int flags,@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,height, densityDpi);builder.setFlags(flags);if (surface != null) {builder.setSurface(surface);}return createVirtualDisplay(null /* projection */, builder.build(), callback, handler);
}// TODO : Remove this hidden API after remove all callers. (Refer to MultiDisplayService)
/** @hide */
public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,@NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface,int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,@Nullable String uniqueId) {final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,height, densityDpi);builder.setFlags(flags);if (uniqueId != null) {builder.setUniqueId(uniqueId);}if (surface != null) {builder.setSurface(surface);}return createVirtualDisplay(projection, builder.build(), callback, handler);
}/** @hide */
public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,@NonNull VirtualDisplayConfig virtualDisplayConfig,@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {// 走的这里,会调用到DisplayManagerGlobal中。return mGlobal.createVirtualDisplay(mContext, projection, virtualDisplayConfig, callback,handler);
}
 
- DisplayManagerGlobal调用DMS(DisplayManagerService)服务创建虚拟屏,得到DMS返回的DisplayID后,通过DisplayID在Client端创建了VirtualDisplay对象。
 
// /frameworks/base/core/java/android/hardware/display/DisplayManager.java
public VirtualDisplay createVirtualDisplay(@NonNull Context context, MediaProjection projection,@NonNull VirtualDisplayConfig virtualDisplayConfig, VirtualDisplay.Callback callback,Handler handler) {VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler);// 从MediaProjection过来的调用,这个地方非空。IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;int displayId;try {// 告知DMS创建虚拟屏,并返回DisplayIDdisplayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper,projectionToken, context.getPackageName());} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}if (displayId < 0) {Log.e(TAG, "Could not create virtual display: " + virtualDisplayConfig.getName());return null;}// 通过DisplayID,取得Display对象信息(也是调用DMS得到的)Display display = getRealDisplay(displayId);if (display == null) {Log.wtf(TAG, "Could not obtain display info for newly created "+ "virtual display: " + virtualDisplayConfig.getName());try {// 创建失败,需要释放mDm.releaseVirtualDisplay(callbackWrapper);} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}return null;}// 创建VirtualDisplayreturn new VirtualDisplay(this, display, callbackWrapper,virtualDisplayConfig.getSurface());
}
 
- DisplayManagerService(DMS)中创建DisplayDevice并添加到Device列表中管理
 
// /frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
@Override // Binder call
public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) {// 检查uid与包名,是否相符。final int callingUid = Binder.getCallingUid();if (!validatePackageName(callingUid, packageName)) {throw new SecurityException("packageName must match the calling uid");}if (callback == null) {throw new IllegalArgumentException("appToken must not be null");}if (virtualDisplayConfig == null) {throw new IllegalArgumentException("virtualDisplayConfig must not be null");}//  拿到client端传过来的surface对象final Surface surface = virtualDisplayConfig.getSurface();int flags = virtualDisplayConfig.getFlags();if (surface != null && surface.isSingleBuffered()) {throw new IllegalArgumentException("Surface can't be single-buffered");}// 下面开始针对Flag,做一些逻辑判断。if ((flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {flags |= VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;// Public displays can't be allowed to show content when locked.if ((flags & VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {throw new IllegalArgumentException("Public display must not be marked as SHOW_WHEN_LOCKED_INSECURE");}}if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {flags &= ~VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;}if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;}if (projection != null) {try {if (!getProjectionService().isValidMediaProjection(projection)) {throw new SecurityException("Invalid media projection");}flags = projection.applyVirtualDisplayFlags(flags);} catch (RemoteException e) {throw new SecurityException("unable to validate media projection or flags");}}if (callingUid != Process.SYSTEM_UID &&(flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {if (!canProjectVideo(projection)) {throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "+ "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "+ "MediaProjection token in order to create a screen sharing virtual "+ "display.");}}if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {if (!canProjectSecureVideo(projection)) {throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "+ "or an appropriate MediaProjection token to create a "+ "secure virtual display.");}}if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {EventLog.writeEvent(0x534e4554, "162627132", callingUid,"Attempt to create a trusted display without holding permission!");throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "+ "create a trusted virtual display.");}}if (callingUid != Process.SYSTEM_UID&& (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "+ "create a virtual display which is not in the default DisplayGroup.");}}if ((flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) {flags &= ~VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;}// Sometimes users can have sensitive information in system decoration windows. An app// could create a virtual display with system decorations support and read the user info// from the surface.// We should only allow adding flag VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS// to trusted virtual displays.final int trustedDisplayWithSysDecorFlag =(VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS| VIRTUAL_DISPLAY_FLAG_TRUSTED);if ((flags & trustedDisplayWithSysDecorFlag)== VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS&& !checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "createVirtualDisplay()")) {throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission");}final long token = Binder.clearCallingIdentity();try {// 调用内部实现return createVirtualDisplayInternal(callback, projection, callingUid, packageName,surface, flags, virtualDisplayConfig);} finally {Binder.restoreCallingIdentity(token);}
}// /frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,IMediaProjection projection, int callingUid, String packageName, Surface surface,int flags, VirtualDisplayConfig virtualDisplayConfig) {synchronized (mSyncRoot) {if (mVirtualDisplayAdapter == null) {Slog.w(TAG, "Rejecting request to create private virtual display "+ "because the virtual display adapter is not available.");return -1;}// 为虚拟屏创建Device(告知surfaceflinger创建Display)DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(callback, projection, callingUid, packageName, surface, flags,virtualDisplayConfig);if (device == null) {return -1;}// 发送添加Device通知,这里比较重要mDisplayDeviceRepo.onDisplayDeviceEvent(device,DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);// 检查Display是否创建成功final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);if (display != null) {return display.getDisplayIdLocked();}// Something weird happened and the logical display was not created.Slog.w(TAG, "Rejecting request to create virtual display "+ "because the logical display was not created.");mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder());mDisplayDeviceRepo.onDisplayDeviceEvent(device,DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);}return -1;
}
 
- 接下来DMS开始调用SurfaceFlinger的接口,创建Display。并将Display放入自身的List中管理。
 
// /frameworks/base/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback,IMediaProjection projection, int ownerUid, String ownerPackageName, Surface surface,int flags, VirtualDisplayConfig virtualDisplayConfig) {String name = virtualDisplayConfig.getName();// VIRTUAL_DISPLAY_FLAG_SECURE 的用途,是判断是否为安全的Display,这个参数会告知SurfaceFlingerboolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;IBinder appToken = callback.asBinder();// 调用SurfaceFligner创建Display(Display的type是virtual)IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure);final String baseUniqueId =UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ",";final int uniqueIndex = getNextUniqueIndex(baseUniqueId);String uniqueId = virtualDisplayConfig.getUniqueId();if (uniqueId == null) {uniqueId = baseUniqueId + uniqueIndex;} else {uniqueId = UNIQUE_ID_PREFIX + ownerPackageName + ":" + uniqueId;}// 通过SurfaceFligner返回的displayToken,创建Device对象VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,ownerUid, ownerPackageName, surface, flags, new Callback(callback, mHandler),uniqueId, uniqueIndex, virtualDisplayConfig);//  放到虚拟屏的List中管理。mVirtualDisplayDevices.put(appToken, device);try {if (projection != null) {projection.registerCallback(new MediaProjectionCallback(appToken));}appToken.linkToDeath(device, 0);} catch (RemoteException ex) {mVirtualDisplayDevices.remove(appToken);device.destroyLocked(false);return null;}// Return the display device without actually sending the event indicating// that it was added.  The caller will handle it.return device;
}// /frameworks/base/services/core/java/com/android/server/display/DisplayDeviceRepository.java
//  mDisplayDeviceRepo.onDisplayDeviceEvent(device,
//				DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); 
// 这段代码,会调用到下面的函数中。
private void handleDisplayDeviceAdded(DisplayDevice device) {synchronized (mSyncRoot) {DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();if (mDisplayDevices.contains(device)) {Slog.w(TAG, "Attempted to add already added display device: " + info);return;}Slog.i(TAG, "Display device added: " + info);device.mDebugLastLoggedDeviceInfo = info;// 需要是将Device(就是上面创建的虚拟屏幕Device)放入到DMS的管理listmDisplayDevices.add(device);// 通知Device添加,会调用到LogicalDisplayMappe的handleDisplayDeviceAddedLocked中。sendEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);}
}
// /frameworks/base/services/core/java/com/android/server/display/LogicalDisplayMapper.java
private void handleDisplayDeviceAddedLocked(DisplayDevice device) {DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();// Internal Displays need to have additional initialization.// This initializes a default dynamic display layout for INTERNAL// devices, which is used as a fallback in case no static layout definitions// exist or cannot be loaded.if (deviceInfo.type == Display.TYPE_INTERNAL) {initializeInternalDisplayDeviceLocked(device);}// Create a logical display for the new display deviceLogicalDisplay display = createNewLogicalDisplayLocked(device, Layout.assignDisplayIdLocked(false /*isDefault*/));// 刷新布局和display配置applyLayoutLocked();updateLogicalDisplaysLocked();
}
 
- 虚拟屏幕的创建,Client端通过Surface告知的DisplayID,创建VirtualDisplay对象。通过DisplayID,与DMS打交道。DMS服务端,通过SurfaceFlinger创建虚拟屏,拿到SurfaceFligner的DisplayToken,然后通过它创建VirtualDisplayDevice + LogicalDisplay来管理虚拟屏幕。

 
如何上屏?
- 创建虚拟屏幕的时候,会传入了一张Surface(比如绑定主屏的一张Buffer)。虚拟屏通过这张Surface拿到Surface对应的Buffer,将上屏内容绘制到这个Buffer上,然后提交到画面流水线上(SurfaceFlinger)。通过SurfaceFlinger将这个这个Buffer最终由SurfaceFlinger描画并显示到Surface所在那张Display上(根据VirtualDisplay位置去显示。)

 
相关文章:
【Android】VirtualDisplay创建流程及原理
Android VirtualDisplay创建流程及原理 Android DisplayManager提供了createVirtualDisplay接口,用于创建虚拟屏。虚拟屏可用于录屏(网上很多资料说这个功能),分屏幕(比如一块很长的屏幕,通过虚拟屏分出不…...
Linux服务器快速搭建pytorch
Linux服务器搭建pytorch 文章目录 Linux服务器搭建pytorch一、使用FileZilla传输Anaconda二、激活Anaconda环境1.创建一个虚拟环境2.使用已有项目生成requirements.txt3.在虚拟环境中使用requirements.txt安装其他项目相关库 总结 一、使用FileZilla传输Anaconda 提示…...
声音克隆,定制自己的声音,使用最新版Bert-VITS2的云端训练+推理记录
说明 本次训练服务器使用Google Colab T4 GPUBert-VITS2库为:https://github.com/fishaudio/Bert-VITS2,其更新较为频繁,使用其2023.10.12的commit版本:主要参考:B站诸多大佬视频,CSDN:https://blog.csdn.…...
LeetCode讲解篇之198. 打家劫舍
LeetCode讲解篇之198. 打家劫舍 文章目录 LeetCode讲解篇之198. 打家劫舍题目描述题解思路题解代码 题目描述 题解思路 该问题可以通过递推来完成 递推公式: 前n间房的最大金额 max(前n-1间房的最大金额, 前n-2间房的最大金额第n-1间房的最…...
【下载共享文件】Java基于SMB协议 + JCIFS依赖下载Windows共享文件(亲测可用)
这篇文章,主要介绍如何使用JCIFS依赖库,基于SMB协议下载Windows共享文件。 目录 一、搭建Windows共享文件服务 1.1、创建共享文件目录 1.2、添加文件...
【评分卡实现】应用Python中的toad.ScoreCard函数实现评分卡
逻辑回归已经在各大银行和公司都实际运用于业务。之前的文章已经阐述了逻辑回归三部曲——逻辑回归和sigmod函数的由来、...
【数据结构】双链表的相关操作(声明结构体成员、初始化、判空、增、删、查)
双链表 双链表的特点声明双链表的结构体成员双链表的初始化带头结点的双链表初始化不带头结点的双链表初始化调用双链表的初始化 双链表的判空带头结点的双链表判空不带头结点的双链表判空 双链表的插入(按值插入)头插法建立双链表带头结点的头插法每次调…...
解析找不到msvcp140.dll的5个解决方法,快速修复dll丢失问题
在使用计算机过程中,我们也会遇到各种各样的问题。其中,找不到msvcp140.dll修复方法是一个非常普遍的问题。msvcp140.dll是一个动态链接库文件,它是Microsoft Visual C 2015 Redistributable的一部分。这个文件包含了许多用于运行C程序的函…...
代码管理工具 gitlab实战应用
系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 第七章 Spring Cloud 之 GateWay 第八章 Sprin…...
小谈设计模式(27)—享元模式
小谈设计模式(27)—享元模式 专栏介绍专栏地址专栏介绍 享元模式模式结构分析享元工厂(FlyweightFactory)享元接口(Flyweight)具体享元(ConcreteFlyweight)非共享具体享元࿰…...
网络代理技术:隐私保护与安全加固的利器
随着数字化时代的不断演进,网络安全和个人隐私保护变得愈发重要。在这个背景下,网络代理技术崭露头角,成为网络工程师和普通用户的得力助手。本文将深入探讨Socks5代理、IP代理,以及它们在网络安全、爬虫开发和HTTP协议中的关键应…...
orgChart.js组织架构图
OrgChart.js是什么? 基于ES6的组织结构图插件。 特征 支持本地数据和远程数据(JSON)。 基于CSS3过渡的平滑扩展/折叠效果。 将图表对齐为4个方向。 允许用户通过拖放节点更改组织结构。 允许用户动态编辑组织图并将最终层次结构保存为…...
华纳云:SQL Server怎么批量导入和导出数据
在SQL Server中,您可以使用不同的方法来批量导入和导出数据,具体取决于您的需求和数据源。以下是一些常见的方法: 批量导入数据: 使用SQL Server Management Studio (SSMS) 导入向导: 打开SQL Server Management Stud…...
深入了解桶排序:原理、性能分析与 Java 实现
桶排序(Bucket Sort)是一种排序算法,通常用于将一组数据分割成有限数量的桶(或容器),然后对每个桶中的数据进行排序,最后将这些桶按顺序合并以得到排好序的数据集。 桶排序原理 确定桶的数量&am…...
微店店铺所有商品数据接口,微店整店商品数据接口,微店店铺商品数据接口,微店API接口
微店店铺所有商品数据接口是一种允许开发者在其应用程序中调用微店店铺所有商品数据的API接口。利用这一接口,开发者可以获取微店店铺的所有商品信息,包括商品名称、价格、介绍、图片等。 其主要用途是帮助开发者进行各种业务场景的构建,例如…...
SSL证书能选择免费的吗?
当涉及到保护您的网站和您的用户的数据时,SSL证书是必不可少的。SSL证书是一种安全协议,用于加密在Web浏览器和服务器之间传输的数据,例如信用卡信息、登录凭据和个人身份信息。 但是,许多SSL证书都是付费的,这可能会…...
苹果macOS电脑版 植物大战僵尸游戏
植物大战僵尸是一款极富策略性的小游戏,充满趣味性和策略性。主题是植物与僵尸之间的战斗。玩家通过武装多种不同的植物,切换不同的功能,快速有效地把僵尸阻挡在入侵的道路上。不同的敌人,不同的玩法构成五种不同的游戏模式&#…...
【每日一题】ABC311G - One More Grid Task | 单调栈 | 简单
题目内容 原题链接 给定一个 n n n 行 m m m 列的矩阵,问权值最大的子矩阵的权值是多少。 对于一个矩阵,其权值定义为矩阵中的最小值 m i n v minv minv 乘上矩阵中所有元素的和。 数据范围 1 ≤ n , m ≤ 300 1\leq n,m \leq 300 1≤n,m≤300 1 ≤…...
第五十六章 学习常用技能 - 执行 SQL 查询
文章目录 第五十六章 学习常用技能 - 执行 SQL 查询执行 SQL 查询检查对象属性 第五十六章 学习常用技能 - 执行 SQL 查询 执行 SQL 查询 要运行 SQL 查询,请在管理门户中执行以下操作: 选择系统资源管理器 > SQL。如果需要,请选择标题…...
2023年起重信号司索工(建筑特殊工种)证考试题库及起重信号司索工(建筑特殊工种)试题解析
题库来源:安全生产模拟考试一点通公众号小程序 2023年起重信号司索工(建筑特殊工种)证考试题库及起重信号司索工(建筑特殊工种)试题解析是安全生产模拟考试一点通结合(安监局)特种作业人员操作证考试大纲和(质检局)特…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
云原生周刊:k0s 成为 CNCF 沙箱项目
开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
基于单片机的宠物屋智能系统设计与实现(论文+源码)
本设计基于单片机的宠物屋智能系统核心是实现对宠物生活环境及状态的智能管理。系统以单片机为中枢,连接红外测温传感器,可实时精准捕捉宠物体温变化,以便及时发现健康异常;水位检测传感器时刻监测饮用水余量,防止宠物…...
python可视化:俄乌战争时间线关键节点与深层原因
俄乌战争时间线可视化分析:关键节点与深层原因 俄乌战争是21世纪欧洲最具影响力的地缘政治冲突之一,自2022年2月爆发以来已持续超过3年。 本文将通过Python可视化工具,系统分析这场战争的时间线、关键节点及其背后的深层原因,全面…...
NineData数据库DevOps功能全面支持百度智能云向量数据库 VectorDB,助力企业 AI 应用高效落地
NineData 的数据库 DevOps 解决方案已完成对百度智能云向量数据库 VectorDB 的全链路适配,成为国内首批提供 VectorDB 原生操作能力的服务商。此次合作聚焦 AI 开发核心场景,通过标准化 SQL 工作台与细粒度权限管控两大能力,助力企业安全高效…...
Unity-ECS详解
今天我们来了解Unity最先进的技术——ECS架构(EntityComponentSystem)。 Unity官方下有源码,我们下载源码后来学习。 ECS 与OOP(Object-Oriented Programming)对应,ECS是一种完全不同的编程范式与数据架构…...
uni-app学习笔记二十七--设置底部菜单TabBar的样式
官方文档地址:uni.setTabBarItem(OBJECT) | uni-app官网 uni.setTabBarItem(OBJECT) 动态设置 tabBar 某一项的内容,通常写在项目的App.vue的onLaunch方法中,用于项目启动时立即执行 重要参数: indexnumber是tabBar 的哪一项&…...
