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

frameworks 之 WMS添加窗口流程

frameworks 之 触摸事件窗口查找

  • 1.获取WindowManager对象
  • 2.客户端添加view
  • 3. 服务端添加view (NO_SURFACE)
  • 4.重新布局 (DRAW_PENDING)
    • 4.1 创建 SurfaceControl
  • 5.通知绘制 (COMMIT_DRAW_PENDING, READY_TO_SHOW, HAS_DRAWN)
    • 5. 1 布局测量和刷新
  • 6.总结

讲解通过Activity添加窗口流程。主要讲解整个WMS创建到绘制的流程
涉及到的类如下

  • frameworks/base/core/java/android/app/Activity.java
  • frameworks/base/core/java/android/app/Activity.java
  • frameworks/base/core/java/android/app/ActivityThread.java
  • frameworks/base/core/java/android/app/ContextImpl.java
  • frameworks/base/core/java/android/view/WindowManagerImpl.java
  • frameworks/base/core/java/android/view/ViewRootImpl.java
  • frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
  • frameworks/base/services/core/java/com/android/server/wm/Session.java

1.获取WindowManager对象

要想添加子窗口,第一步就是通过Context的 getSystemService 获取对应的 WindowManager 对象。一般通过 Activity 获取,getSystemService 作为抽象方法实现在其子类。

WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);

对应Activity实现方法,判断了对应windowManger 的获取走了对应的缓存。直接返回对应的对象

