WindowInsets 分发 WindowInsets 相关类

news/2024/4/20 6:49:21/文章来源:https://blog.csdn.net/lzs781/article/details/127927082

WindowInsets 分发

ViewRootImpl 在 performTraversals 时会调用 dispatchApplyInsets 方法

private void performTraversals() {// cache mView since it is used so much below...final View host = mView;...Rect frame = mWinFrame;if (mFirst) {...dispatchApplyInsets(host);} else {...}...
}

ViewRootImpl::dispatchApplyInsets 定义如下

public void dispatchApplyInsets(View host) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchApplyInsets");mApplyInsetsRequested = false;WindowInsets insets = getWindowInsets(true /* forceConstruct */);if (!shouldDispatchCutout()) {// Window is either not laid out in cutout or the status bar inset takes care of// clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.insets = insets.consumeDisplayCutout();}host.dispatchApplyWindowInsets(insets);//1mAttachInfo.delayNotifyContentCaptureInsetsEvent(insets.getInsets(Type.all()));Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

注释 1 处的 host,其实就是在调用 ViewRootImpl::setView 时传递进来的 View 对象,通常来说,是一个 ViewGroup 对象
ViewGroup::dispatchApplyWindowInsets

@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {insets = super.dispatchApplyWindowInsets(insets);//1if (insets.isConsumed()) {return insets;}if (View.sBrokenInsetsDispatch) {return brokenDispatchApplyWindowInsets(insets);} else {return newDispatchApplyWindowInsets(insets);}
}

注释 1 处先执行 View 的 dispatchApplyWindowInsets 方法
View::dispatchApplyWindowInsets

