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

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 的获取走了对应的缓存。直接返回对应的对象

@Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }
		// 获取windowManger
        if (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());
        }
        // 赋值给 mWindowManager
        mWindowManager = mWindow.getWindowManager();
        ....
    }

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

	private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // 创建 context
        ContextImpl 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

@Override
    public Object getSystemService(String name) {
        ...
        // 通过 SystemServiceRegistry 获取
        return SystemServiceRegistry.getSystemService(this, name);
    }

	registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public 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();
	@Override
    public 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 和 token
        if (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;
            }
        }
        // 创建对应的 ViewRootImpl
        ViewRootImpl 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() 获取session
            root = 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 things
            try {
                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 @UnsupportedAppUsage
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }


	@UnsupportedAppUsage
    public 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;
        }
    }
	@Override
    public 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();
                // 创建触摸的socket
                InputChannel inputChannel = null;
              	...
                try {
                    // 客户端通知WMS创建一个窗口,并添加到WindowToken
                    res = 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 方法

	@Override
    public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, 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,构造方法创建 WindowStateAnimator
            final 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);
            // 验证当前窗口是否可以添加到WMS
            res = 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,构造方法创建 WindowStateAnimator
            final 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);
            // 验证当前窗口是否可以添加到WMS
            res = 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查询到对应的WindowState
            mWindowMap.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加入到WindowToken
            win.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获取对应的WindowState
            final WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return 0;
            }
            // 获取DisplayContent、DisplayPolicy
            final DisplayContent displayContent = win.getDisplayContent();
            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();

            WindowStateAnimator winAnimator = win.mWinAnimator;
            if (viewVisibility != View.GONE) {
                // 根据客户端请求的窗口大小设置WindowState的requestedWidth, requestedHeight
                // 并设置WindowState.mLayoutNeeded为true
                win.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的可见性,此时为INVISIBLE
            final 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调整为VISIBLE
            win.setViewVisibility(viewVisibility);
  1. 调用 setDisplayLayoutNeeded 将 displayContentmLayoutNeeded置为true
  2. 判断是否可见的话,则调用 createSurfaceControl 开始创建 SurfaceControl
			// 将displayContent中的布局标志为mLayoutNeeded置为true
            win.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开始创建SurfaceControl
                    result = 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赋值给客户端的outSurfaceControl
            surfaceController.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;
        // 首先判断是否存在mSurfaceController
        if (mSurfaceController != null) {
            return mSurfaceController;
        }

        w.setHasSurface(false);

        if (DEBUG_ANIM) {
            Slog.i(TAG, "createSurface " + this + ": mDrawState=DRAW_PENDING");
        }
        // 将 mDrawState 置为 DRAW_PENDING
        resetDrawState();
        ...
        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中获取对应的WindowState
                WindowState 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标志位设置位true
                    requestTraversal();
                } 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() {
        //获取WindowSurfacePlacer
        final 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或者位置变化,则通知客户端重新relayout
        handleResizingWindows();

        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设置为该状态

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


http://www.kler.cn/a/457687.html

相关文章:

  • 2025加密风云:行业变革与未来趋势全景透视
  • 设计心得——流程图和数据流图绘制
  • leetcode题目(3)
  • Vue3 子组件向父组件传递消息(Events)
  • 【51单片机零基础-chapter6:LCD1602调试工具】
  • overleaf写学术论文常用语法+注意事项+审阅修订
  • 嵌入式开发 的循环实现
  • 牛客周赛 Round 66 E题 小苯的蓄水池(hard)
  • 【电路复习--选择题】
  • 【汇编】关于函数调用过程的若干问题
  • 选择排序cYuyan
  • 破解无人机能源瓶颈:优化调度与智能布局的实践
  • mongdb的简介和使用
  • 面向机器学习的Java库与平台
  • cellphoneDB进行CCI以及可视化
  • TCP网络编程(二)—— 服务器端的编写
  • Upload-labs 靶场(学习)
  • 【Linux】Socket编程-UDP构建自己的C++服务器
  • 3.微服务灰度发布落地实践(组件灰度增强)
  • AI 自动化编程的现状与局限
  • delete,drop,truncate的区别
  • ChatGPT与Postman协作完成接口测试(四)
  • sql注入杂谈(一)--union select
  • Mysql(MGR)和ProxySQL搭建部署-Kubernetes版本
  • 【机器学习篇】穿越数字迷雾:机器深度学习的智慧领航
  • 【Hackthebox 中英 Write-Up】Manipulating a CRUD API | 操控 CRUD API:一步步提取 Flag