@Overridepublic Object getSystemService(@ServiceName @NonNull String name) {if (getBaseContext() == null) {throw new IllegalStateException("System services not available to Activities before onCreate()");}// 获取windowMangerif (WINDOW_SERVICE.equals(name)) {return mWindowManager;} else if (SEARCH_SERVICE.equals(name)) {ensureSearchManager();return mSearchManager;}return super.getSystemService(name);}

mWindowManager 的初始化是在 Activity 的 attach 方法。该方法关于window的操作主要做了

  1. 初始化 PhoneWindow
  2. 获取对应的 WindowManager 对象(为 WindowManagerImpl ),并设置到 PhoneWindow 中
  3. 赋值给 mWindowManager 给后面业务逻辑获取使用
	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,Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,IBinder shareableActivityToken) {attachBaseContext(context);mWindow = new PhoneWindow(this, window, activityConfigCallback);...// 初始化 并设置WindowManager到phoneWindow对应的变量mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);if (mParent != null) {mWindow.setContainer(mParent.getWindow());}// 赋值给 mWindowManagermWindowManager = mWindow.getWindowManager();....}

(WindowManager)context.getSystemService(Context.WINDOW_SERVICE) 通过该方法获取对应的实现类。这里用的 Attach 传进来的 context 即为 ContextImpl。ContextImpl 的创建如下

	private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {// 创建 contextContextImpl appContext = createBaseContextForActivity(r);Activity activity = null;...if (activity != null) {...activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback,r.assistToken, r.shareableActivityToken);...}

查看对应 ContextImpl 的 getSystemService 方法,可以看到是通过 SystemServiceRegistry 获取缓存下来的。具体的获取以及注册流程可以查看该文章 《frameworks 之 SystemServiceRegistry》。查看对应的实现的 可以看到最终获取的是 WindowManagerImpl 实现类。所以也就是说 mWindowManager 即为 WindowManagerImpl

@Overridepublic Object getSystemService(String name) {...// 通过 SystemServiceRegistry 获取return SystemServiceRegistry.getSystemService(this, name);}registerService(Context.WINDOW_SERVICE, WindowManager.class,new CachedServiceFetcher<WindowManager>() {@Overridepublic WindowManager createService(ContextImpl ctx) {return new WindowManagerImpl(ctx);}});

2.客户端添加view

获取到 windowManger对象后, 通过调用对应的 addView 方法进行添加。即为调用 WindowManagerImpl 的 addView

// 添加到窗口
windowManager.addView(textView, params);

addView 方法调用 mGlobal 的addVIew方法。 mGlobal 为 WindowManagerGlobal 是全局单例

	// 全局的静态变量private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyTokens(params);mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,mContext.getUserId());}

global addview 方法主要做了

  1. 判断是否有父窗口,如果不为空则认为子窗口添加,通过 adjustLayoutParamsForSubWindow 补全对应的参数 如title 和 token
  2. // 从 mViews 获取该view是否已添加,如果已添加但是处于移除中,则执行doDie方法,否则抛异常
  3. 创建viewRootImpl,并分别将参数放到 mViews,mRoots,mParams 三个数组
  4. 调用 viewROotImpl 的 setView加粗样式方法
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (display == null) {throw new IllegalArgumentException("display must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;// 如果不为空则认为子窗口添加,通过 adjustLayoutParamsForSubWindow 补全对应的参数 如title 和 tokenif (parentWindow != null) {parentWindow.adjustLayoutParamsForSubWindow(wparams);} else {// If there's no parent, then hardware acceleration for this view is// set from the application's hardware acceleration setting.final Context context = view.getContext();if (context != null&& (context.getApplicationInfo().flags& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;}}// 创建对应的 ViewRootImplViewRootImpl root;View panelParentView = null;synchronized (mLock) {// Start watching for system property changes.if (mSystemPropertyUpdater == null) {mSystemPropertyUpdater = new Runnable() {@Override public void run() {synchronized (mLock) {for (int i = mRoots.size() - 1; i >= 0; --i) {mRoots.get(i).loadSystemProperties();}}}};SystemProperties.addChangeCallback(mSystemPropertyUpdater);}// 从 mViews 获取该view是否已添加int index = findViewLocked(view, false);if (index >= 0) {// 判断该view是否在移除if (mDyingViews.contains(view)) {// Don't wait for MSG_DIE to make it's way through root's queue.mRoots.get(index).doDie();} else {throw new IllegalStateException("View " + view+ " has already been added to the window manager.");}// The previous removeView() had not completed executing. Now it has.}// If this is a panel window, then find the window it is being// attached to for future reference.if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {final int count = mViews.size();for (int i = 0; i < count; i++) {if (mRoots.get(i).mWindow.asBinder() == wparams.token) {panelParentView = mViews.get(i);}}}// 创建viewRootImpl,该方法会通过 WindowManagerGlobal.getWindowSession() 获取sessionroot = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);// 添加到对应的数组mViews.add(view);mRoots.add(root);mParams.add(wparams);// do this last because it fires off messages to start doing thingstry {root.setView(view, wparams, panelParentView, userId);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.if (index >= 0) {removeViewLocked(index, true);}throw e;}}}

客户端和wms的连接并不是直接的而是通过 WindowSession 作为中介者进行沟通,windowSession 的获取如下。在 WindowManagerGlobal.java

  1. 通过 getWindowManagerService 获取对应的 windowManger对象。方法里面是通过 ServiceManager.getService(“window”) 从系统获取对应的对象。
  2. 在调用 openSession 获取对应的sWindowSession AIDL对象,负责与后续的沟通
	public static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {if (sWindowSession == null) {try {// Emulate the legacy behavior.  The global instance of InputMethodManager// was instantiated here.// TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsageInputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();IWindowManager windowManager = getWindowManagerService();sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {@Overridepublic void onAnimatorScaleChanged(float scale) {ValueAnimator.setDurationScale(scale);}});} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowSession;}}@UnsupportedAppUsagepublic static IWindowManager getWindowManagerService() {synchronized (WindowManagerGlobal.class) {if (sWindowManagerService == null) {sWindowManagerService = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));try {if (sWindowManagerService != null) {ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale());sUseBLASTAdapter = sWindowManagerService.useBLAST();}} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowManagerService;}}
	@Overridepublic IWindowSession openSession(IWindowSessionCallback callback) {return new Session(this, callback);}

viewRootImpl 的 setView 主要做了几个事情

  1. 调用 requestLayout ,开启同步屏障。执行的方法顺序 scheduleTraversals->doTraversal->performTraversals
    performTraversals中调用了五个关键方法:
    a. relayoutWindow 客户端通知WMS创建Surface,并计算窗口尺寸大小
    b. performMeasure 客户端获取到WMS计算的窗口大小后,进一步测量该窗口下View的宽度和高度
    c. performLayout 客户端确定该窗口下View的尺寸和位置
    d. performDraw 确定好View的尺寸大小位置之后,便对View进行绘制
    createSyncIfNeeded->reportDrawFinished 通知WMS,客户端已经完成绘制。WMS进行系统窗口的状态刷新以及动画处理,并最终将Surface显示出来

  2. 创建 InputChannel 用于接收输入事件

  3. 调用 addToDisplayAsUser 添加到对应的层级树中,这个步骤将 WMS窗口添加之后,还没有创建Surface,此时mDrawState状态为NO_SURFACE

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {synchronized (this) {if (mView == null) {...// 开启同步屏障,进行view的测量布局绘制requestLayout();// 创建触摸的socketInputChannel inputChannel = null;...try {// 客户端通知WMS创建一个窗口,并添加到WindowTokenres = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), userId,mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,mTempControls);if (mTranslator != null) {mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);}}...// 添加失败报错提醒if (res < WindowManagerGlobal.ADD_OKAY) {...}// Set up the input pipeline.// 输入事件处理CharSequence counterSuffix = attrs.getTitle();mSyntheticInputStage = new SyntheticInputStage();...}}}

客户端总结:

  1. WindowManager:是一个接口类,负责窗口的管理(增、删、改)
  2. WindowManagerImpl:WindowManager的实现类,但是他把对于窗口的具体管理操作交给WindowManagerGlobal来处理。
  3. WindowManagerGlobal:是一个单例类,实现了窗口的添加、删除、更新的逻辑。
  4. ViewRootImpl:通过IWindowSession与WMS进行通信。其内部类W实现了WMS与ViewRootImpl的通信。

3. 服务端添加view (NO_SURFACE)

通过 session 的 addToDisplayAsUser 方法,会执行调用 wms的 addWindow 方法

	@Overridepublic 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, window, attrs, viewVisibility, displayId, userId,requestedVisibilities, outInputChannel, outInsetsState, outActiveControls);}

WMS 的 addWindow 具体流程

  1. 调用 checkAddPermission 检查权限
  2. 如果token不为空,获取token对应的 DisplayContent,否则根据 displayId 获取对应的 DisplayContent
  3. mWindowMap 获取 根据 client (IWindow的 AIDL),判断是否已添加,已添加则返回错误
  4. 判断是否子窗口的type 根据 attrs.token(如果是子窗口会在AddView 将父窗口的token(IWindow)放到attrs.token)依然从 mWindowMap 获取如果为空或者 父亲的type也是子窗口则报错。
  5. 判断 parentWindow 是否为null.也就是是否子窗口,如果有父窗口,对获取对应的WindowToken 和 type,否则就直接赋值当前的参数。
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls) {Arrays.fill(outActiveControls, null);int[] appOp = new int[1];final boolean isRoundedCornerOverlay = (attrs.privateFlags& PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;// 检查权限int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,appOp);if (res != ADD_OKAY) {return res;}WindowState parentWindow = null;final int type = attrs.type;synchronized (mGlobalLock) {// 变量在初始化,判断是否wms服务初始化好if (!mDisplayReady) {throw new IllegalStateException("Display has not been initialialized");}// 获取对应的屏幕容器, 如果是单独添加 token为空final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);if (displayContent == null) {ProtoLog.w(WM_ERROR, "Attempted to add window to a display that does "+ "not exist: %d. Aborting.", displayId);return WindowManagerGlobal.ADD_INVALID_DISPLAY;}if (!displayContent.hasAccess(session.mUid)) {ProtoLog.w(WM_ERROR,"Attempted to add window to a display for which the application "+ "does not have access: %d.  Aborting.",displayContent.getDisplayId());return WindowManagerGlobal.ADD_INVALID_DISPLAY;}// clinet是 IWindow AIDL接口,用于wms和应用交互通知,判断是否添加已添加在map会保存if (mWindowMap.containsKey(client.asBinder())) {ProtoLog.w(WM_ERROR, "Window %s is already added", client);return WindowManagerGlobal.ADD_DUPLICATE_ADD;}// 子窗口判断if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {// 传入ssion为null attrs.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;}}...ActivityRecord activity = null;final boolean hasParent = parentWindow != null;// Use existing parent window token for child windows since they go in the same token// as there parent window so we can apply the same policy on them.// 获取对应的window token 如果有父节点就用他的token 当通过直接addView attrs.token为空// 通过里面的mTokenMap 获取// 根据客户端传来的token获取windowToken*///attrs.token去DisplayContent.mTokenMap中去取WindowToken//那么WindowToken是什么时候加入到mTokenMap中的呢//这就要追溯到Activity的启动时,加入到DisplayContent中//在ActivityStarter.startActivityInner中调用addOrReparentStartingActivity通过addChild一步步调用到WindowContainert中。//在调用setParent,最终通过onDisplayChanged将ActivityRecord加入到DisplayContent.mTokenMap中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.final int rootType = hasParent ? parentWindow.mAttrs.type : type;...}
  1. 判断 token 是否为空, 如果为空,则判断是否有父窗口,有用父亲的mToken,没有的话创建对应的 WindowToken。 创建token的时候,构造方法会将该token通过 displlayContent的 addWindowToken 方法添加到层级树中。也会保存到 mTokenMap 中。
protected 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;mOptions = options;mPersistOnEmpty = persistOnEmpty;mOwnerCanManageAppTokens = ownerCanManageAppTokens;mRoundedCornerOverlay = roundedCornerOverlay;mFromClientToken = fromClientToken;// DisplayContent为根节点的WindowContainer层级结构中if (dc != null) {dc.addWindowToken(token, this);}}
  1. 创建对应的 WindowState 容器 (构造方法会创建 WindowStateAnimator ),调用 adjustWindowParamsLw 填充对应层级参数,在调用 validateAddingWindowLw 验证是否允许添加
  2. 根据 INPUT_FEATURE_NO_INPUT_CHANNEL 判断是否要创建对应的触摸监听,不带调用 openInputChannel
			// 创建新的windowState,构造方法创建 WindowStateAnimatorfinal WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);if (win.mDeathRecipient == null) {// Client has apparently died, so there is no reason to// continue.ProtoLog.w(WM_ERROR, "Adding window client %s"+ " that is dead, aborting.", client.asBinder());return WindowManagerGlobal.ADD_APP_EXITING;}if (win.getDisplayContent() == null) {ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");return WindowManagerGlobal.ADD_INVALID_DISPLAY;}final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();displayPolicy.adjustWindowParamsLw(win, win.mAttrs);win.setRequestedVisibilities(requestedVisibilities);attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);// 验证当前窗口是否可以添加到WMSres = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);if (res != ADD_OKAY) {return res;}// 有这个标志 就不创建触摸通道final boolean openInputChannels = (outInputChannel != null&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);if  (openInputChannels) {win.openInputChannel(outInputChannel);}
  1. 调用 attach 创建对应 SurfaceSession,用于SurfaceFlinger通信。
  2. 在将该clinet 和windowState 放到 mWindowMap中。
  3. 在调用 addWindow 方法将 state 挂载到 windowToken中。
			// 创建新的windowState,构造方法创建 WindowStateAnimatorfinal WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);...final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();displayPolicy.adjustWindowParamsLw(win, win.mAttrs);win.setRequestedVisibilities(requestedVisibilities);attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);// 验证当前窗口是否可以添加到WMSres = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);if (res != ADD_OKAY) {return res;}// 有这个标志 就不创建触摸通道final boolean openInputChannels = (outInputChannel != null&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);if  (openInputChannels) {win.openInputChannel(outInputChannel);}...// WindowState的挂载,创建 SurfaceSession// 创建SufaceSession用于SurfaceFlinger通信win.attach();// 将当前的iWindow放到map// mWindowMap保存了每个WindowState和客户端窗口的映射关系,客户端应用请求窗口操作时,// 通过mWindowMap查询到对应的WindowStatemWindowMap.put(client.asBinder(), win);win.initAppOpsState();final boolean suspended = mPmInternal.isPackageSuspended(win.getOwningPackage(),UserHandle.getUserId(win.getOwningUid()));win.setHiddenWhileSuspended(suspended);final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);boolean imMayMove = true;// 将WindowState加入到WindowTokenwin.mToken.addWindow(win);

WMS窗口添加之后,还没有创建Surface,此时mDrawState状态为NO_SURFACE

4.重新布局 (DRAW_PENDING)

界面端viewRootImple 的 performTraversals 通过重新测量,布局后,会调用 session 的 relayout 方法。参互意义如下

参数意义
window是WMS与客户端通信的Binder
attrs窗口的布局属性,根据attrs提供的属性来布局窗口
requestedWidthrequestedWidth
requestedHeightrequestedHeight
viewFlags窗口的可见性。包括VISIBLE(0,view可见),INVISIBLE(4,view不可见,但是仍然占用布局空间)GONE(8,view不可见,不占用布局空间)
outFrames返回给客户端的,保存了重新布局之后的位置与大小
flags定义一些布局行为
mergedConfiguration相关配置信息
outSurfaceControl返回给客户端的surfaceControl
outInsetsState用来保存系统中所有Insets的状态
outActiveControlsInSetsSourceControl数组

relayout 方法又会调用 wms的 relayoutWindow 方法

  1. mWindowMap 获取对应的 WindowState
  2. 如果界面可见的话,根据客户端请求的 宽高通过 setRequestedSize 设置 state的 requestedWidth, requestedHeight,并设置 mLayoutNeeded为true
  3. 通过 setWindowScale 设置缩放比例
  4. 获取原来的可见性,保存到 oldVisibility变量,用于后续的判断。在判断布局的可见性是否变化。将 mRelayoutCalled,mInRelayout 设置true,并且设置布局的可见性。
/ 从mWindowMap根据client获取对应的WindowStatefinal WindowState win = windowForClientLocked(session, client, false);if (win == null) {return 0;}// 获取DisplayContent、DisplayPolicyfinal DisplayContent displayContent = win.getDisplayContent();final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();WindowStateAnimator winAnimator = win.mWinAnimator;if (viewVisibility != View.GONE) {// 根据客户端请求的窗口大小设置WindowState的requestedWidth, requestedHeight// 并设置WindowState.mLayoutNeeded为truewin.setRequestedSize(requestedWidth, requestedHeight);}...// 根据请求的宽带和高度窗口缩放比例win.setWindowScale(win.mRequestedWidth, win.mRequestedHeight);if (win.mAttrs.surfaceInsets.left != 0|| win.mAttrs.surfaceInsets.top != 0|| win.mAttrs.surfaceInsets.right != 0|| win.mAttrs.surfaceInsets.bottom != 0) {winAnimator.setOpaqueLocked(false);}// 获取原来window的可见性,此时为INVISIBLEfinal int oldVisibility = win.mViewVisibility;// If the window is becoming visible, visibleOrAdding may change which may in turn// change the IME target.final boolean becameVisible =(oldVisibility == View.INVISIBLE || oldVisibility == View.GONE)&& viewVisibility == View.VISIBLE;boolean imMayMove = (flagChanges & (FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE)) != 0|| becameVisible;boolean focusMayChange = win.mViewVisibility != viewVisibility|| ((flagChanges & FLAG_NOT_FOCUSABLE) != 0)|| (!win.mRelayoutCalled);boolean wallpaperMayMove = win.mViewVisibility != viewVisibility&& win.hasWallpaper();wallpaperMayMove |= (flagChanges & FLAG_SHOW_WALLPAPER) != 0;if ((flagChanges & FLAG_SECURE) != 0 && winAnimator.mSurfaceController != null) {winAnimator.mSurfaceController.setSecure(win.isSecureLocked());}// 代表现在没有surface但应该很快就有标志位win.mRelayoutCalled = true;win.mInRelayout = true;// 将当前窗口的可见性有原来的INVISIBLE调整为VISIBLEwin.setViewVisibility(viewVisibility);
  1. 调用 setDisplayLayoutNeeded 将 displayContentmLayoutNeeded置为true
  2. 判断是否可见的话,则调用 createSurfaceControl 开始创建 SurfaceControl
			// 将displayContent中的布局标志为mLayoutNeeded置为truewin.setDisplayLayoutNeeded();...// 判断是否允许relayout,此时为true// view可见且(activityRecord不为空,或者布局类型为TYPE_APPLICATION_STARTING,或者窗口已经告诉客户端可以显示)final boolean shouldRelayout = viewVisibility == View.VISIBLE &&(win.mActivityRecord == null || win.mAttrs.type == TYPE_APPLICATION_STARTING|| win.mActivityRecord.isClientVisible());...// surface开始创建if (shouldRelayout) {try {// 进入creatSurfaceControl开始创建SurfaceControlresult = createSurfaceControl(outSurfaceControl, result, win, winAnimator);} catch (Exception e) {displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);ProtoLog.w(WM_ERROR,"Exception thrown when creating surface for client %s (%s). %s",client, win.mAttrs.getTitle(), e);Binder.restoreCallingIdentity(origId);return 0;}}

4.1 创建 SurfaceControl

  1. 会调用 windowState的 WindowStateAnimator 方法来创建 createSurfaceLocked
  2. 将 创建好的 SurfaceControl 放到 传进来参数的 outSurfaceControl,返回给客户端
private int createSurfaceControl(SurfaceControl outSurfaceControl, int result,WindowState win, WindowStateAnimator winAnimator) {if (!win.mHasSurface) {result |= RELAYOUT_RES_SURFACE_CHANGED;}WindowSurfaceController surfaceController;try {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");// WindowStateAnimator用来帮助WindowState管理animator和surface基本操作的// WMS将创建的surfaceContorl的操作交给windowAnimator来处理surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type);} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}if (surfaceController != null) {// 将WMS的SurfaceControl赋值给客户端的outSurfaceControlsurfaceController.getSurfaceControl(outSurfaceControl);ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);} else {// For some reason there isn't a surface.  Clear the// caller's object so they see the same state.ProtoLog.w(WM_ERROR, "Failed to create surface control for %s", win);outSurfaceControl.release();}return result;}

查看 WindowStateAnimator 的 createSurfaceLocked方法

  1. 判断原本是否就有 mSurfaceController ,有的话直接返回
  2. 调用 resetDrawState ,将 mDrawState 置为 DRAW_PENDING
	WindowSurfaceController createSurfaceLocked(int windowType) {final WindowState w = mWin;// 首先判断是否存在mSurfaceControllerif (mSurfaceController != null) {return mSurfaceController;}w.setHasSurface(false);if (DEBUG_ANIM) {Slog.i(TAG, "createSurface " + this + ": mDrawState=DRAW_PENDING");}// 将 mDrawState 置为 DRAW_PENDINGresetDrawState();...try {// This can be removed once we move all Buffer Layers to use BLAST.final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;// 创建WindowSurfaceController// attrs.getTitle().toString()为当前activity的全路径名// format为位图格式// flags为surface创建的标志位(如:HIDDED(0x04,surface创建为隐藏),// SKIP_SCREENSHOT(0x040,截屏时跳过此图层将不会包含在非主显示器上),// SECURE(0X080,禁止复制表面的内容,屏幕截图和次要的非安全显示将呈现黑色内容而不是surface内容)等)// attrs.type为窗口类型mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), width,height, format, flags, this, windowType);mSurfaceController.setColorSpaceAgnostic((attrs.privateFlags& WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);mSurfaceFormat = format;w.setHasSurface(true);}...return mSurfaceController;}
  1. 构造 WindowSurfaceController 对象,在 WindowSurfaceController的构造构造方法会调用 makeSurface,创建对应 SurfaceControl ,makeSurface 通过递归 最终会调用 displayContent 重写的 makeChildSurface 方法。最终调用 build 创建 并保存到 mSurfaceControl 变量
	SurfaceControl.Builder makeSurface() {final WindowContainer p = getParent();return p.makeChildSurface(this);}/*** @param child The WindowContainer this child surface is for, or null if the Surface*              is not assosciated with a WindowContainer (e.g. a surface used for Dimming).*/SurfaceControl.Builder makeChildSurface(WindowContainer child) {final WindowContainer p = getParent();// Give the parent a chance to set properties. In hierarchy v1 we rely// on this to set full-screen dimensions on all our Surface-less Layers.return p.makeChildSurface(child).setParent(mSurfaceControl);}
  1. 调用 WindowSurfacePlacer 的 performSurfacePlacement 方法。该方法用于 窗口尺寸的计算以及Surface的状态变更,确定所有窗口的Surface的如何摆放,如何显示、显示在什么位置、显示区域多大的一个入口方法。该部分处理有关窗口布局循环的逻辑。该部分处理Surface的状态变更,以及调用layoutWindowLw的流程。
  2. 在调用 fillClientWindowFramesAndConfiguration 通知填充数据给客户端。
			// We may be deferring layout passes at the moment, but since the client is interested// in the new out values right now we need to force a layout.// 窗口尺寸的计算以及Surface的状态变更,确定所有窗口的Surface的如何摆放,如何显示、显示在什么位置、显示区域多大的一个入口方法// 1.该部分处理有关窗口布局循环的逻辑。// 2.该部分处理Surface的状态变更,以及调用layoutWindowLw的流程。// 3.计算窗口位置大小。mWindowPlacerLocked.performSurfacePlacement(true /* force */);...// 填充计算好的frame返回给客户端,更新mergedConfiguration对象win.fillClientWindowFramesAndConfiguration(outFrames, mergedConfiguration,false /* useLatestConfig */, shouldRelayout);

5.通知绘制 (COMMIT_DRAW_PENDING, READY_TO_SHOW, HAS_DRAWN)

客户端会调用对应的finishDrawing 通过 session 通知到 wms的 finishDrawingWindow,该方法具体如下

  1. windowForClientLocked 调用 获取 windowState
  2. 调用 windowState的 finishDrawing 方法,该方法会调用 finishDrawingLocked 将 mDrawState 变为 COMMIT_DRAW_PENDING
  3. 调用 windowState的setDisplayLayoutNeeded.将DisplayContentmLayoutNeeded置为true,标志位是判断是否进行窗口大小尺寸计算的条件之一
	void finishDrawingWindow(Session session, IWindow client,@Nullable SurfaceControl.Transaction postDrawTransaction) {final long origId = Binder.clearCallingIdentity();try {synchronized (mGlobalLock) {// 1.根据客户端的Binder在mWindowMap中获取对应的WindowStateWindowState win = windowForClientLocked(session, client, false);ProtoLog.d(WM_DEBUG_ADD_REMOVE, "finishDrawingWindow: %s mDrawState=%s",win, (win != null ? win.mWinAnimator.drawStateToString() : "null"));// 2.finishDrawing执行mDrawState的状态更变if (win != null && win.finishDrawing(postDrawTransaction)) {if (win.hasWallpaper()) {win.getDisplayContent().pendingLayoutChanges |=WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;}// 3.将DisplayContent中mLayoutNeeded置为true// 该标志位是判断是否进行窗口大小尺寸计算的条件之一win.setDisplayLayoutNeeded();// 4.请求进行布局刷新mWindowPlacerLocked.requestTraversal();}}} finally {Binder.restoreCallingIdentity(origId);}}
  1. 调用 requestTraversal ,请求进行布局刷新。方法中又会 post一个任务 mPerformSurfacePlacement,任务里面又会执行 performSurfacePlacement,继续调用 performSurfacePlacement,在调用对应的 performSurfacePlacementLoop 方法。

5. 1 布局测量和刷新

performSurfacePlacementLoop,最重要又会调用 RootWindowContainerperformSurfacePlacement 方法。

private void performSurfacePlacementLoop() {//若当前已经进行布局操作,则无需重复调用直接返回if (mInLayout) {...return;}...if (!mService.mDisplayReady) {// Not yet initialized, nothing to do.return;}// 将该标志位置为true,表示正在处于布局过程中mInLayout = true;...try {// 调用RootWindowContainer的performSurfacePlacement()方法对所有窗口执行布局操作mService.mRoot.performSurfacePlacement();mInLayout = false;if (mService.mRoot.isLayoutNeeded()) {if (++mLayoutRepeatCount < 6) {// 若需要布局,且布局次数小于6次,则需要再次请求布局// 该方法中会将mTraversalScheduled标志位设置位truerequestTraversal();} else {Slog.e(TAG, "Performed 6 layouts in a row. Skipping");mLayoutRepeatCount = 0;}} else {mLayoutRepeatCount = 0;}} catch (RuntimeException e) {mInLayout = false;Slog.wtf(TAG, "Unhandled exception while laying out windows", e);}}

该方法又继续调用 performSurfacePlacementNoTrace ,该方法主要做了

  1. 判断是否有焦点变化,如果有更新焦点
  2. 开启事务,获取GlobalTransactionWrapper对象
  3. 调用 applySurfaceChangesTransaction 方法 执行窗口尺寸计算,surface状态变更等操作
    其中 applySurfaceChangesTransaction 方法,会进行屏幕的水印位置处理之外,还会遍历下面的每个displayContent 的 方法 applySurfaceChangesTransaction
private void applySurfaceChangesTransaction() {mHoldScreenWindow = null;mObscuringWindow = null;// TODO(multi-display): Support these features on secondary screens.// 1.水印、StrictMode警告框以及模拟器显示的布局//获取手机默认DisplayContent的信息final DisplayContent defaultDc = mWmService.getDefaultDisplayContentLocked();final DisplayInfo defaultInfo = defaultDc.getDisplayInfo();final int defaultDw = defaultInfo.logicalWidth;final int defaultDh = defaultInfo.logicalHeight;// 布局水印if (mWmService.mWatermark != null) {mWmService.mWatermark.positionSurface(defaultDw, defaultDh, mDisplayTransaction);}//布局StrictMode警告框if (mWmService.mStrictModeFlash != null) {mWmService.mStrictModeFlash.positionSurface(defaultDw, defaultDh, mDisplayTransaction);}// 布局模拟器显示覆盖if (mWmService.mEmulatorDisplayOverlay != null) {mWmService.mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh,mWmService.getDefaultDisplayRotation(), mDisplayTransaction);}// 2.遍历RootWindowContainer下所有DisplayContent执行其applySurfaceChangesTransaction()final int count = mChildren.size();for (int j = 0; j < count; ++j) {final DisplayContent dc = mChildren.get(j);dc.applySurfaceChangesTransaction();}// Give the display manager a chance to adjust properties like display rotation if it needs// to.mWmService.mDisplayManagerInternal.performTraversal(mDisplayTransaction);SurfaceControl.mergeToGlobalTransaction(mDisplayTransaction);}

applySurfaceChangesTransaction方法 会调用 performLayout 方法进行布局,在调用 forAllWindows触发传进去的回调 mApplySurfaceChangesTransaction,里面又会调用 winAnimator.commitFinishDrawingLocked 将 状态更变为READY_TO_SHOW,在通过 prepareSurfaces 处理各个surface的位置、大小以及是否要在屏幕上显示等。

void applySurfaceChangesTransaction() {//获取WindowSurfacePlacerfinal WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;...do {...// FIRST LOOP: Perform a layout, if needed.// 1.执行布局,该方法最终会调用performLayoutNoTrace,计算窗口的布局参数if (repeats < LAYOUT_REPEAT_THRESHOLD) {performLayout(repeats == 1, false /* updateInputWindows */);} else {Slog.w(TAG, "Layout repeat skipped after too many iterations");}} while (pendingLayoutChanges != 0);try {// 2.遍历所有窗口,主要是改变surface的状态。mDrawState变更为HAS_DRAW流程forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}...// 3.处理各个surface的位置、大小以及是否要在屏幕上显示等。后面finishDrawing()流程中再跟踪prepareSurfaces();...}
  1. 调用 checkAppTransitionReady 方法,将状态改为 HAS_DRAWN,触发App触发动画。该方法里面会调用 handleAppTransitionReady 方法,在调用 handleOpeningApps 方法。在调用 showAllWindowsLocked,在调用 performShowLocked,将状态改为 HAS_DRAWN。
  2. 遍历所有DisplayContent,如果壁纸有变化,更新壁纸
  3. 在一次此处理焦点变化
  4. 如果过程中size或者位置变化,则通知客户端重新relayout
  5. 销毁不可见的窗口
	void performSurfacePlacementNoTrace() {...// 1.如果有焦点变化,更新焦点if (mWmService.mFocusMayChange) {mWmService.mFocusMayChange = false;mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);}...Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");// 开启事务,获取GlobalTransactionWrapper对象mWmService.openSurfaceTransaction();try {// 2.执行窗口尺寸计算,surface状态变更等操作applySurfaceChangesTransaction();} catch (RuntimeException e) {Slog.wtf(TAG, "Unhandled exception in Window Manager", e);} finally {// 关闭事务mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);if (SHOW_LIGHT_TRANSACTIONS) {Slog.i(TAG,"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");}}...// 3.将Surface状态变更为HAS_DRAWN,触发App触发动画。checkAppTransitionReady(surfacePlacer);// Defer starting the recents animation until the wallpaper has drawn// 4.遍历所有DisplayContent,如果壁纸有变化,更新壁纸final RecentsAnimationController recentsAnimationController =mWmService.getRecentsAnimationController();if (recentsAnimationController != null) {recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController);}for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {final DisplayContent displayContent = mChildren.get(displayNdx);// 判断DisplayContent的壁纸是否需要改变if (displayContent.mWallpaperMayChange) {ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper may change!  Adjusting");displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;if (DEBUG_LAYOUT_REPEATS) {surfacePlacer.debugLayoutRepeats("WallpaperMayChange",displayContent.pendingLayoutChanges);}}}// 5.在此处理焦点变化if (mWmService.mFocusMayChange) {mWmService.mFocusMayChange = false;mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,false /*updateInputWindows*/);}if (isLayoutNeeded()) {defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;if (DEBUG_LAYOUT_REPEATS) {surfacePlacer.debugLayoutRepeats("mLayoutNeeded",defaultDisplay.pendingLayoutChanges);}}// 6.如果过程中size或者位置变化,则通知客户端重新relayouthandleResizingWindows();if (mWmService.mDisplayFrozen) {ProtoLog.v(WM_DEBUG_ORIENTATION,"With display frozen, orientationChangeComplete=%b",mOrientationChangeComplete);}if (mOrientationChangeComplete) {if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);}mWmService.stopFreezingDisplayLocked();}// Destroy the surface of any windows that are no longer visible.// 7.销毁不可见的窗口*i = mWmService.mDestroySurface.size();if (i > 0) {do {i--;WindowState win = mWmService.mDestroySurface.get(i);win.mDestroying = false;final DisplayContent displayContent = win.getDisplayContent();if (displayContent.mInputMethodWindow == win) {displayContent.setInputMethodWindowLocked(null);}if (displayContent.mWallpaperController.isWallpaperTarget(win)) {displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;}win.destroySurfaceUnchecked();} while (i > 0);mWmService.mDestroySurface.clear();}...}

注:WindowStateAnimator的commitFinishDrawingLocked()方法中,如果是应用通过WindowManager中的addView的方式创建窗口,则不会有ActivityRecord,或者该窗口类型为启动窗口,则直接调用result = mWin.performShowLocked();,即WindowState的performShowLocked()方法改变窗口状态为HAS_DRAW,否则会从RootWindowContainercheckAppTransitionReady方法逐步调用到performShowLocked()
在这里插入图片描述

6.总结

WMS为了管理窗口的显示进度,在WindowStateAnimator中定义了mDrawState来描述Surface所处的状态。主要有如下五种状态:

状态时机
NO_SURFACEWMS添加窗口,即调用addWindow之后,还没有创建Surface,mDrawState处于该状态
DRAW_PENDINGapp调用relayoutWindow创建Surface后,但是Surface还没有进行绘制,mDrawState处于该状态
COMMIT_DRAW_PENDINGapp完成Surface的绘制,调用finishDrawing,将mDrawState设置为该状态
READY_TO_SHOW在performSurfacePlacement过程中会将所有处于COMMIT_DRAW_PENDING状态的mDrawState变更为READY_TO_SHOW
HAS_DRAW若准备显示窗口,WMS执行performShowLocked,将mDrawState设置为该状态

在这里插入图片描述
文章部分图参考该文 原文链接在此点击。

相关文章:

frameworks 之 WMS添加窗口流程

frameworks 之 触摸事件窗口查找 1.获取WindowManager对象2.客户端添加view3. 服务端添加view (NO_SURFACE)4.重新布局 (DRAW_PENDING)4.1 创建 SurfaceControl 5.通知绘制 (COMMIT_DRAW_PENDING&#xff0c; READY_TO_SHOW&#xff0c; HAS_DRAWN)5. 1 布局测量和刷新 6.总结 …...

搜索方法归类全解析

搜索方法归类全解析 搜索方法是人工智能和计算机科学中用于解决问题、优化路径或发现数据模式的关键技术。根据不同的标准&#xff0c;搜索方法可以被分为多种类别。本文将详细介绍这些分类标准&#xff0c;并探讨每一类的特点及其代表算法&#xff0c;同时补充更多关于搜索的相…...

第1关:简易考试系统之用户注册

任务描述 本关任务&#xff1a;实现简易考试系统中新用户注册的功能。 编程要求 仔细阅读右侧编辑区内给出的代码框架及注释&#xff0c;在 Begin-End 中实现简易考试系统中新用户注册的功能&#xff0c;具体要求如下&#xff1a; User.java 提供了用户的基本信息&#xff0c…...

VMware的三种网络模式——在NAT模式下开放接口为局域网内其他主机提供服务

众所周知 VMware 有三种常用的网络通讯模式&#xff0c;分别是&#xff1a;Bridged&#xff08;桥接模式&#xff09;、NAT&#xff08;网络地址转换模式&#xff09;、Host-Only&#xff08;仅主机模式&#xff09;&#xff0c;它们各有不同的用法。 Bridged 桥接模式是与主机…...

智慧地下采矿:可视化引领未来矿业管理

图扑智慧地下采矿可视化平台通过整合多源数据&#xff0c;提供实时 3D 矿井地图及分析&#xff0c;提升了矿产开采的安全性与效率&#xff0c;为矿业管理提供数据驱动的智能决策支持&#xff0c;推动行业数字化转型。...

流量主微信小程序工具类去水印

工具类微信小程序流量主带后台管理&#xff0c;可开通广告&#xff0c;带自有后台管理&#xff0c;不借助第三方接口 介绍 支持抖音&#xff0c;小红书&#xff0c;哔哩哔哩视频水印去除&#xff0c;功能实现不借助第三方平台。可实现微信小程序流量主广告变现功能&#xff0c…...

代码随想录算法【Day5】

DAY5 1.熟悉哈希表的数据结构&#xff1a;数组、map和set&#xff0c;使用方法、使用场景 2.哈希表应用场景&#xff1a;解决给你一个元素&#xff0c;判断它在集合里是否出现过。 242.有效的字母异位词 本题用数组解决的。 class Solution { public:bool isAnagram(strin…...

Leetcode 3403. Find the Lexicographically Largest String From the Box I

Leetcode 3403. Find the Lexicographically Largest String From the Box I 1. 解题思路2. 代码实现 题目链接&#xff1a;3403. Find the Lexicographically Largest String From the Box I 1. 解题思路 这一题我一开始的思路是想用动态规划&#xff0c;结果发现想复杂了&…...

【游戏设计原理】36 - 环境叙事

一、 分析并总结 核心要点 环境叙事的本质&#xff1a;将游戏的设定视为叙事的一部分&#xff0c;利用环境元素&#xff08;如物品、对话、视觉效果等&#xff09;传递故事和信息。世界设定的重要性&#xff1a;一个强大的世界设定可以像角色一样&#xff0c;驱动叙事并增强玩…...

Python 中的 lambda 函数和嵌套函数

Python 中的 lambda 函数和嵌套函数 Python 中的 lambda 函数和嵌套函数Python 中的 lambda 函数嵌套函数&#xff08;内部函数&#xff09;封装辅助函数闭包和工厂函数 Python 中的 lambda 函数和嵌套函数 Python 中的 lambda 函数 Lambda 函数是基于单行表达式的匿名函数。…...

语言模型评价指标

1. BLEU&#xff08;Bilingual Evaluation Understudy&#xff09; 目标&#xff1a;衡量生成文本和参考文本之间的词汇相似性。 计算步骤&#xff1a; N-gram 匹配&#xff1a; 将生成文本和参考文本分解成 1-gram、2-gram、…、N-gram&#xff08;通常取到 4-gram&#xff…...

工程师 - MSYS2介绍

https://www.msys2.org/ MSYS2 是一系列工具和库&#xff0c;为您提供了一个易于使用的环境&#xff0c;用于构建、安装和运行本地 Windows 软件。 MSYS2 is a collection of tools and libraries providing you with an easy-to-use environment for building, installing an…...

算法基础三:插入排序

定义 插入排序&#xff08;英语&#xff1a;Insertion Sort&#xff09;是一种简单直观的排序算法。它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上&#xff0c;通常采用…...

小米汽车加速出海,官网建设引领海外市场布局!

面对国内市场的饱和态势&#xff0c;中国企业出海步伐纷纷加速&#xff0c;小米也是其中的一员。Canalys数据显示&#xff0c;2024年第三季度&#xff0c;小米以13.8%的市场份额占比&#xff0c;实现了连续17个季度位居全球前三的成绩。 据“36 氪汽车”报道&#xff0c;小米汽…...

Python Polars快速入门指南:LazyFrames

前文已经介绍了Polars的Dataframe, Contexts 和 Expressions&#xff0c;本文继续介绍Polars的惰性API。惰性API是该库最强大的功能之一&#xff0c;使用惰性API可以设定一系列操作&#xff0c;而无需立即运行它们。相反&#xff0c;这些操作被保存为计算图&#xff0c;只在必要…...

什么是网络安全(Cybersecurity)?

不同组织机构对网络安全&#xff08;Cybersecurity或Cyber Security&#xff09;的定义不尽相同。从目标上来说&#xff0c;网络安全主要用于保护网络、计算机、移动设备、应用程序及数据等资产免受网络攻击&#xff0c;避免造成数据泄露、业务中断等安全问题。 网络钓鱼、勒索…...

VBA批量插入图片到PPT,一页一图

Sub InsertPicturesIntoSlides()Dim pptApp As ObjectDim pptPres As ObjectDim pptSlide As ObjectDim strFolderPath As StringDim strFileName As StringDim i As Integer 设置图片文件夹路径strFolderPath "C:\您的图片文件夹路径\" 请替换为您的图片文件夹路径…...

Pandas-DataFrame入门

文章目录 一. Pandas DataFrame简介二. 加载数据集1. 目的2. 步骤① 导包② 加载csv③ 查看数据类型及属性④ Pandas与Python常用数据类型对照 三. 查看部分数据1. 根据列名加载部分列数据① 加载一列数据&#xff0c;通过df[列名]方式获取② 加载多列数据&#xff0c;通过df[[…...

爬虫 - 爬取王者荣耀所有皮肤图片

结果展示 安装 pip install requests logger代码 import json import os import re from concurrent.futures import ThreadPoolExecutorimport requests from loguru import loggerdef parse_url(url, bFalse):try:headers {"User-Agent": "Mozilla/5.0 (Wi…...

【畅购商城】购物车模块之查看购物车

目录 分析 接口 后端实现 前端实现&#xff1a;显示页面 前端实现&#xff1a;显示购物车信息 分析 用户如果没有登录&#xff0c;购物车存放在浏览器端的localStorage处&#xff0c;且以数组的方式进行存储。用户如果登录了&#xff0c;购物车存放在redis中&#xff0c…...

OpenLayers 可视化之热力图

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 热力图&#xff08;Heatmap&#xff09;又叫热点图&#xff0c;是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...

基于开源AI智能名片链动2 + 1模式S2B2C商城小程序的沉浸式体验营销研究

摘要&#xff1a;在消费市场竞争日益激烈的当下&#xff0c;传统体验营销方式存在诸多局限。本文聚焦开源AI智能名片链动2 1模式S2B2C商城小程序&#xff0c;探讨其在沉浸式体验营销中的应用。通过对比传统品鉴、工厂参观等初级体验方式&#xff0c;分析沉浸式体验的优势与价值…...

goreplay

1.github地址 https://github.com/buger/goreplay 2.简单介绍 GoReplay 是一个开源的网络监控工具&#xff0c;可以记录用户的实时流量并将其用于镜像、负载测试、监控和详细分析。 3.出现背景 随着应用程序的增长&#xff0c;测试它所需的工作量也会呈指数级增长。GoRepl…...

嵌入式面试常问问题

以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...

GB/T 43887-2024 核级柔性石墨板材检测

核级柔性石墨板材是指以可膨胀石墨为原料、未经改性和增强、用于核工业的核级柔性石墨板材。 GB/T 43887-2024核级柔性石墨板材检测检测指标&#xff1a; 测试项目 测试标准 外观 GB/T 43887 尺寸偏差 GB/T 43887 化学成分 GB/T 43887 密度偏差 GB/T 43887 拉伸强度…...