【Android 13源码分析】WindowContainer窗口层级-3-实例分析
在安卓源码的设计中,将将屏幕分为了37层,不同的窗口将在不同的层级中显示。
对这一块的概念以及相关源码做了详细分析,整理出以下几篇。
【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树
【Android 13源码分析】WindowContainer窗口层级-2-构建流程
【Android 13源码分析】WindowContainer窗口层级-3-实例分析
【Android 13源码分析】WindowContainer窗口层级-4-Surface树
当前为第三篇,以应用窗口和系统窗口2大类型窗口的挂载为例介绍窗口是如何挂载到层级树中的。
这篇看完对AOSP中整个窗口树就有了比较完整的了解。
1. 应用窗口挂载
应用启动流程中会触发ActivityRecord,Task,WindowState的创建与挂载,其中WindowState的处理上在addWindow流程。
ActivityTaskManagerService::startActivityActivityTaskManagerService::startActivityAsUserActivityTaskManagerService::startActivityAsUserActivityStartController::obtainStarterActivityStarter::executeActivityStarter::executeRequest -- 构建 ActivityRecord --2.2.1 创建ActivityRecordActivityStarter::startActivityUncheckedActivityStarter::startActivityInner -- 2.2.2 关键函数startActivityInnerActivityStarter::getOrCreateRootTask -- 2.2.2.1 创建或者拿到TaskActivityStarter::setNewTask -- 2.2.2.2 将task与activityRecord 绑定RootWindowContainer::resumeFocusedTasksTopActivities --2.2.2.3 显示ActivityaddWindow流程调用链:
WindowManagerImpl::addView --- 创建ViewRootImplWindowManagerGlobal::addView ViewRootImpl::setView --- 与WMS通信 addViewSession.addToDisplayAsUserWindowManagerService::addWindowWindowState::init -- WindowState的创建WindowToken::addWindow -- WindowState的挂载
1.1 ActivityRecord的创建
启动流程开始的时候会执行到ActivityStarter::executeRequest,在这个方法里会创建一个ActivityRecord
# ActivityStarterprivate int executeRequest(Request request) {......final ActivityRecord r = new ActivityRecord.Builder(mService).setCaller(callerApp).setLaunchedFromPid(callingPid).setLaunchedFromUid(callingUid).setLaunchedFromPackage(callingPackage).setLaunchedFromFeature(callingFeatureId).setIntent(intent).setResolvedType(resolvedType).setActivityInfo(aInfo).setConfiguration(mService.getGlobalConfiguration()).setResultTo(resultRecord).setResultWho(resultWho).setRequestCode(requestCode).setComponentSpecified(request.componentSpecified).setRootVoiceInteraction(voiceSession != null).setActivityOptions(checkedOptions).setSourceRecord(sourceRecord).build();......// 继续执行startActivityUncheckedmLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,request.voiceInteractor, startFlags, true /* doResume */, checkedOptions,inTask, inTaskFragment, restrictedBgActivity, intentGrants);......}
tips: ActivityRecord的构造方法会创建一个Token,这个token就是阅读源码经常看到看到代表activity的那个token。
1.2 Task的创建与挂载
流程开始会执行到ActivityStarter::startActivityInner方法,在这里会执行ActivityStarter::getOrCreateRootTask方法来创建(获取)一个Task
调用链如下:
ActivityStarter::getOrCreateRootTaskRootWindowContainer::getOrCreateRootTaskRootWindowContainer::getOrCreateRootTaskTaskDisplayArea::getOrCreateRootTaskTaskDisplayArea::getOrCreateRootTaskTask::Build ---创建Task
主流程代码
# ActivityStarterprivate Task getOrCreateRootTask(ActivityRecord r, int launchFlags, Task task,ActivityOptions aOptions) {final boolean onTop =(aOptions == null || !aOptions.getAvoidMoveToFront()) && !mLaunchTaskBehind;final Task sourceTask = mSourceRecord != null ? mSourceRecord.getTask() : null;return mRootWindowContainer.getOrCreateRootTask(r, aOptions, task, sourceTask, onTop,mLaunchParams, launchFlags);}// onTop 表示是否要移到到当前栈顶,那肯定是要的,新启动的Activity当前要再最上面,这里 aOptions 为null,所以为true// sourceTask 表示从哪里启动的,当前launch所在的Task 就是sourceTask# RootWindowContainerTask getOrCreateRootTask(@Nullable ActivityRecord r, @Nullable ActivityOptions options,@Nullable Task candidateTask, boolean onTop) {return getOrCreateRootTask(r, options, candidateTask, null /* sourceTask */, onTop,null /* launchParams */, 0 /* launchFlags */);}Task getOrCreateRootTask(@Nullable ActivityRecord r,@Nullable ActivityOptions options, @Nullable Task candidateTask,@Nullable Task sourceTask, boolean onTop,@Nullable LaunchParamsController.LaunchParams launchParams, int launchFlags) {......final int activityType = resolveActivityType(r, options, candidateTask);if (taskDisplayArea != null) {if (canLaunchOnDisplay(r, taskDisplayArea.getDisplayId())) {// 重点*1. 传递到TaskDisplayAreareturn taskDisplayArea.getOrCreateRootTask(r, options, candidateTask,sourceTask, launchParams, launchFlags, activityType, onTop);} else {taskDisplayArea = null;}}......}// 经过同名调用后,逻辑进入到 TaskDisplayArea# TaskDisplayAreaTask getOrCreateRootTask(int windowingMode, int activityType, boolean onTop,@Nullable Task candidateTask, @Nullable Task sourceTask,@Nullable ActivityOptions options, int launchFlags) {if(....) {// 拿到之前创建的Taskreturn candidateTask.getRootTask();}......// 第一次显示所以是新建Taskreturn new Task.Builder(mAtmService).setWindowingMode(windowingMode).setActivityType(activityType).setOnTop(onTop).setParent(this) // 主要这个this被设置为Parent。所以直接挂载到了DefaultTaskDisplayArea下.setSourceTask(sourceTask).setActivityOptions(options).setLaunchFlags(launchFlags).build();}// 看方法名是获取或创建Task, 这边是新启动的Activity所以需要创建Task。如果是以默认启动方式打开应用内的另一个Activity,就走的是上面的 return candidateTask.getRootTask();接下来就是真正触发Task的创建。// 另外设置的parent就是层级结构树应用所在的名为“DefaultTaskDisplayArea”的TaskDisplayArea# Task# Task.BuilderTask build() {if (mParent != null && mParent instanceof TaskDisplayArea) {validateRootTask((TaskDisplayArea) mParent);}if (mActivityInfo == null) {mActivityInfo = new ActivityInfo();mActivityInfo.applicationInfo = new ApplicationInfo();}mUserId = UserHandle.getUserId(mActivityInfo.applicationInfo.uid);mTaskAffiliation = mTaskId;mLastTimeMoved = System.currentTimeMillis();mNeverRelinquishIdentity = true;mCallingUid = mActivityInfo.applicationInfo.uid;mCallingPackage = mActivityInfo.packageName;mResizeMode = mActivityInfo.resizeMode;mSupportsPictureInPicture = mActivityInfo.supportsPictureInPicture();if (mActivityOptions != null) {mRemoveWithTaskOrganizer = mActivityOptions.getRemoveWithTaskOranizer();}// 重点* 1. 创建taskfinal Task task = buildInner();task.mHasBeenVisible = mHasBeenVisible;// Set activity type before adding the root task to TaskDisplayArea, so home task can// be cached, see TaskDisplayArea#addRootTaskReferenceIfNeeded().if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {task.setActivityType(mActivityType);}// 重点* 2. 入栈 这里的 mOnTop为trueif (mParent != null) {if (mParent instanceof Task) {final Task parentTask = (Task) mParent;parentTask.addChild(task, mOnTop ? POSITION_TOP : POSITION_BOTTOM,(mActivityInfo.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);} else {mParent.addChild(task, mOnTop ? POSITION_TOP : POSITION_BOTTOM);}}// Set windowing mode after attached to display area or it abort silently.if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {task.setWindowingMode(mWindowingMode, true /* creating */);}// 返回return task;}// 创建Task buildInner() {return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,mAskedCompatMode, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,mRealActivitySuspended, mUserSetupComplete, mMinWidth, mMinHeight,mActivityInfo, mVoiceSession, mVoiceInteractor, mCreatedByOrganizer,mLaunchCookie, mDeferTaskAppear, mRemoveWithTaskOrganizer);}
小结:
最后描述一下最后创建的2个重点部分:
- 看到通过buildInner 创建了一个task,而buildInner 也很简单粗暴,通过各个变量直接new Task 对象。
- mParent 不为null, 是 因为在创建的时候 setParent(this),当前的这个this,就是 getDefaultTaskDisplayArea返回的也就是应用Activity存在的"DefaultTaskDisplayArea"。
在 RootWindowContainer::getOrCreateRootTask 体现。

