当前位置: 首页 > 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…...

Spring Boot 学习笔记

学习代码第一步&#xff1a;如何写 Hello world &#xff1f; 1、新建项目 新建一个 Maven Java 工程&#xff0c;在 pom.xml 文件中添加 Spring Boot Maven 依赖&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>spri…...

快速打造智能应用:从设计到上线的全流程指南

随着人工智能技术的快速发展&#xff0c;如何将大模型技术转化为实际应用成为了各行业关注的焦点。本文将以一个经典的 RAG&#xff08;检索增强生成&#xff09;知识问答系统为例&#xff0c;详细介绍从智能体设计到最终应用部署的全流程。通过结合阿里云的魔笔低代码平台和丰…...

Java-将一个大列表均分成多个小列表,每个小列表包含10个元素

要将一个大列表均分成多个小列表,每个小列表包含10个元素,可以使用多种方法。以下是几种常 见的方法: 方法一:使用 subList 这是你已经提到的方法,通过 subList 来获取子列表。 import java.util.ArrayList; import java.util.List;public class BatchProcessingExamp…...

tcp_rcv_synsent_state_process函数

tcp_rcv_synsent_state_process 是 Linux Kernel 中用于处理 TCP 连接在 SYN-SENT 状态下接收到报文的函数。这个函数在 TCP 三次握手阶段起到了至关重要的作用,处理了在客户端发送 SYN 请求之后收到服务器响应报文的各种情况。 以下是这个函数的解读和剖析: int tcp_rcv_sy…...

关于无线AP信道调整的优化(锐捷)

目录 一、信道优化的基本原则二、2.4G频段信道优化三、5G频段信道优化四、信道优化代码具体示例五、其他优化措施 一、信道优化的基本原则 信道优化旨在减少信道间的干扰&#xff0c;提高网络覆盖范围和信号质量。基本原则包括&#xff1a; 1. 选择合适的信道&#xff1a;根据…...

C#编写的金鱼趣味小应用 - 开源研究系列文章

今天逛网&#xff0c;在GitHub中文网上发现一个源码&#xff0c;里面有这个金鱼小应用&#xff0c;于是就下载下来&#xff0c;根据自己的C#架构模板进行了更改&#xff0c;最终形成了这个例子。 1、 项目目录&#xff1b; 2、 源码介绍&#xff1b; 1) 初始化&#xff1b; 将样…...

计算机网络|数据流向剖析与分层模型详解

文章目录 一、网络中的数据流向二、计算机网络通信模型1.OSI 模型2.TCP/IP 模型3.TCP/IP五层模型3.1 分层架构描述3.2各层地址结构3.3UDP数据包报头结构 三、总结 一、网络中的数据流向 在计算机网络中&#xff0c;数据的流向是指数据从发送端到接收端的传输路径。数据流向涉及…...

某些iphone手机录音获取流stream延迟问题 以及 录音一次第二次不录音问题

一些型号的iphone手机录音获取流stream延迟问题 以及 录音一次第二次不录音问题 延迟问题 navigator.mediaDevices.getUserMedia({ audio: true }) .then((stream) > {console.log(stream) }&#xff09;从开始到获取stream会有将近2s的延迟 导致按下按钮开始录音 会有前…...

gazebo_world 基本围墙。

如何使用&#xff1f; 参考gazebo harmonic的官方教程。 本人使用harmonic的template&#xff0c;在里面进行修改就可以分流畅地使用下去。 以下是world 文件. <?xml version"1.0" ?> <!--Try sending commands:gz topic -t "/model/diff_drive/…...

Ubuntu 上高效实现 Texlive 安装和管理

文章目录 介绍操作步骤1. 下载 Texlive 安装包2. 解压安装包3. 安装基础安装命令通用的 scheme 选项 4. 配置环境变量 使用 tlmgr 管理包总结 介绍 Texlive 是学术和技术文档编写的重要工具, 选择适合的安装方案能帮助您提升效率并减少磁盘空间占用. 本文将为您提供在 Ubuntu …...