public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {try {mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {//1return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets);//2} else {return onApplyWindowInsets(insets);//3}} finally {mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;}
}

在注释 1 处判断,是否存在 mOnApplyWindowInsetsListener,该 Listener 可以通过 View:: setOnApplyWindowInsetsListener 来设置,如果设置过 mOnApplyWindowInsetsListener 则走注释 2 的代码,即调用 mOnApplyWindowInsetsListener 的 onApplyWindowInsets 方法;若未设置 mOnApplyWindowInsetsListener,则走注释 3 的代码,即继续执行 View::onApplyWindowInsets 的逻辑。
注意注释 2 和注释 3 是互斥的,如果调用 View::setOnApplyWindowInsetsListener 设置了 listener,则不会走注释 3 的逻辑。
先来看不设置 listener 的情况,即注释 2 处的代码

onApplyWindowInsets 分支

View::onApplyWindowInsets

public WindowInsets onApplyWindowInsets(WindowInsets insets) {if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0&& (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {return onApplyFrameworkOptionalFitSystemWindows(insets);//1}if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {// We weren't called from within a direct call to fitSystemWindows,// call into it as a fallback in case we're in a class that overrides it// and has logic to perform.if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {return insets.consumeSystemWindowInsets();//2}} else {// We were called from within a direct call to fitSystemWindows.if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {return insets.consumeSystemWindowInsets();//3}}return insets;
}

View::onApplyWindowInsets 根据两种情况来讨论

分支 1

注释 1 所在分支是第一种情况,DecorView 满足 (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0,参考下面代码
PhoneWindow::installDecor

private void installDecor() {if (mDecor == null) {mDecor = generateDecor(-1);mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);}} else {mDecor.setWindow(this);}if (mContentParent == null) {mContentParent = generateLayout(mDecor);// Set up decor part of UI to ignore fitsSystemWindows if appropriate.mDecor.makeFrameworkOptionalFitsSystemWindows();...}
}

View::makeFrameworkOptionalFitsSystemWindows

public void makeFrameworkOptionalFitsSystemWindows() {mPrivateFlags4 |= PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS;
}

(mViewFlags & FITS_SYSTEM_WINDOWS) != 0 可以通过View::setFitsSystemWindows 控制
View::setFitsSystemWindows

public void setFitsSystemWindows(boolean fitSystemWindows) {setFlags(fitSystemWindows ? FITS_SYSTEM_WINDOWS : 0, FITS_SYSTEM_WINDOWS);
}

Android S 上 DecorView 默认不具备 FITS_SYSTEM_WINDOWS Flag

分支 2

分支 1 不满足以后,第二个 if 语句用于判断是使用 fitSystemWindows 还是使用 fitSystemWindowsInt
View

protected boolean fitSystemWindows(Rect insets) {if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) {if (insets == null) {// Null insets by definition have already been consumed.// This call cannot apply insets since there are none to apply,// so return false.return false;}// If we're not in the process of dispatching the newer apply insets call,// that means we're not in the compatibility path. Dispatch into the newer// apply insets path and take things from there.try {mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS;return dispatchApplyWindowInsets(new WindowInsets(insets)).isConsumed();} finally {mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS;}} else {// We're being called from the newer apply insets path.// Perform the standard fallback behavior.return fitSystemWindowsInt(insets);}
}private boolean fitSystemWindowsInt(Rect insets) {if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {//1Rect localInsets = sThreadLocal.get();boolean res = computeFitSystemWindows(insets, localInsets);//2applyInsets(localInsets);//3return res;}return false;
}

fitSystemWindows 是老方法,在 SDK 20 以后就已经不推荐使用了,主要研究 fitSystemWindowsInt
在注释 1 处,判断当前 View 是否设置了 android:fitsSystemWindows=”true” 如果设置了,则在注释 2 处计算 Insets,并在注释 3 处应用该 Insets
View::computeFitSystemWindows

protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {WindowInsets innerInsets = computeSystemWindowInsets(new WindowInsets(inoutInsets),outLocalInsets);//1inoutInsets.set(innerInsets.getSystemWindowInsetsAsRect());return innerInsets.isSystemWindowInsetsConsumed();
}

computeFitSystemWindows 调用 computeSystemWindowInsets 计算 insets

public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0|| (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0;//1if (isOptionalFitSystemWindows && mAttachInfo != null) {OnContentApplyWindowInsetsListener listener =mAttachInfo.mContentOnApplyWindowInsetsListener;//2if (listener == null) {// The application wants to take care of fitting system window for// the content.outLocalInsets.setEmpty();return in;}Pair<Insets, WindowInsets> result = listener.onContentApplyWindowInsets(this, in);//3outLocalInsets.set(result.first.toRect());return result.second;} else {outLocalInsets.set(in.getSystemWindowInsetsAsRect());return in.consumeSystemWindowInsets().inset(outLocalInsets);}
}

注释 1 的判断是针对 DecorView 的,DecorView 带有 PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS
注释 2 的 mContentOnApplyWindowInsetsListener,是 PhoneWindow 根据mDecorFitsSystemWindows 调用过判断而给 ViewRootImpl 设置的监听器,用户可以通过调用 PhoneWindow::setDecorFitsSystemWindows 手动设置该值。
PhoneWindow

@Override
public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {mDecorFitsSystemWindows = decorFitsSystemWindows;applyDecorFitsSystemWindows();
}private void applyDecorFitsSystemWindows() {ViewRootImpl impl = getViewRootImplOrNull();if (impl != null) {impl.setOnContentApplyWindowInsetsListener(mDecorFitsSystemWindows? sDefaultContentInsetsApplier: null);}
}

注释 3 在 mContentOnApplyWindowInsetsListener 不为 null 的前提下,根据 ContentOnApplyWindowInsetsListener::onContentApplyWindowInsets 计算 insets
对于 DecorView 来说,这里调用的是下面的方法

/*** @see Window#setDecorFitsSystemWindows*/
private static final OnContentApplyWindowInsetsListener sDefaultContentInsetsApplier =(view, insets) -> {if ((view.getWindowSystemUiVisibility() & SYSTEM_UI_LAYOUT_FLAGS) != 0) {return new Pair<>(Insets.NONE, insets);}Insets insetsToApply = insets.getSystemWindowInsets();return new Pair<>(insetsToApply,insets.inset(insetsToApply).consumeSystemWindowInsets());};

对于 computeSystemWindowInsets 方法来说,它的 outLocalInsets 参数指应用的 insets 矩形,返回值用于标记该 insets 是否被消费过,如果 in.consumeSystemWindowInsets() 则说明该 insets 被消费了
回到 computeFitSystemWindows 方法

protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {WindowInsets innerInsets = computeSystemWindowInsets(new WindowInsets(inoutInsets),outLocalInsets);//1inoutInsets.set(innerInsets.getSystemWindowInsetsAsRect());return innerInsets.isSystemWindowInsetsConsumed();//2
}

在注释 2 处根据当前 insets 是否被消费过,返回 true/false
View::fitSystemWindowsInt

private boolean fitSystemWindowsInt(Rect insets) {if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {//1Rect localInsets = sThreadLocal.get();boolean res = computeFitSystemWindows(insets, localInsets);//2applyInsets(localInsets);//3return res;}return false;
}

回到 View::fitSystemWindowsInt 的注释 3,在这里将应用 Insets
View::applyInsets

private void applyInsets(Rect insets) {mUserPaddingStart = UNDEFINED_PADDING;mUserPaddingEnd = UNDEFINED_PADDING;mUserPaddingLeftInitial = insets.left;mUserPaddingRightInitial = insets.right;internalSetPadding(insets.left, insets.top, insets.right, insets.bottom);
}

在这里将通过 internalSetPadding 将 insets 以 padding 的形式给 View 设置上
后续,回到 View::onApplyWindowInsets

public WindowInsets onApplyWindowInsets(WindowInsets insets) {if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0&& (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {return onApplyFrameworkOptionalFitSystemWindows(insets);//1}if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {// We weren't called from within a direct call to fitSystemWindows,// call into it as a fallback in case we're in a class that overrides it// and has logic to perform.if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {return insets.consumeSystemWindowInsets();//2}} else {// We were called from within a direct call to fitSystemWindows.if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {return insets.consumeSystemWindowInsets();//3}}return insets;
}

若 View::fitSystemWindowsInt 的注释 2 返回 true (即被消耗),则也会让 View::onApplyWindowInsets 的参数也变为被消耗的 Insets (注释 2、注释 3)
回到 ViewGroup::dispatchApplyWindowInsets

@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {insets = super.dispatchApplyWindowInsets(insets);//1if (insets.isConsumed()) {//2return insets;}if (View.sBrokenInsetsDispatch) {//3return brokenDispatchApplyWindowInsets(insets);} else {return newDispatchApplyWindowInsets(insets);//4 }
}

若 insets 在注释 2 处判断为被消耗,则不会进行分发 WindowInsets,否则会继续执行注释 3 的逻辑判断是否中断分发, sBrokenInsetsDispatch 为 true 的条件如下

sBrokenInsetsDispatch = targetSdkVersion < Build.VERSION_CODES.R;

在 Android S 系统中,该值为 false,所以继续看注释 4 处的逻辑
ViewGroup::newDispatchApplyWindowInsets

private WindowInsets newDispatchApplyWindowInsets(WindowInsets insets) {final int count = getChildCount();for (int i = 0; i < count; i++) {getChildAt(i).dispatchApplyWindowInsets(insets);//1}return insets;
}

在注释 1 处,开始调用子 View 的 dispatchApplyWindowInsets 方法,让 WindowInsets 继续向子分发
对于低于 R 版本的系统来说,替代 newDispatchApplyWindowInsets 的是 brokenDispatchApplyWindowInsets
ViewGroup::brokenDispatchApplyWindowInsets

private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) {final int count = getChildCount();for (int i = 0; i < count; i++) {insets = getChildAt(i).dispatchApplyWindowInsets(insets);if (insets.isConsumed()) {break;}}return insets;
}

如果 app targetSdkVersion < 30 ,如果某个节点消费了 Insets,所有没遍历到的节点都不会收到 WindowInsets 的分发;
当 app 运行在 Android 11 以上版本的设备上且 targetSdkVersion >= 30,如果某个节点消费了 Insets,该节点的所有子节点不会收到 WindowInsets 分发。
区别在于一个节点消费了 WindowInsets 分发,其兄弟节点似乎否还能收到分发。

WindowInsets 相关类

InsetsSource

表示为单个类型 insets 的具体信息,例如 Insets 大小、是否可见等信息

InsetsState

InsetsState 持有当前系统 insets 的状态信息,其内部存有一个 InsetsSource 类型的数组 mSources
android.view.InsetsState#mSources

private final InsetsSource[] mSources = new InsetsSource[SIZE];

该数组用于保存当前屏幕中所有类型的 insets
当一个 Window 在客户端被添加、更新的时候,ViewRootImpl 将会调用 relayoutWindow 方法,从 WMS 获取当前 Window 的 InsetsState 信息
android.view.ViewRootImpl#relayoutWindow

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,boolean insetsPending) throws RemoteException {float appScale = mAttachInfo.mApplicationScale;boolean restore = false;if (params != null && mTranslator != null) {restore = true;params.backup();mTranslator.translateWindowLayout(params);}if (params != null) {if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);if (mOrigWindowType != params.type) {// For compatibility with old apps, don't crash here.if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {Slog.w(mTag, "Window type can not be changed after "+ "the window is added; ignoring change of " + mView);params.type = mOrigWindowType;}}}long frameNumber = -1;if (mSurface.isValid()) {frameNumber = mSurface.getNextFrameNumber();}int relayoutResult = mWindowSession.relayout(mWindow, params,(int) (mView.getMeasuredWidth() * appScale + 0.5f),(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,mTempControls, mSurfaceSize);//1mPendingBackDropFrame.set(mTmpFrames.backdropFrame);if (mSurfaceControl.isValid()) {if (!useBLAST()) {mSurface.copyFrom(mSurfaceControl);} else {final Surface blastSurface = getOrCreateBLASTSurface();// If blastSurface == null that means it hasn't changed since the last time we// called. In this situation, avoid calling transferFrom as we would then// inc the generation ID and cause EGL resources to be recreated.if (blastSurface != null) {mSurface.transferFrom(blastSurface);}}if (mAttachInfo.mThreadedRenderer != null) {if (HardwareRenderer.isWebViewOverlaysEnabled()) {addPrepareSurfaceControlForWebviewCallback();addASurfaceTransactionCallback();}mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);}} else {destroySurface();}mPendingAlwaysConsumeSystemBars =(relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;if (restore) {params.restore();}if (mTranslator != null) {mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);}setFrame(mTmpFrames.frame);//2mWillMove = false;mWillResize = false;mInsetsController.onStateChanged(mTempInsets);//3mInsetsController.onControlsChanged(mTempControls);//4return relayoutResult;
}

在注释 1 处,调用 IWindowSession::relayout 方法,IWindowSession 是 ViewRootImpl 与 WindowMangerService 的代理,从注释 1 处得到该当前 Window 的框架大小 mTmpFrames、以及 Insets 情况(mTempInsets、mTempControls),并在注释 2 、注释 3 、注释 4 将这些属性同步给 ViewRootImpl
注释 3、4 中的 mInsetsController 是 InsetsController 类型的实例,它实现了 WindowInsetsController 接口

WindowInsetsController

WindowInsetsController 是客户端用于控制 Window 的管理器,它在客户端的实现类是 InsetsController,每个 Window 会有一个 ViewRootImpl,每个 ViewRootImpl 都将持有一个 InsetsController 实例,客户端程序可以通过 View::getWindowInsetsController 得到该实例,对当前 Window 的 insets 进行控制
android.view.View#getWindowInsetsController

/*** Retrieves the single {@link WindowInsetsController} of the window this view is attached to.** @return The {@link WindowInsetsController} or {@code null} if the view is neither attached to*         a window nor a view tree with a decor.* @see Window#getInsetsController()*/
public @Nullable WindowInsetsController getWindowInsetsController() {if (mAttachInfo != null) {return mAttachInfo.mViewRootImpl.getInsetsController();}ViewParent parent = getParent();if (parent instanceof View) {return ((View) parent).getWindowInsetsController();} else if (parent instanceof ViewRootImpl) {// Between WindowManager.addView() and the first traversal AttachInfo isn't set yet.return ((ViewRootImpl) parent).getInsetsController();}return null;
}

InsetsSourceControl

用于控制单独 InsetsSource 的控制器,被 InsetsController 所持有,同样也是在 ViewRootImpl:: relayoutWindow 时从 WMS 中获得

InsetsSourceConsumer

对单一 InsetsSource 的消费者,其内部持有 InsetsSourceControl,可以控制其leash的可见性和动画。
InsetsSourceControl 持有 InsetsSourceConsumer 的创造器,会在 InsetsController::getSourceConsumer 时创建实例
android.view.InsetsController#getSourceConsumer

public InsetsController(Host host) {this(host, (controller, type) -> {if (type == ITYPE_IME) {return new ImeInsetsSourceConsumer(controller.mState, Transaction::new, controller);} else {return new InsetsSourceConsumer(type, controller.mState, Transaction::new,controller);}}, host.getHandler());//1
}@VisibleForTesting
public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetsType int type) {InsetsSourceConsumer controller = mSourceConsumers.get(type);if (controller != null) {return controller;}controller = mConsumerCreator.apply(this, type);//2mSourceConsumers.put(type, controller);return controller;
}

注释 2 处实际调用的是注释 1 的箭头函数

InsetsStateController

负责在服务端管理全局 insets 状态信息,InsetsStateController 中的 mState 代表当前屏幕的所以类型的 Insets 状态信息
InsetsStateController 还持有不同类型的 InsetsSourceProvider,通常 DisplayContent、InsetsPolicy 等模块会通过 InsetsStateController 访问某一类型的 InsetsSourceProvider 实例

InsetsSourceProvider

负责在服务端根据 WindowState 配置、生成特定类型的 InsetsSource
com.android.server.wm.InsetsSourceProvider#getSource

/*** Updates the window that currently backs this source.** @param win The window that links to this source.* @param frameProvider Based on display frame state and the window, calculates the resulting*                      frame that should be reported to clients.* @param imeFrameProvider Based on display frame state and the window, calculates the resulting*                         frame that should be reported to IME.*/
void setWindow(@Nullable WindowState win,@Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider,@Nullable TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider) {if (mWin != null) {if (mControllable) {mWin.setControllableInsetProvider(null);}// The window may be animating such that we can hand out the leash to the control// target. Revoke the leash by cancelling the animation to correct the state.// TODO: Ideally, we should wait for the animation to finish so previous window can// animate-out as new one animates-in.mWin.cancelAnimation();mWin.mProvidedInsetsSources.remove(mSource.getType());}ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);mWin = win;mFrameProvider = frameProvider;mImeFrameProvider = imeFrameProvider;if (win == null) {setServerVisible(false);mSource.setFrame(new Rect());mSource.setVisibleFrame(null);} else {mWin.mProvidedInsetsSources.put(mSource.getType(), mSource);if (mControllable) {mWin.setControllableInsetProvider(this);if (mPendingControlTarget != null) {updateControlForTarget(mPendingControlTarget, true /* force */);mPendingControlTarget = null;}}}
}
InsetsSource getSource() {return mSource;
}

InsetsPolicy

被 DisplayContent 创建并持有,InsetsPolicy 负责管理不同类型 Inests 的 InsetsControlTarget 实例,同时 InsetsPolicy 还持有 InsetsStateController 类型实例,也提供了一些 Inests 相关行为策略
DisplayContent 对应一块屏幕的内容,一块屏幕对应一个 DisplayContent,一个 DisplayContent 下持有且仅一个 InsetsStateController 实例与 InsetsPolicy 实例,

InsetsControlTarget

InsetsControlTarget 是一个接口,代表可以控制 Insets 状态的对象,WindowState 实现了该接口,由于 WindowState 是每个 Window 在 WMS 中的状态描述,所以每个 Window 都具有控制 Insets 的能力,不同类型的 Insets 可能被不同的 Window 控制,可以通过 dump InsetsStateController 的信息查看当前 Insets 被谁控制
com.android.server.wm.InsetsStateController#dump

void dump(String prefix, PrintWriter pw) {pw.println(prefix + "WindowInsetsStateController");prefix = prefix + "  ";mState.dump(prefix, pw);pw.println(prefix + "Control map:");for (int i = mTypeControlTargetMap.size() - 1; i >= 0; i--) {pw.print(prefix + "  ");pw.println(InsetsState.typeToString(mTypeControlTargetMap.keyAt(i)) + " -> "+ mTypeControlTargetMap.valueAt(i));}pw.println(prefix + "InsetsSourceProviders:");for (int i = mProviders.size() - 1; i >= 0; i--) {mProviders.valueAt(i).dump(pw, prefix + "  ");}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_224171.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kubernetes集群基于kubeadm部署以及常见问题解决

文章目录集群类型主机规划环境初始化检查操作系统版本关闭防火墙设置主机名主机名解析时间同步关闭 SELinux关闭 swap 分区将桥接的IPv4流量传递到iptables的链开启ipvs安装容器运行时&#xff08;Docker&#xff09;卸载Docker旧版本&#xff1a;安装 gcc 相关安装Docker设置阿…

一个Adapter+recycleview实现多种布局,区分布局中

文章目录&#x1f353;&#x1f353;简述&#x1f353;&#x1f353;效果图&#x1f353;&#x1f353;代码&#x1f96d;&#x1f96d;AllAdapter.java&#x1f96d;&#x1f96d; FuritAdapter3.java&#x1f96d;&#x1f96d;MainActivity.java(主函数)&#x1f96d;&#…

【全网热点】打造全网最全爱心代码仓库【火速领取爱心】

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 本文章收录于专栏 【代码实践】 目录&#x1f319;正文&#x1f30f;部分效果在线演示&#x1f30f;部分效果截屏&#x1f338;文末祝…

个人设计web前端大作业 基于html5制作美食菜谱网页设计作业代码

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

Linux的前世今生

14天学习训练营导师课程&#xff1a; 互联网老辛《 符合学习规律的超详细linux实战快速入门》 努力是为了不平庸~ 学习有些时候是枯燥的&#xff0c;但收获的快乐是加倍的&#xff0c;欢迎记录下你的那些努力时刻&#xff08;学习知识点/题解/项目实操/遇到的bug/等等&#xf…

2022年深度学习最新研究成果

一、开源深度学习编译器 二、 开源深度学习加速器 三、AITemplate引擎 四、微型机器学习框架 参考文献&#xff1a;https://arxiv.org/pdf/1510.00149.pdf 五、Contrastive Learning 对比学习综述 六、Diffusion Model 扩散模型综述 Diffusion Models: A Comprehensive Surv…

Java面向对象中阶(七)

面向对象中阶 1、包 2、访问修饰符 3、封装 4、继承 5、方法重写(override) 6、多态 7、Object类的常用方法 8、断点调试 1、包 包的本质&#xff1a; 实际上就是创建不同的文件夹来保存类文件 包的三大作用&#xff1a; 区分相同名字的类当类很多时&#xff0c;可以…

【freeRTOS】操作系统之六-低功耗模式

六&#xff0c;低功耗模式 本章节为大家讲解 FreeRTOS 本身支持的低功耗模式 tickless 实现方法&#xff0c;tickless 低功耗机制是当前小型 RTOS 所采用的通用低功耗方法&#xff0c;比如 embOS&#xff0c;RTX 和 uCOS-III&#xff08;类似方法&#xff09;都有这种机制。ti…

原来背后都是商业利益,看到网易和暴雪的解约之后,原来是要定以后的KPI,坐地起价,但是一个时代已经结束了,都留在了记忆之中

1&#xff0c;大瓜新闻&#xff0c;2023年1月暴雪游戏中国将不会续约&#xff1f;&#xff1f; 2&#xff0c;原因是主要坐地起价&#xff0c;提高分成设置KPI 还好网易有自研游戏&#xff0c;估计早知道会有现在这样的情况。 提前做好了准备。还记得有个公司叫 九城吗&#x…

创建自己的函数库

创建自己的函数库前言一、什么是STM32标准函数库1.定义&#xff1a;2.作用&#xff1a;3.对比&#xff1a;二、构建库函数1.修改寄存器地址封装2.定义访问的结构体指针和引脚3.创建封装函数3.1创建拉低引脚函数3.2创建引脚初始化函数总结前言 回顾一下&#xff0c;前面点亮led…

堆 (带图详解)

文章目录1.堆的基本概念1. 概念2.性质1.必须为完全二叉树2.满足大堆/小堆成立的条件3.存储方式1.逻辑结构2.物理结构4. 孩子与父亲之间下标的关系2.堆的基本实现1.push——插入1.代码2. 情况分析情况1情况23. 向上调整算法1.过程分析2. 临界条件的判断2. pop—— 删除1.代码2. …

redis哨兵系列1

需要配合源码一起康~ 9.1 哨兵基本概念 官网手册yyds&#xff1a;https://redis.io/docs/manual/sentinel/ redis主从模式&#xff0c;如果主挂了&#xff0c;需要人工将从节点提升为主节点&#xff0c;通知应用修改主节点的地址。不是很友好&#xff0c;so Redis 2.8之后开…

C# async / await 用法

目录 一、简介 二、异步等待返回结果 三、异步方法返回类型 四、await foreach 五、Task.Delay 结束 一、简介 await 运算符暂停对其所属的 async 方法的求值&#xff0c;直到其操作数表示的异步操作完成。 异步操作完成后&#xff0c;await 运算符将返回操作的结果&…

使用STM32CubeMX实现按下按键,电平反转

需提前学习&#xff1a;使用STM32CubeMX实现LED闪烁 目录 原理图分析 按键部分原理图分析 LED部分原理图分析 STM32CubeMX配置 关于STM32CubeMXSYS的Debug忘记配置Serial Wire处理办法 GPIO配置 LED的GPIO配置 KEY1配置 关于PA0后面这个WKUP是什么&#xff1f; 那么啥…

利用ogg微服务版将oracle同步到kafka

ogg微服务版可以再界面上配置抽取、复制进程&#xff0c;不必进入到shell中进行配置&#xff0c;并且图形化界面可以看到更多信息。 系统架构 源端安装ogg for oracle 19C , 目标端安装ogg for bigdata 21C kafka 2.2 数据库&#xff1a;19C 所有软件安装在同台服务器上&#…

理解Linux32位机器下虚拟地址到物理地址的转化

文章目录前言一、基本概念介绍二、虚拟地址到物理地址的转化过程总结前言 简要介绍LINUX32位系统下虚拟地址到物理地址的转化过程。 一、基本概念介绍 在32位机器下&#xff0c;IO的基本单位是块&#xff08;块&#xff1a;4kb),在程序编译成可执行程序时也划分好了以4kb为单…

JVM【类加载与GC垃圾回收机制】

JVM【类加载与GC垃圾回收机制】&#x1f34e;一.JVM&#x1f352;1.1JVM简介&#x1f352;1.2JVM执行流程&#x1f34e;二.JVM运行时数据区&#x1f352;2.1 程序计数器(线程私有)&#x1f352;2.2 栈(线程私有)&#x1f352;2.3 堆(线程共享)&#x1f352;2.4 方法区(线程共享…

OWASP API SECURITY TOP 10

目录 1. API 安全风险 2. 细说TOP10 1. Broken Object Level Authorization 2. Broken User Authentication 3 Excessive Data Exposure 4 Lack of Resources & Rate Limiting 5 Broken Function Level Authorization 6 Mass Assignment 7 security misconfigura…

【原创】使用Golang的电商搜索技术架构实现

作者&#xff1a;黑夜路人 时间&#xff1a;2022年11月 一、背景&#xff1a; 现在搜索技术已经是非常主流的应用技术&#xff0c;各种优秀的索引开源软件已经很普遍了&#xff0c;比如 Lucene/Solr/Elasticsearch 等等主流搜索索引开源软件&#xff0c;让我们搭建一个优秀的…

【FLASH存储器系列十】Nand Flash芯片使用指导之一

目录 1.1 芯片简介 1.2 功能框图 1.3 存储结构 1.4 信号定义 1.5 双平面&#xff08;plane&#xff09;操作 1.6 Die间交错操作 1.7 错误管理 今天以MT29F8G08AJADAWP芯片为例&#xff0c;说明nand flash的操作方法。 1.1 芯片简介 这是一款镁光的容量8Gb&#xff0c;总…