Android之Window与WindowManager

 Window表示一个窗口的概念,在日常开发中直接接触Window的机会并不多,但却会经常用到Window,activitytoastdialogPopupWindow、状态栏等都是Window。在Android中Window是个抽象类,并且仅有一个实现类PhoneWindow

1、Window

 Android中,Window有应用Window、子Window及系统Window三种类型,分别对应不同的层级范围,层级越高,显示越靠前,这里的“靠前”是指层级大的Window会覆盖在层级小的Window上面。

  • 应用Window:对应层级范围是1~99,每个activity就对应一个应用Window,如果在activity中创建了一个应用Window,那么当跳转到另外一个Activity时,该Window会被覆盖。应用Window的高度不受状态栏影响。
  • 子Window:对应层级范围是1000~1999,PopupWindow默认就是一个子Window(可以修改PopupWindow的Window类型),如果在activity中创建了一个子Window,那么当跳转到另外一个Activity时,该Window也会被覆盖。子Window的高度受状态栏影响。
  • 系统Window:对应层级范围是2000~2999,toast、状态栏等都是系统Window,如果创建了一个系统Window,那么只有当该应用被销毁时,该Window才被会关闭(排除主动关闭),所以可以用系统Window实现像360那样的悬浮小球。系统Window需要设置<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />权限,否则会抛异常,在6.0以上需要动态申请。系统Window的高度不受状态栏影响。

 前面说了Window的层级,下面就来看一个示例。

    //代码参考了PopupWindow的源代码。private void startWindow() {//拿到activity中的wm对象,在attach中创建,是一个WindowManagerImpl对象wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);frame = new PopupDecorView(this);frame.setLayoutParams(new ActivityzhoLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));View view = View.inflate(this, R.layout.window_layout, null);Button bt = view.findViewById(R.id.window_layout_button);bt.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {dismiss();}});//重新设置WindowManager.LayoutParams的值WindowManager.LayoutParams p = createPopupLayoutParams(frame.getWindowToken());frame.addView(view);wm.addView(frame, p);}private LayoutParams createPopupLayoutParams(IBinder windowToken) {final WindowManager.LayoutParams p = new WindowManager.LayoutParams();//设置Window gravity。gravity 表示居中,top表示位于顶部p.gravity = Gravity.CENTER|Gravity.TOP;p.flags = computeFlags(p.flags);//设置Window的类型,其实这里我们也可以设置1~99、1000~1999、2000~2999之间的任意数字p.type = LayoutParams.TYPE_APPLICATION;//设置Window Tokenp.token = windowToken;//设置输入法模式p.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;//设置Window动画p.windowAnimations = 0;//设置Window像素格式p.format = PixelFormat.TRANSLUCENT;// Used for debugging.p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));//设置Window宽p.width = LayoutParams.MATCH_PARENT;//设置Window高p.height = LayoutParams.WRAP_CONTENT;return p;}private int computeFlags(int curFlags) {curFlags &= ~(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;return curFlags;}//关闭Windowprivate void dismiss() {wm.removeView(frame);}private class PopupDecorView extends FrameLayout {public PopupDecorView(Context context) {super(context);}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {if (getKeyDispatcherState() == null) {return super.dispatchKeyEvent(event);}if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {final KeyEvent.DispatcherState state = getKeyDispatcherState();if (state != null) {state.startTracking(event, this);}return true;} else if (event.getAction() == KeyEvent.ACTION_UP) {final KeyEvent.DispatcherState state = getKeyDispatcherState();if (state != null && state.isTracking(event) && !event.isCanceled()) {dismiss();return true;}}return super.dispatchKeyEvent(event);} else {return super.dispatchKeyEvent(event);}}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {
//	            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
//	                return true;
//	            }return super.dispatchTouchEvent(ev);}}
复制代码