注意log里的 #17 的这个Task,与前面的层级结构树新增的Task,是对应的上的。而且this= DefaultTaskDisplayArea 说明也确实是往DefaultTaskDisplayArea里添加了。
1.3 ActivityRecord的挂载
调用链
ActivityStarer::setNewTask
ActivityStarer::addOrReparentStartingActivity
主流程代码
# ActivityStarerprivate void setNewTask(Task taskToAffiliate) {// 为truefinal boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;// 就是mTargetRootTask,也就是刚刚创建的Taskfinal Task task = mTargetRootTask.reuseOrCreateTask(mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);task.mTransitionController.collectExistenceChange(task);// ActivityRecord的挂载addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask");// 需要注意这里的日志打印ProtoLog.v(WM_DEBUG_TASKS, "Starting new activity %s in new task %s",mStartActivity, mStartActivity.getTask());// mLaunchTaskBehind 为false,所以taskToAffiliate 为null if (taskToAffiliate != null) {mStartActivity.setTaskToAffiliateWith(taskToAffiliate);}}
这里的task 和mTargetRootTask是同一个对象, 进源码跟到流程也是一样。
然后进入 addOrReparentStartingActivity
# ActivityStarerprivate void addOrReparentStartingActivity(@NonNull Task task, String reason) {// newParent = task 都是刚刚创建的TaskTaskFragment newParent = task;......if (mStartActivity.getTaskFragment() == null|| mStartActivity.getTaskFragment() == newParent) {// 重点, 将 ActivityRecord挂在到新创建的Task中,并且是顶部newParent.addChild(mStartActivity, POSITION_TOP);} else {mStartActivity.reparent(newParent, newParent.getChildCount() /* top */, reason);}}
这里的逻辑设计到的Task就是上一步创建的Task,mStartActivity则是“电话”在之前逻辑创建的ActivityRecord.
setNewTask的堆栈信息如下

