Android从屏幕刷新到View的绘制(一)之 Window、WindowManager和WindowManagerService之间的关系
0. 相关分享
Android从屏幕刷新到View的绘制(一)之 Window、WindowManager和WindowManagerService之间的关系
Android从屏幕刷新到View的绘制(二)之Choreographer、Vsync与屏幕刷新
1. 相关类
WindowManagerService,下文简称WMS
这是一个系统服务,由SystemServer启动,运行在一个Binder线程,管理着Android系统中所有的Window。
这有什么实际作用呢?除了刷新View,它还可以为其他服务提供Window管理的支持,例如当触摸屏幕时产生输入事件,InputManangerService可以通过WMS来拿到所有Window信息,找到合适的Window进行输入事件的派发,此后,Window就会把这个输入事件传递给顶级View,也就是DecorView,接着就进入到熟悉的事件分发机制了。
Window
表示一个窗口的抽象的概念,这是一个空实现的抽象类,在APP进程中,它有实现类PhoneWindow。
PhoneWindow
是Window的实现类,一个Activity对应着一个PhoneWindow,在PhoneWindow中有一个顶级View——DecorView
DecorView
Activity的根View,继承自FrameLayout
ViewRootImpl
负责DecorView下所有View的调度,例如invalidate()等,Activity下的所有View都会向上找到DecorView,最终找到ViewRootImpl来处理
WindowManagerImpl
它是WindowManager接口的实现类,WindowManager接口又继承自ViewManager,顾名思义它是管理View和Window关系的。ViewManager中规范了三个方法:addView(), updateViewLayout(), removeView()。这三个任务最后也交到了WindowManagerGlobal来处理
WindowManagerGlobal
它是一个单例设计,一个APP进程对应一个WindowManagerGlobal,持有WMS的binder引用,可以通过它来与WMS进行IPC(跨进程通信)交互。
它还拥有许多集合,例如mViews包含了进程下所有View,mRoots包含了进程下所有ViewRootImpl,mDyingViews包含了进程下所有要销毁的View。
2. 上述类间的关系图
Window是View的载体,我们想要对Window进行添加、删除、更新View,就要通过WindowManager,实际管理着是WindowManagerGlobal,它与WMS通过Session进行IPC通信,具体的实现交给了WMS处理。
WMS也会为每个WIndow创建一个WindowState来管理它们,具体的渲染工作交给了SurfaceFinger处理。本文只讨论View、Window、WindowManager与WMS的关系。
3. Window对View的管理
Window是抽象的概念,它的实现类为PhoneWindow,内部维护着一个DecorView,换句话说,WIndow是以View的形式呈现给用户的。Window对View的操作,实际是通过ViewRootImpl实现。使用过程中,我们不会接触并访问到Window,而是通过WindowMananger来进行操作。
接下来说的Window对View的管理其实具体来说是对DecorView的管理,一个Window对应一个DecorView。其中DecorView的创建、删除,就相当于Window的添加、删除,所以也有的地方说,这部分的讨论叫做window的创建、更新、删除。
3.1 Window对View的添加
WindowManager的实现类是WindowManagerImpl
//WindowManagerImpl
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.updateViewLayout(view, params);
}@Override
public void removeView(View view) {mGlobal.removeView(view, false);
}
WindowManagerImpl将对View的添加、删除、更新都交给了WIndowManagerGlobal,mGlobal是一个单例:
//WindowManagerGlobal
public static WindowManagerGlobal getInstance() {synchronized (WindowManagerGlobal.class) {if (sDefaultWindowManager == null) {sDefaultWindowManager = new WindowManagerGlobal();}return sDefaultWindowManager;}
}
首先我们看到WindowManagerGlobal的addView(),具体步骤大概如下:
- 各类数据检查
- 更新mViews、mRoots等集合
- 创建一个ViewRootImpl,将要添加的view交给它
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {//1.数据检查//...ViewRootImpl root;View panelParentView = null;synchronized (mLock) {//2. 更新mViews/mRoots等集合root = new ViewRootImpl(view.getContext(), display);mViews.add(view);mRoots.add(root);mParams.add(wparams);//3.把要添加的view交给ViewRootImpltry {root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {//...}}
}
ViewRootImpl添加到View之后,主要做了几件事:
- 调用requestLayout()异步刷新view
- 通过session与WMS通信,真正完成window的添加
//ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {//值维护一个view(DecorView)if (mView == null) {mView = view;int res;//1. 调用requestLayout绘制ViewrequestLayout();//...try {//通过session与WMS通信,完成window的添加res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel);} catch (RemoteException e) {...}//...
}
首先,requestLayout()最后通过scheduleTraversals()来申请绘制,这部分我们在屏幕绘制的部分再详谈。只需要知道发起了重绘View的请求即可。
//ViewRootImpl
@Override
public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {//检查线程checkThread();//标志位mLayoutRequested = true;//请求绘制scheduleTraversals();}
}
当收到允许绘制的通知的时候,最终会进入到performTraversals(),从而对DecorView自顶向下地分发绘制流程,如measure/layout/draw。
requestLayout()之后,ViewRootImpl通过session通知WMS,去完成window的添加。IWindowSession是一个Binder引用,可以通过它来与WMS通信。WMS为每个应用创建一个单独的session,这个session可以通过WindowManagerGlobal获得。
public ViewRootImpl(Context context, Display display) {mContext = context;//实例化ViewRootImpl的时候,ViewRootImpl就从WIndowManagerGlobal中拿到了可用的sessionmWindowSession = WindowManagerGlobal.getWindowSession();...
}
看一下WindowManagerGlobal中如何提供session的:
public static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {//单例,如果已经有了,就不再创建了if (sWindowSession == null) {try {InputMethodManager imm = InputMethodManager.getInstance();//WMS的引用,这个是全局引用,和AMS一样,在ServiceManager中获取。IWindowManager windowManager = getWindowManagerService();//wms创建一个session交给当前客户端进程的WindowManagerGlobal。sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {@Overridepublic void onAnimatorScaleChanged(float scale) {ValueAnimator.setDurationScale(scale);}},imm.getClient(), imm.getInputContext());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowSession;}
}
首先我们发现WindowManagerGlobal首先会拿到WMS的引用,然后才通过WMS创建一个session,用于后续的通信。我们知道,系统服务由ServiceMananger管理,可以全局获取到WMS的binder引用。但为了让WMS知道和它通信的到底是哪个window,这久需要单独创建一个session,客户端window通过session向WMS发起通信。
我们来看一下WMS是如何处理这个openSession()请求的:
//WindowManagerService
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,IInputContext inputContext) {//实例化了一个SessionSession session = new Session(this, callback, client, inputContext);return session;
}
Session是一个binder实体,它持有WMS的直接引用,客户端window可以通过session来间接地通知WMS做一些操作。
获取到session后,ViewRootImpl的setView()就进入到了最后一步:session.addToDisplay():
//Session
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,Rect outStableInsets, Rect outOutsets,DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {//mServices就是WMS,让WMS来addWindow()return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
}
我们来到WMS的addWIndow()
//WindowManagerService
public int addWindow(Session session,IWindow client,...){//为session建立一个WindowState,可以通过这个WindowState来与客户端通信。final WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], seq, attrs, viewVisibility, session.mUid,session.mCanAddInternalSystemWindow);
}
至此,APP进程就成功将view注册到WMS中,同时,APP进程的WindowManagerGlobal可以通过session对WMS进行binder通信,WMS也可以通过WindowState来与WindowManagerGlobal进行binder通信。
3.2 Window对View的更新
我们再看到mGlobal.updateViewLayout(view,params);
//WindowManagerGlobal
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {//1. 参数检查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;//2.更新layoutParams以及mRoot中对应的ViewRootImplview.setLayoutParams(wparams);synchronized (mLock) {//找到这个view对应的ViewRootImpl是谁int index = findViewLocked(view, true);ViewRootImpl root = mRoots.get(index);mParams.remove(index);mParams.add(index, wparams);//更新ViewRootImpl,这里setLayoutParams()最后也会调用到scheduleTraversals()来请求重绘,细节不在这里讨论root.setLayoutParams(wparams, false);}
}
最后到了ViewRootImpl.setLayoutParams(),最后也会调用到scheduleTraversals()来请求重绘,View的绘制、刷新的细节不在本文中讨论。
3.3 WIndow对View的删除
对View的删除大概分为以下几步:
- 首先让ViewRootImpl用die来删除
- 然后将要删除的view记录到mGlobal的mDyingViews集合中。
- View可能立即删除 doDie(),也可能不是立即删,就放入队列
- 移除各种回调
- 最后通知WMS移除这个window
我们直接看到最后:
//ViewRootImpl
mWindowSession.remove(mWindow);
4. 都有哪些Window会进行这样的创建、更新、删除操作?
Activity、Dialog、Toast等都需要View,而View都需要依附于WIndow
先从简单的Dialog对Window的创建谈起,最后再长篇大论到Activity的Window的创建
4.1 Dialog 的window创建
Dialog的构造方法明显看打了PhoneWindow的实例化、WindowManager的引用(借以与WMS通信)
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {//...//获取windowManagermWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//实例化PhoneWindowfinal Window w = new PhoneWindow(mContext);mWindow = w;//设置回调w.setCallback(this);w.setOnWindowDismissedCallback(this);w.setOnWindowSwipeDismissedCallback(() -> {if (mCancelable) {cancel();}});w.setWindowManager(mWindowManager, null, null);//...
}
接着来到setContentView,就是把视图布局交给DecorView,细节我们在Activity.ssetContent()中讨论,几乎一样的。我们再来看一下show()方法
public void show() {//...mDecor = mWindow.getDecorView();//...WindowManager.LayoutParams l = mWindow.getAttributes();boolean restoreSoftInputMode = false;if ((l.softInputMode& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {l.softInputMode |=WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;restoreSoftInputMode = true;}//使用WindowManager.addViewmWindowManager.addView(mDecor, l);//...
}
mWindowManager.addView()我们知道最后会通过session通知到WMS,需要创建一个window来展示这个dialog。
4.2 长篇大论 Activity 的 Window创建
大概步骤如下:
- APP进程启动时,会通知AMS,application启动好了,并把applicationThread这个binder实体引用交给AMS
- AMS再以此通知app进程的第一个activity启动
- Activity启动之前初始化WindowManagerGlobal
- 最后onResume()执行完毕后,将window的添加通知给WMS
我们直接切入重点,AMS -> ActivityStarter-> ActivityStackSupervisor->realStartActivity()->app进程->handleLaunchActivity(),在这个方法中主要调用了Activity几个回调:
- attach()
- onCreate()
- onStart()
- onResume()
//ActivityThread
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {// Initialize before creating the activityif (!ThreadedRenderer.sRendererDisabled) {GraphicsEnvironment.earlyInitEGL();}//创建单例,创建WMS引用 IWindowManagerWindowManagerGlobal.initialize();//启动activityperformLaunchActivity();//回调onResume()handleResumeActivity();
}
Activity.attach()主要做了几件事:
- 建立了一个与Activity一一对应的PhoneWindow实例mWindow
- 为mWindow设置一个WMS引用
- Activity的mWindowManager也持有WMS引用
public class Activity implements Window.Callback,Window.OnWindowDismissedCallback,WindowControllerCallback,...{private Window mWindow;//一个Activity有一个Window,实现类为PhoneWindowprivate WindowManager mWindowManager;//WMS的引用final void attach(...){//一个activity对应一个PhoneWindowmWindow = new PhoneWindow(Activit.this,window,...);//设置一些回调mWindow.setWindowControllerCallback(this);mWindow.setCallback(this);mWindow.setOnWindowDismissedCallback(this);//...//给Window设置一个WMS的引用mWindow.setWindowManager(//获取WMS的远程引用(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//拿到WMS的引用mWindowManager = mWindow.getWindowManager();}
}
正常情况下,我们可能会在onCreate()中调用setContentView():
public void setContentView(int layoutResId){getWindow().setContentView(layoutResId);initWindowDecorActionBar();
}
getWindow()拿到的是mWindow,实现类是PhoneWindow,看到它的setContentView()
//PhoneWindow
private DecorView mDecor;
private ViewGroup mContentParent;public void setContentView(int layoutResId){if(mContentParent==null){installDecor();//创建decorView,如果没有的话}//...
}private void installDecor(){//1. mDecore初始化if(mDecor==null){mDecore = generateDecor(-1);//new DecoreView(phoneWindow.this)}//2. mContentParent初始化,mDecoreView下的第一个ViewGroupif (mContentParent == null) {mContentParent = generateLayout(mDecor);//根据xml属性配置Activity的默认版型样式}}protected ViewGroup generateLayout(DecorView decor){//...mDecor.startChanging();mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//设置背景//...//设置title之类的mDecor.finishChanging();
}
Activity的DecorView是由PhoneWindow来管理的,包括mContentParent。最后来到handleResumeActivity(),需要注意的是:
- 首先回调onResume()
- 然后使用设置了的DecorView,或者给一个默认的DecorView,通知WMS要addView()
performResumeActivity()->Activity.performResume()->Activity.onResume()
//ActivityThread
final void handleResumeActivity(){//1.回调onResumer = performResumeActivity();//2.如果之前没有通过setContentView()设置mDecor,则给一个默认的Activity a = r.activity;//当前这个activityif(r.window==null){//如果activity在onResume之后还没有mDecor,则会在这里给一个View decor = r.window.getDecorView();//如果没有的话会PhoneWindow.installDecor()decor.setVisibility(View.INVISIBLE);//这个wm是一个WindowManager,它持有WMS的引用//WindowManager也是一个接口,实现类是WindowManagerImplViewManager wm = a.getWindowManager();a.mDecor = decor;if(a.mVisibleFromClient){if(!a.mWindowAdded){a.mWindowAdded= true;wm.addView(decor,l);//l:WM.LayoutParams}} }
}
如果在onResume()结束之前,用户都没调用setContentView(),那么会在这里给到一个mDecor。
wm.addView(decor,l)-> WindowManagerGlobal.addView()
此后的工作我们之前就讨论过了,不过是创建一个ViewRootImpl来维护这个DecorView,请求绘制之后通过session让WMS来添加window。
Activity.onDestroy()时,进入到ActivityThread.handleDestroyActivity(),其中通知了WMS去removeView(),也就是移除这个Activity的Window。最后再通知AMS自己的销毁,ActivityManager.getService().activityDestroyed();
参考文献:
本文基于Android8.0源码分析。结合一些博客的思路进行编排。
https://juejin.cn/post/6863756420380196877
https://blog.csdn.net/hfy8971613/article/details/103241153
相关文章:
Android从屏幕刷新到View的绘制(一)之 Window、WindowManager和WindowManagerService之间的关系
0. 相关分享 Android从屏幕刷新到View的绘制(一)之 Window、WindowManager和WindowManagerService之间的关系 Android从屏幕刷新到View的绘制(二)之Choreographer、Vsync与屏幕刷新 1. 相关类 WindowManagerService,…...
#多源数据融合#:HSI与Lidar
Lidar数据与HSI数据融合应该注意的问题 融合激光雷达(lidar)数据和高光谱数据可以提高地物特征的识别和分类准确性。以下是一些融合这两种数据的注意事项: 数据预处理 由于激光雷达数据和高光谱数据的特点不同,需要对两种数据进…...
android 权限控制与进程隔离
每次介绍说是做系统安全的,面试和领导首先就是说配selinux,实在很无语。虽然权限控制是安全很重要一环。 linux的进程就是系统运行中的程序(process),是正在执行的一个程序或者命令,每一个进程都是一个运行的实体,都有自己的地址空间,并占用一定的系统资源。Linux环境下…...
链表(一):移除链表元素、设计链表等力扣经典链表题目
203.移除链表元素相关题目链接:力扣 - 移除链表元素题目重现给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。思路链表的删除操作如上图所示,我们需要先找到要删除的…...
计算机网络 第4章 作业1
一、选择题 1. 由网络层负责差错控制与流量控制,使分组按序被递交的传输方式是_________(C) A.电路交换 B.报文交换 C.基于虚电路的分组交换 D.基于数据报的分组交换 2. TCP/IP 参考…...
Redis-Java代码使用示例
在我之前的项目中,使用Redis是我们团队自己封装了一个Redis操作类,但是这只是在Spring提供的RedisTemplate上做了一层封装而已,当时使用不是很熟练,都是一边在网上查资料,一边使用;这篇文章会介绍两种使用方…...
acwing3485最大异或和(trie树,贪心)
给定一个非负整数数列 a,初始长度为 N。 请在所有长度不超过 M 的连续子数组中,找出子数组异或和的最大值。 子数组的异或和即为子数组中所有元素按位异或得到的结果。 注意:子数组可以为空。 输入格式 第一行包含两个整数 N,M。 第二行…...
EasyRecovery16免费的电脑的数据恢复工具
常见的数据恢复有两种方式,第一种方式是找别人恢复,按照市场价来说,数据恢复的价格每次在100-500之间,但这种方式容易使自己设备上的隐私资料泄露出去,不安全。 另一种方式则是自己学会数据恢复的方法,有问…...
银行数字化转型导师坚鹏:平安银行数字化转型—橙E网战略研究
平安银行对公业务数字化转型案例—橙E网战略研究课程背景: 很多银行存在以下问题:不清楚银行对公业务数字化转型能否成功?不知道其它银行对公业务数字化转型的实际做法? 课程特色:用实战案例解读平安银行对公业务…...
tun驱动之open
tun驱动对应的设备文件是:/dev/net/tun,其详细信息如下: crw-rw-rw- 1 root root 10, 200 2月 26 08:05 tun 主次设备号的定义如下: #define MISC_MAJOR 10 #define TUN_MINOR 200 由于tun驱动属于misc设备驱动,因此用…...
计算机网络体系结构
计算机网络体系结构是指计算机网络中各个层次和功能组成的结构体系,它定义了计算机网络中各层次之间的协议和接口,以实现不同类型、不同规模、不同性能的计算机之间的互联和通信,同时提供各种网络服务和应用。计算机网络体系结构通常被分为多…...
基础夯实,字节内部总结240道算法LeetCode刷题笔记,直呼太全
1、什么是算法算法(algorithm,[ˈlɡərɪəm],计算程序):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结…...
Three.js使用WebWorker进行八叉树碰撞检测
经过一番探索后还是采用了整个碰撞检测都交给worker来做 原因 如果是小的模型还是不需要这么做的 js线程足够处理构建时的开销 步骤 将需要被检测的物体集合转换成可以背worker接收的结构化数据发送给worker worker将结构化的数据转换成有效的Three元素集合并对其构建八叉树fr…...
【教程】Notion笔记多平台设置中文显示
这个笔记软件界面挺好看,惊艳到了。 目录 网页版 桌面端 Windows版 Mac端 安卓端 网页版 直接安装这个插件即可,Chrome/Edge适用:Notion中文版 桌面端 都要去这个github下载语言包,用于替换文件:https://github.c…...
[牛客Hot101]链表篇
文章目录1.翻转链表2.链表内指定区间翻转3. 链表中的节点每k个一组翻转4. 合并两个排序的链表5. 合并k个排序的链表6. 判断链表是否有环7. 链表中倒数第k个节点8. 删除链表中的倒数第k和节点9. 两个链表的第一个公共节点10.链表的入环节点11. 链表相加(二࿰…...
Vue3 核心模块源码解析(上)
Vue3相比大家也都有所了解,即使暂时没有使用上,但肯定也学习过!Vue3是使用TS进行重写,采用了MonoRepo的管理方式进行管理,本篇文章我们一起来看看 Vue3的使用,与Vue2有什么区别,以及我们该如何优…...
【C进阶】指针的高级话题
文章目录:star:1. 字符指针:star:2. 指针数组2.1 指针数组的定义2.2 指针数组的使用:star:3. 数组指针3.1 数组的地址3.2 数组指针的使用:star:4. 数组参数和指针参数:star:5. 函数指针5.1 函数名和函数的地址5.2 练习:star:6. 函数指针数组6.1 转移表:star:7. 指向函数指针数组…...
无源晶振匹配电容—计算方法
以前有写过一篇文章“晶振”简单介绍了晶振的一些简单参数,今天我们来说下无源晶振的匹配电容计算方法: 如上图,是常见的的无源晶振常见接法,而今天来说到就是这种常见电路的电容计算方法,有两种: A&#…...
【测试】自动化测试03(JUnit)
努力经营当下,直至未来明朗! 文章目录JUnit一) 注解1. Test2. BeforeEach3. BeforeAll4. AfterEach5. AfterAll二) 断言(Assertions类)三)用例的执行顺序四)参数化五)测试…...
《计算机视觉和图像处理简介 - 中英双语版》:神经网络中的激活函数 ReLU vs Sigmoid
文章大纲 Neural Network Module and Training Function创建数据集Define Neural Network, Criterion function, Optimizer and Train the ModelTest Sigmoid and ReluAnalyze Results参考文献与学习路径在本文中,我们使用含有两个隐藏层的神经网络基于MNIST数据集测试Sigmoid…...
(三十七)大白话SQL标准中对事务的4个隔离级别,都是如何规定的呢?
之前我们给大家讲了数据库中多个事务并发时可能产生的几种问题,包括了脏写、脏读、不可重复读、幻读,几种问题 那么针对这些多事务并发的问题,实际上SQL标准中就规定了事务的几种隔离级别,用来解决这些问题。 注意一下ÿ…...
全国计算机等级考试三级网络技术考试大纲(2022年版)
全国计算机等级考试三级网络技术考试大纲(2022年版)基本要求 1. 了解大型网络系统规划、管理方法; 2. 具备中小型网络系统规划、设计的基本能力; 3. 掌握中小…...
服务器部署—若依【vue】如何部署到nginx里面?nginx刷新页面404怎么办?【完美解决建议收藏】
服务器部署项目我们大家都会遇到,但是有些铁子会遇到很多的问题,比如前端部署nginx如何操作? 前端有单纯的静态页面、还有前后端分离的项目;这里博主直接分享最牛最到位的前后端分离项目的前端部署到nginx上面,以若依项…...
算法练习(特辑)算法常用的数据结构、集合和方法总结
一、栈stack 1、初始化:Stack<Integer> st new Stack<Integer>(); 2、常用方法: boolean empty() :测试堆栈是否为空。Object peek( ):查看堆栈顶部的对象,但不从堆栈中移除它。Object pop( )ÿ…...
Apk转Aab(Android-App-Bundle)
这篇文章是参考Apk转Aab(Android-App-Bundle)_YoungBillsohu的博客-CSDN博客 基本照着这个大佬的步骤来就行,但是要注意的是apkTool最好是下新的,否则,会出现说一堆无语的错误,然后导致AAPT2关联资源的时候报错 类似这样的&#…...
大学物理期末大题专题训练总结-热学大题
今天下午去找郑老师权老师等去答疑,老师说大题会考查得比较套路,计算不难。明天就要考试了,再把大题常见题型总结一下,热学这块我做完了蓝本的热学题目,发现了如下三种:有关循环过程曲线的:给出…...
有趣的Hack-A-Sat黑掉卫星挑战赛——卫星平台内存dump
国家太空安全是国家安全在空间领域的表现。随着太空技术在政治、经济、军事、文化等各个领域的应用不断增加,太空已经成为国家赖以生存与发展的命脉之一,凝聚着巨大的国家利益,太空安全的重要性日益凸显[1]。而在信息化时代,太空安…...
OAK相机如何将yoloV8模型转换成blob格式?
编辑:OAK中国 首发:oakchina.cn 喜欢的话,请多多👍⭐️✍ 内容可能会不定期更新,官网内容都是最新的,请查看首发地址链接。 ▌前言 Hello,大家好,这里是OAK中国,我是助手…...
Python解题 - CSDN周赛第32期 - 运输石油(三维背包)
上期周赛因为最后一题出现bug,再加上都是经典的模板题,问哥就懒得写题解了。 本期也是有两道考过的题目,不过最后一题因为考到了背包问题的特殊类型,还是值得拿出来记个笔记。 第一题:传奇霸业 传奇霸业,是…...
JVM - G1垃圾收集器深入剖析
1、G1收集器概述 HotSpot团队一直努力朝着高效收集、减少停顿(STW: Stop The World)的方向努力,也贡献了从串行Serial收集器、到并行收集器Parallerl收集器,再到CMS并发收集器,乃至如今的G1在内的一系列优秀的垃圾收集器。 G…...
安陆网站建设/网站建设品牌公司
【SymPy】(一)SymPy简介 【SymPy】(二)使用SymPy需要避开的坑 【SymPy】(三)基本操作(四)打印 简化 文章目录简化1 simplify2 多项式/有理函数简化2.1 expand2.2 factor2.2.3 colle…...
丰台网站建设多少钱/seo站点
考研真题资料优惠价原价选择湖南科技大学(专业学位)计算机技术教材,也叫湖南科技大学(专业学位)计算机技术考研参考书、指定书目等等,是考验专业课复习过程中最重要的资料。考研是一种针对性很强的考试项目,参考书目由报考院校的研究生院制定…...
做网站php的作用/免费企业网站建设流程
在画出来的图中左上方找: 文件———— 导出设置———— 左方属性里选渲染—— 分辨率设为300/600———— 右侧导出...
wordpress主题框架Genesis/网上怎么推销自己的产品
在微软平台上,想追求新鲜是越来越不容易了! 昨晚在家,想试一把WPF,到微软网站上去下载orcas最新的版本,发现竟有8个img文件需要下载。想这将来卸载时,也要花不少时间吧,于是就准备下那个base im…...
政府网站建设注意事项/济南seo网站排名优化工具
自古以来,人们就认识到了信用的重要性。司马迁在《史记季布栾布列传》中记载:得黄金百,不如得季布一诺。英国哲学家约翰穆勒认为:互相信任可以弥合人类生活中的每一个裂隙[1]。然而,或许正是因为信用的无所不在&#x…...
网站建设 钱/百度应用商店下载
我们平时在linux下切换用户后命令行为什么会变成-bash-3.2$呢,我们来分析一下,这就是跟linux的机制有关联了,因为在linux下每次通过useradd创建新的用户时,都会将所有的配置文件从/etc/skel复制到新用户的主目录下,一般…...