WindowManager.LayoutParams用于描述Window的参数,关于其详细参数可以参考Android Activity应用窗口的创建过程分析这篇文章。  先来看WindowManager.LayoutParams的参数type,也就是Window类型。下面来看不同Window类型的显示效果

系统Window及应用Window显示效果
子Window显示效果

 粉色部分就是创建的Window,可以看出系统及应用Window不受状态栏影响,而子Window却因为状态栏导致按钮超出Window范围。所以可以认为子Window的高度被被状态栏占去一部分,而其他类型Window则不受此影响,让WIndow居中时,子Window在手机中的位置也会比其他类型Window的位置高一些,这里就不验证了,至于子Window为什么在状态栏的下面,那是因为状态栏的层级比子Window层级要高。

WindowManager.LayoutParamsflags也是一个非常重要的参数,由于类型比较多,这里就主要介绍以下几个类型。

  • FLAG_NOT_TOUCH_MODAL:在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域内的单击事件则自己处理。一般都需要开启此标记
  • FLAG_NOT_FOCUSABLE:在此模式下,Window不能获取焦点,也不能接受各种输入事件,此标记会同时开启FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window。所以如果Window中有EditText等输入控件时,就不应该启用此标记。
  • FLAG_SHOW_WHEN_LOCKED:开启此模式可以让Window显示在锁屏的界面。

WindowManager.LayoutParams中比较常用的参数就上面两个,当然也可以设置Window的宽高、动画、token等等,这里就不一一叙述了。  从上面示例可以看出,Window并不实际存在,它是以一个View的形式展示在屏幕上。

2、WindowManager

WindowManager的主要功能是提供简单的API使得使用者可以方便地将一个View作为一个窗口添加到系统中,它是一个接口,继承自ViewManager接口,ViewManager接口比较简单,只有以下三个方法。

public interface ViewManager
{public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view);
}
复制代码

 从方法名也可以看出对Window的增删改就是针对View的增删改。方法虽然只有三个,但已经完全够用了。WindowManager的具体实现是WindowManagerImpl

public final class WindowManagerImpl implements WindowManager {private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();private final Context mContext;//父Windowprivate final Window mParentWindow;private IBinder mDefaultToken;...//添加View@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}//更新View@Overridepublic void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.updateViewLayout(view, params);}...//异步移除View@Overridepublic void removeView(View view) {mGlobal.removeView(view, false);}//同步移除View@Overridepublic void removeViewImmediate(View view) {mGlobal.removeView(view, true);}...
}
复制代码

 这里采用了代理模式,将所有操作交给WindowManagerGlobal来执行。首先来看Window的添加。