另外这段逻辑里有个ProtoLog打印,日志如下:

1.4 WindowState的创建与挂载
WindowManagerService::addWindowWindowState::init -- WindowState的创建WindowToken::addWindow -- WindowState的挂载
# WindowManagerServicepublic int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls) {......// token处理// 创建WindowStatefinal WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);......// 7. 窗口添加进容器win.attach();......win.mToken.addWindow(win);......}"win.mToken"窗口的token是ActyivityRecord# ActivityRecord@Overridevoid addWindow(WindowState w) {super.addWindow(w);......}直接调用其父类方法,ActivityRecord是WindowToken# WindowTokenvoid addWindow(final WindowState win) {ProtoLog.d(WM_DEBUG_FOCUS,"addWindow: win=%s Callers=%s", win, Debug.getCallers(5));if (win.isChildWindow()) {// Child windows are added to their parent windows.return;}// This token is created from WindowContext and the client requests to addView now, create a// surface for this token.// 真正添加进子容器,调用的是WindowContainer的方法if (!mChildren.contains(win)) {ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", win, this);// 定义在WindowContainer中,其实就是挂载到父容器下了addChild(win, mWindowComparator);mWmService.mWindowsChanged = true;// TODO: Should we also be setting layout needed here and other places?}}
2. 系统窗口挂载
2.1 WindowToken,WindowState的创建与挂载
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls) {......// 系统应用获取不到tokenWindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);......if (token == null) {......} else {final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();token = new WindowToken.Builder(this, binder, type).setDisplayContent(displayContent).setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow).setRoundedCornerOverlay(isRoundedCornerOverlay).build();}}// 创建WindowStatefinal WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);......// 窗口添加进容器win.attach();......win.mToken.addWindow(win);......
WindowState的创建和应用窗口一样,区别在与WindowToken,系统窗口执行addWindow方法是没有token的,所以会执行创建逻辑。
在创建的时候会根据窗口类型选择挂载的层级。
2.2 WindowToken的挂载
WMS::addWindowWindowToken::initDisplayContent::addWindowTokenmTokenMap::put -- 存入mTokenMapDisplayContent::findAreaForToken -- 找到对应的层级DisplayContent::findAreaForWindowTypeDisplayAreaPolicyBuilder.Result::findAreaForWindowTypeRootDisplayArea::findAreaForWindowTypeInLayer -- 在 mAreaForLayer中根据type查找对应的位置DisplayArea.Tokens::addChild -- 挂载
WindowToken的构造方法如下:
# WindowTokenprotected WindowToken(WindowManagerService service, IBinder _token, int type,boolean persistOnEmpty, DisplayContent dc, boolean ownerCanManageAppTokens,boolean roundedCornerOverlay, boolean fromClientToken, @Nullable Bundle options) {super(service);token = _token;windowType = type;......if (dc != null) {// 添加tokendc.addWindowToken(token, this);}}
创建WindowToken的时候会由DisplayContent执行挂载逻辑,
# DisplayContentDisplayAreaPolicy mDisplayAreaPolicy;void addWindowToken(IBinder binder, WindowToken token) {......// 放入集合mTokenMap.put(binder, token);if (token.asActivityRecord() == null) {......// 找到对应的位置挂载final DisplayArea.Tokens da = findAreaForToken(token).asTokens();da.addChild(token);}}DisplayArea findAreaForToken(WindowToken windowToken) {// 根据type查找return findAreaForWindowType(windowToken.getWindowType(), windowToken.mOptions,windowToken.mOwnerCanManageAppTokens, windowToken.mRoundedCornerOverlay);}DisplayArea findAreaForWindowType(int windowType, Bundle options,boolean ownerCanManageAppToken, boolean roundedCornerOverlay) {// 应用类型if (windowType >= FIRST_APPLICATION_WINDOW && windowType <= LAST_APPLICATION_WINDOW) {return mDisplayAreaPolicy.getTaskDisplayArea(options);}// 输入法窗口if (windowType == TYPE_INPUT_METHOD || windowType == TYPE_INPUT_METHOD_DIALOG) {return getImeContainer();}// 其他类型return mDisplayAreaPolicy.findAreaForWindowType(windowType, options,ownerCanManageAppToken, roundedCornerOverlay);}
状态栏不属于应用窗口,走后面的逻辑,DisplayAreaPolicy是个接口,真正的实现是DisplayAreaPolicyBuilder的内部类Result
# DisplayAreaPolicyBuilderstatic class Result extends DisplayAreaPolicy {final BiFunction<Integer, Bundle, RootDisplayArea> mSelectRootForWindowFunc;......@Overridepublic DisplayArea.Tokens findAreaForWindowType(int type, Bundle options,boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) {return mSelectRootForWindowFunc.apply(type, options).findAreaForWindowTypeInLayer(type,ownerCanManageAppTokens, roundedCornerOverlay);}......}mSelectRootForWindowFunc是一个存放RootDisplayArea的map,所以后续逻辑在RootDisplayArea中// 根据type 找到在容器树的位置, 如果是应用或者输入法都走不到这# RootDisplayArea// 这个就是层级树的private DisplayArea.Tokens[] mAreaForLayer;@NullableDisplayArea.Tokens findAreaForWindowTypeInLayer(int windowType, boolean ownerCanManageAppTokens,boolean roundedCornerOverlay) {// 获取到typeint windowLayerFromType = mWmService.mPolicy.getWindowLayerFromTypeLw(windowType,ownerCanManageAppTokens, roundedCornerOverlay);if (windowLayerFromType == APPLICATION_LAYER) {throw new IllegalArgumentException("There shouldn't be WindowToken on APPLICATION_LAYER");}// 根据type查找对应的位置return mAreaForLayer[windowLayerFromType];}
getWindowLayerFromTypeLw会根据type找到对应的层级,返回一个int。
然后根据这个值去mAreaForLayer拿到对应的DisplayArea.Tokens,将系统窗口的WindowToken挂载进去
mAreaForLayer其实就是开始构建层级树的那个集合。
2.2.1 mAreaForLayer的赋值
在开机构建窗口层级树的逻辑,最后会执行到RootDisplayArea::onHierarchyBuilt将层级树的集合传递出去。
# DisplayAreaPolicyBuilder.HierarchyBuilderprivate final RootDisplayArea mRoot;private void build(@Nullable List<HierarchyBuilder> displayAreaGroupHierarchyBuilders) {......// 层级树的构建// 通知根节点已经完成了所有DisplayArea的添加 (将displayAreaForLayer保存在RootDisplayArea成员变量roomAreaForLayer中,供后面逻辑使用)mRoot.onHierarchyBuilt(mFeatures, displayAreaForLayer, featureAreas);}
RootDisplayArea下的mAreaForLayer变量赋值
# RootDisplayAreaprivate DisplayArea.Tokens[] mAreaForLayer;void onHierarchyBuilt(ArrayList<Feature> features, DisplayArea.Tokens[] areaForLayer,Map<Feature, List<DisplayArea<WindowContainer>>> featureToDisplayAreas) {if (mHasBuiltHierarchy) {throw new IllegalStateException("Root should only build the hierarchy once");}mHasBuiltHierarchy = true;mFeatures = Collections.unmodifiableList(features);// 赋值mAreaForLayer = areaForLayer;mFeatureToDisplayAreas = featureToDisplayAreas;}
所以mAreaForLayer保存了层级树各个层级的对象因此根据index可以获取到对应的DisplayArea.Tokens,并执行系统窗口WindowToken的挂载。
相关文章:
【Android 13源码分析】WindowContainer窗口层级-3-实例分析
在安卓源码的设计中,将将屏幕分为了37层,不同的窗口将在不同的层级中显示。 对这一块的概念以及相关源码做了详细分析,整理出以下几篇。 【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树 【Android 13源码分析】WindowCon…...
Redis常用操作及springboot整合redis
1. Redis和Mysql的区别 数据模型:二者都是数据库,但是不同的是mysql是进行存储到磁盘当中,而Redis是进行存储到内存中. 数据模型 : mysql的存储的形式是二维表而Redis是通过key-value键值对的形式进行存储数据. 实际的应用的场景: Redis适合于需要快速读写的场景&…...
动态规划day34|背包理论基础(1)(2)、46.携带研究材料(纯粹的01背包)、416. 分割等和子集(01背包的应用)
动态规划day34|背包理论基础(1)(2)、46.携带研究材料、416. 分割等和子集 背包理论基础(1)——二维背包理论基础(2)——一维46.携带研究材料(卡码网 01背包)1. 二维背包2. 一维背包 …...
pytorch优化器
在反向传播计算完所有参数的梯度后,还需要使用优化方法更新网络的权重和参数。例如,随机梯度下降法(SGD)的更新策略如下: weight weight - learning_rate * gradient 手动实现如下: learning_rate 0.01 …...
必备工具,AI生成证件照,再也不用麻烦他人,电子驾驶证等多种证件照一键生成
最近有一个生成证件照的开源项目很火,今天我们来学习一下。之前我生成证件照都是线下去拍照,线上使用也是各种限制,需要付费或看广告,而且效果也不是很理想, 今天要分享的这个 AI 证件照生成工具可以一键可以生成一寸…...
深度解析 MintRich 独特的价格曲线机制玩法
随着 Meme 币赛道的迅速崛起,NFT 市场也迎来了新的变革。作为一个创新的 NFT 发行平台,Mint.Rich 正掀起一场全民参与的 NFT 热潮。其简易的操作界面和独特的价格曲线设计,让任何人都能以极低的门槛发行和交易自己的 NFT,从而参与…...
实时数仓3.0DWD层
实时数仓3.0DWD层 DWD层设计要点:9.1 流量域未经加工的事务事实表9.1.1 主要任务9.1.2 思路9.1.3 图解9.1.4 代码 9.2 流量域独立访客事务事实表9.2.1 主要任务9.2.2 思路分析9.2.3 图解9.2.4 代码 9.3 流量域用户跳出事务事实表9.3.1 主要任务9.3.2 思路分析9.3.3 …...
路径规划 | 基于A*算法的往返式全覆盖路径规划的改进算法(Matlab)
目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 基于A*算法的往返式全覆盖路径规划的改进算法 matlab实现代码 往返式全覆盖路径规划,通过建立二维栅格地图,设置障碍物,以及起始点根据定义往返式路径规划的定义的优先级运动规则从…...
QT 串口上位机读卡显示
目录 一. QT创建工程 二. 软件更换图标 三. QT打包 一. QT创建工程 文件新建,选择创建一个桌面QT。 重命名RFID,并选择工程保存路径 RFID.pro QT core gui serialport #串行串口greaterThan(QT_MAJOR_VERSION, 4): QT widgetsTARGET RFID TE…...
Chrome谷歌浏览器登录账号next无反应
文章目录 问题描述 我们的Chrome浏览器在更新之后,会出现登录谷歌账号的时候,当你输入你的谷歌邮箱之后,点击 n e x t next next,也就是下一步的时候,页面没有反应,也就是没有跳转到输入密码的页面。 分析 根据logs里…...
Android相关线程基础
线程基础 进程与线程 进程:可以被看做是程序的实体, 是系统进行资源分配和调度的基本单位. 线程:是操作系统调度的最小单元, 也叫轻量级进程 使用多线程的优点 可以减少程序的响应时间。如果某个操作很耗时, 能够避免陷入长时间的等待, 从而有着更好的交互性. 线程较之进…...
uniapp 如何自定义导航栏并自适应机型
如今的移动设备有各种不同的屏幕形状,如刘海屏、水滴屏等。这些异形屏会影响页面的布局,尤其是导航栏和底部栏的显示。通过获取安全区域信息,可以确保页面内容不会被异形屏的特殊区域遮挡。 在设计页面顶部导航栏时,可以根据 saf…...
Java高级Day43-类加载
117.类加载 静态和动态加载 反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强 动态加载:运行时加载需要的类,如果运行时不用该类…...
【LeetCode 算法笔记】155. 最小栈
目录 问题描述单个栈实现双栈实现不开辟额外空间 问题描述 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。 void push(int val) 将元素val推入堆栈。 void pop()…...
面试题 05.01. 插入
目录 一:题目: 二:代码: 三:结果: 一:题目: 给定两个整型数字 N 与 M,以及表示比特位置的 i 与 j(i < j,且从 0 位开始计算)。…...
稠密向量检索、稀疏向量检索、BM25检索三者对比
在当今的信息检索领域,随着人工智能和自然语言处理技术的发展,稠密向量检索和稀疏向量检索成为了两种主要的研究方向。稠密向量检索依托于高维空间中的向量表示,能够捕捉文档的深层语义信息,而稀疏向量检索则侧重于关键词的匹配&a…...
UEFI学习笔记(六):EDK II 模块:Libraries,DriversApplication
UEFI学习笔记(六):EDK II Modules:Libraries,Application&Drivers 一、模块(Modules)的概念1、Library模块2、Application模块3、Driver模块4、Application和Driver的区别 二、EDK II 实现U…...
详解 Pandas 的透视表函数
Pandas 的透视表函数主要为 pivot() 和 pivot_table(),主要的功能为对 DataFrame 的行和列进行重新组合来重塑数据。 一、pivot 函数 pivot 函数只能对数据进行重塑,不能进行聚合 1. 数据准备 import pandas as pddf1 pd.DataFrame({department_id: […...
基于python+django+vue的农业管理系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于pythondjangovueMySQL的农…...
动态内存管理之malloc,free,calloc和realloc函数
Hello,各位小伙伴们,小编在这里祝福各位中秋佳节快乐呀,今天让我们来学习一下动态内存管理吧! 引言 像我们之前在开辟一段空间的时候你可能会使用整型变量来申请一块空间,或者使用数组来申请一段连续的空间ÿ…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