2.1、添加Window

 在前面的例子中可以看到,创建一个Window就是向WindowManagerImpl中添加一个View,而WindowManagerImpl又将操作交给了WindowManagerGlobal来处理,下面就来看看WindowManagerGlobaladdView的实现。

    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {//检查参数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");}//拿到Window的宽高、type等布局参数final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;...ViewRootImpl root;View panelParentView = null;synchronized (mLock) {...//查找View是否已经存在,WindowManager不允许同一个View被添加两次int index = findViewLocked(view, false);if (index >= 0) {//如果View已在被销毁的列表中,那么就先销毁列表中存在的Viewif (mDyingViews.contains(view)) {// Don't wait for MSG_DIE to make it's way through root's queue.mRoots.get(index).doDie();} else {//很常见的一个异常,表示不能重复添加同一Viewthrow new IllegalStateException("View " + view+ " has already been added to the window manager.");}// The previous removeView() had not completed executing. Now it has.}//如果是子Window则需要先找到它的父Viewif (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);}}}//创建一个新的ViewRootImplroot = new ViewRootImpl(view.getContext(), display);//给View设置参数view.setLayoutParams(wparams);//保存ViewmViews.add(view);//保存ViewRootImplmRoots.add(root);//保存参数mParams.add(wparams);//绘制View、添加Windowtry {// 将作为窗口的控件设置给ViewRootImpl。这个动作将导致ViewRootImpl向WMS添加新的窗口、申请Surface以及托管控件在Surface上的重绘动作。这才是真正意义上完成了窗口的添加操作root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.if (index >= 0) {removeViewLocked(index, true);}throw e;}}}
复制代码

 在addView方法中主要做了参数检查、查找子Window的父View、创建ViewRootImpl对象并通过ViewRootImplsetView方法来实现View的绘制及Window添加操作。下面来看ViewRootImplsetView方法的实现。

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {//保存当前ViewmView = view;...//保存参数attrs = mWindowAttributes;...//绘制View。requestLayout();...try {...res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel);} catch (RemoteException e) {....} finally {if (restore) {attrs.restore();}}...//添加失败if (res < WindowManagerGlobal.ADD_OKAY) {mAttachInfo.mRootView = null;//添加失败mAdded = false;mFallbackEventHandler.setView(null);unscheduleTraversals();setAccessibilityFocus(null, null);//返回错误的原因,相比很多错误信息大家都会遇到过switch (res) {//token出错case WindowManagerGlobal.ADD_BAD_APP_TOKEN:case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:throw new WindowManager.BadTokenException("Unable to add window -- token " + attrs.token+ " is not valid; is your activity running?");case WindowManagerGlobal.ADD_NOT_APP_TOKEN:throw new WindowManager.BadTokenException("Unable to add window -- token " + attrs.token+ " is not for an application");case WindowManagerGlobal.ADD_APP_EXITING:throw new WindowManager.BadTokenException("Unable to add window -- app for token " + attrs.token+ " is exiting");//添加Window已存在case WindowManagerGlobal.ADD_DUPLICATE_ADD:throw new WindowManager.BadTokenException("Unable to add window -- window " + mWindow+ " has already been added");case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:// Silently ignore -- we would have just removed it// right away, anyway.return;case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:throw new WindowManager.BadTokenException("Unable to add window "+ mWindow + " -- another window of type "+ mWindowAttributes.type + " already exists");//未申请权限,当创建系统Window时是需要申请权限的case WindowManagerGlobal.ADD_PERMISSION_DENIED:throw new WindowManager.BadTokenException("Unable to add window "+ mWindow + " -- permission denied for window type "+ mWindowAttributes.type);case WindowManagerGlobal.ADD_INVALID_DISPLAY:throw new WindowManager.InvalidDisplayException("Unable to add window "+ mWindow + " -- the specified display can not be found");//window类型未在1~99,1000~1999,2000~2999这个范围内。case WindowManagerGlobal.ADD_INVALID_TYPE:throw new WindowManager.InvalidDisplayException("Unable to add window "+ mWindow + " -- the specified window type "+ mWindowAttributes.type + " is not valid");}throw new RuntimeException("Unable to add window -- unknown error code " + res);}...}}}
复制代码

 该方法真正意义上完成了View的绘制及Window的添加操作,来看requestLayoutmWindowSession.addToDisplay这两个方法。前者主要是申请Surface以及托管控件在Surface上的重绘动作,即View的测量、布局、绘制流程。关于该方法详细内容可以参考Android源码分析之View绘制流程、《深入理解Android 卷III》第六章 深入理解控件(ViewRoot)系统这两篇文章。后者主要向WindowManagerService(WMS)添加新的窗口。  总体来说,WindowManagerGlobal通过父窗口调整了布局参数之后,将新建的ViewRootImpl、控件以及布局参数保存在mRootsmViewsmParams这三个数组中,然后将View交给新建的ViewRootImpl进行处理,从而完成了窗口的添加。  WindowManagerGlobal管理窗口的原理如下图所示。

来自于《深入理解Android 卷III》第六章 深入理解控件(ViewRoot)系统
2.2、更新Window

 相对于添加Window,更新Window就简单很多了,主要是修改布局参数,然后调用ViewRootImpl.setLayoutParams来更新View。

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;//修改view的布局参数view.setLayoutParams(wparams);synchronized (mLock) {int index = findViewLocked(view, true);//查找view对应的ViewRootImplViewRootImpl root = mRoots.get(index);//移除旧的布局参数mParams.remove(index);//添加新的布局参数mParams.add(index, wparams);//更新布局参数root.setLayoutParams(wparams, false);}}复制代码

 代码还是比较简单的,下面就来看ViewRootImplsetLayoutParams方法的实现。

    void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {synchronized (this) {//修改布局参数的操作...//对View进行重新测量、布局、绘制mWindowAttributesChanged = true;scheduleTraversals();}}
复制代码

 该方法也比较简单,主要就是调用scheduleTraversals方法来对View进行重新测量、布局及绘制。scheduleTraversals在这里就不详细讲解了,在View的绘制流程中已经讲解的很清楚了。  总体上来说,Window的更新操作就是对View的重新测量、布局及绘制。

2.2、关闭Window

 关闭Window调用的是WindowManagerGlobalremoveView方法。

    public void removeView(View view, boolean immediate) {if (view == null) {throw new IllegalArgumentException("view must not be null");}synchronized (mLock) {int index = findViewLocked(view, true);View curView = mRoots.get(index).getView();removeViewLocked(index, immediate);if (curView == view) {return;}throw new IllegalStateException("Calling with view " + view+ " but the ViewAncestor is attached to " + curView);}}//移除Viewprivate void removeViewLocked(int index, boolean immediate) {ViewRootImpl root = mRoots.get(index);View view = root.getView();if (view != null) {//拿到输入法管理InputMethodManager imm = InputMethodManager.getInstance();if (imm != null) {//关闭输入法Windowimm.windowDismissed(mViews.get(index).getWindowToken());}}//返回true表示异步删除,false表示同步删除boolean deferred = root.die(immediate);if (view != null) {view.assignParent(null);if (deferred) {//异步删除只是将view添加到mDyingViews这个集合即可。mDyingViews.add(view);}}}//该方法在ViewRootImpl中boolean die(boolean immediate) {//立即移除Viewif (immediate && !mIsInTraversal) {doDie();return false;}...//异步移除View,mHandler.sendEmptyMessage(MSG_DIE);return true;}
复制代码

 最终还是通过ViewRootImpl来实现的Window的关闭,immediatetrue时则代表立即删除当前Window的信息及资源释放,否则异步执行。当异步移除View时,也是调用了ViewRootImpldoDie方法,只不过异步需要排队而已。

    void doDie() {//如果在非UI线程则报错checkThread();...synchronized (this) {if (mRemoved) {return;}mRemoved = true;if (mAdded) {//资源释放dispatchDetachedFromWindow();}if (mAdded && !mFirst) {destroyHardwareRenderer();...}mAdded = false;}//从mRoots、mViews及mParams这三个数组中移除信息WindowManagerGlobal.getInstance().doRemoveView(this);}
复制代码

 在该方法里主是调用dispatchDetachedFromWindow进行资源释放,在dispatchDetachedFromWindow中会释放Surface所占内存、从WMS中移除Window、停止动画、线程等。最后刷新WindowManagerGlobalmRootsmViewsmParams这三个数组的数据。  当调用ViewRootImpldoDie方法后,该ViewRootImpl也就完成了自己的使命了,等待被GC回收。因此可以得出这样一个结论:ViewRootImpl的生命从setView()开始,到die()结束。

3、总结

 到这里,相必对WIndow及WindowManager就有了较深入的了解,主要总结以下几点。

  • Window分为应用Window、子Window及系统Window,不同类型的Window对应着不同的层级范围,层级越高,显示越靠前。
  • 子Window的高度受状态栏的影响。而系统Window及应用Window则无此限制,所以实现一个子Window需要考虑状态栏的高度
  • 一个Window对应着一个ViewRootImpl,也就是说ViewRootImpl与Window同生共死。
  • Window的更新其实对View的重新执行测量、布局及绘制。

【参考资料】 《Android艺术探索》 Android Activity应用窗口的创建过程分析 Android Window 机制探索 《深入理解Android 卷III》第四章 深入理解WindowManagerService 《深入理解Android 卷III》第六章 深入理解控件(ViewRoot)系统 [深入理解Android卷一全文-第八章]深入理解Surface系统

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

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

相关文章

C# WPF This用法详解(经典)

概述this在C#中有多种用法&#xff0c;也比较常见&#xff0c;这节主要针对它常用的四种用法展开讲解.用法1:构造函数串联执行;用法2:通过this区分传参和类中全局的定义;用法3:方法扩展类;用法4:将对象作为参数传递;代码实例using System.Text;namespace Caliburn.Micro.Hello.…

前端node 和vue开发之环境搭建

下载nvm nodejs 的快捷键是配置后自动生成的 nvm 的 setting.txt配置 root: C:\dev\nvmpath: C:\dev\nodejsarch: 32proxy: root指向 nvm.exeroot: C:\dev\nvmpath: C:\dev\nodejs 配置环境变量 变量名 变量值 GIT_HOME C:\dev…

如何从特定位置开始分享YouTube视频

Tech tutorials that start with 3 minutes of “hey guys what’s up” are the worst. Get to the point! Here’s how you can bypass that nonsense when sharing a video with your friends. 最糟糕的是从3分钟的“嗨&#xff0c;大家好起来”开始的技术教程。 讲到重点&a…

解决git提交问题error: The requested URL returned error: 403 Forbidden while accessing

2019独角兽企业重金招聘Python工程师标准>>> git提交代码时&#xff0c;出现这个错误“error: The requested URL returned error: 403 Forbidden while accessing https” 解决方法&#xff1a; 编辑.git目录下的config文件即可。 vim .git/config [core] …

基于.NetCore开发博客项目 StarBlog - (24) 统一接口数据返回格式

1前言开发接口&#xff0c;是给客户端&#xff08;Web前端、App&#xff09;用的&#xff0c;前面说的RESTFul&#xff0c;是接口的规范&#xff0c;有了统一的接口风格&#xff0c;客户端开发人员在访问后端功能的时候能更快找到需要的接口&#xff0c;能写出可维护性更高的代…

如何将C# 7类库升级到C# 8?使用可空引用类型

这篇文章将介绍将C# 7类库升级到C# 8&#xff08;支持可空引用类型&#xff09;的一个案例。本案例中使用的项目Tortuga Anchor由一组MVVM风格的基类、反射代码和各种实用程序函数组成。之所以选择这个项目&#xff0c;是因为它很小&#xff0c;并且同时包含了惯用和不常用的C#…

android 设备名称_如何更改您的Android TV的设备名称

android 设备名称Android TV is Google’s attempt at taking over the living room, and with some units being available for under $99, it’s not unheard of for users to have more than one box. The problem is, when multiple devices identify themselves identical…

AD-查找符合指定条件的用户Get-User

以下服务器为Exchange 2010一、使用 Get-User 命令查找部门为IT的用户Get-User -ResultSize Unlimited | ? { $_.Department -Eq "IT" } | ft Name,Department二、查找注释为多行内容的指定用户如下图&#xff1a;注释Notes信息为多行要使用 match 和 (?*) 来做匹配…

目标检测算法之Fast R-CNN算法详解

在介绍Fast R-CNN之前我们先介绍一下SPP Net 一、SPP Net SPP&#xff1a;Spatial Pyramid Pooling&#xff08;空间金字塔池化&#xff09; 众所周知&#xff0c;CNN一般都含有卷积部分和全连接部分&#xff0c;其中&#xff0c;卷积层不需要固定尺寸的图像&#xff0c;而全连…

RGB-D(深度图像) 图像深度

RGB-D&#xff08;深度图像&#xff09; 深度图像 普通的RGB三通道彩色图像 Depth Map 在3D计算机图形中&#xff0c;Depth Map&#xff08;深度图&#xff09;是包含与视点的场景对象的表面的距离有关的信息的图像或图像通道。其中&#xff0c;Depth Map 类似于灰度图像&…

WPF-21 基于MVVM员工管理-01

接下来我们通过两节课程使用MVVM来开发一个简单的Demo&#xff0c;首先我们创建一个项目名称WPF-22-MVVM-Demo&#xff0c;目录结构如下&#xff1a;我们在Models文件下创建Employee类并让该类实现INotifyPropertyChanged接口&#xff0c;该类中定义编号、姓名和角色三个基本属…

qt 苹果应用程序_什么是苹果的电视应用程序,您应该使用它吗?

qt 苹果应用程序Apple’s TV app, which recently appeared on iOS devices and Apple TV, is meant to help users discover and watch shows across an increasingly expanding lineup of television channels, as well as iTunes movies and shows, in one central app. App…

细说flush、ob_flush的区别

ob_flush/flush在手册中的描述, 都是刷新输出缓冲区, 并且还需要配套使用, 所以会导致很多人迷惑… 其实, 他们俩的操作对象不同, 有些情况下, flush根本不做什么事情.. ob_*系列函数, 是操作PHP本身的输出缓冲区. 所以, ob_flush是刷新PHP自身的缓冲区. 而flush, 严格来讲, 这…

关于jHipster框架在构建中的出现的error修复

jhipster The JDL object and the database type are both mandatory.这个错误应该是在构建基于jHipster的spring-cloud项目中经常遇到的&#xff0c;因为这个在这个过程中会读取.yo-rc文件&#xff0c;之后生成相关的.json文件&#xff0c;再之后生成相关的.java文件&#xff…

protobuf编码

proto2Protocol Buffers 是一种轻便高效的结构化数据存储格式&#xff0c;可以用于结构化数据序列化&#xff0c;适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。 字段规则 required: 字段必须存在opti…

定制.NET 6.0的Middleware中间件

大家好&#xff0c;我是张飞洪&#xff0c;感谢您的阅读&#xff0c;我会不定期和你分享学习心得&#xff0c;希望我的文章能成为你成长路上的垫脚石&#xff0c;让我们一起精进。在本文中&#xff0c;我们将学习中间件&#xff0c;以及如何使用它进一步定制应用程序。我们将快…

Python-循环控制--个人课堂笔记

Python中的两种循环方式&#xff08;目前学到&#xff09;&#xff1a;for循环和while循环 for循环和while循环的区别&#xff1a; for循环一般用于控制循环的次数&#xff0c;while循环则是条件循环。 操作实例-猜数字小游戏&#xff08;3次猜错提示游戏结束&#xff09;&…

删除microsoft_如何从您的Microsoft帐户中删除设备

删除microsoftWhen you sign into Windows 8 or 10 using your Microsoft account (and other Microsoft devices, like an Xbox), those devices become associated with your account. If you want to remove an old device you’ve gotten rid of, you’ll have to pay a vi…

线程的语法 (event,重要)

Python threading模块 2种调用方式 直接调用 12345678910111213141516171819import threadingimport timedef sayhi(num): #定义每个线程要运行的函数print("running on number:%s" %num)time.sleep(3)if __name__ __main__:t1 threading.Thread(targetsayhi,args(…

求最大值和下标值

本题要求编写程序&#xff0c;找出给定的n个数中的最大值及其对应的最小下标&#xff08;下标从0开始&#xff09;。 输入格式: 输入在第一行中给出一个正整数n&#xff08;1<n≤10&#xff09;。第二行输入n个整数&#xff0c;用空格分开。 输出格式: 在一行中输出最大值及…