理解安卓的视图体系结构

news2024/11/29 16:40:07

原文链接 理解安卓的视图体系结构

当我们想要写一个页面的时候,通过一个Activity,然后调用其setContentView方法,把一个布局文件当作一个参数传递过去,然后一个页面就好了,但是除此之外,我们还需要与一些组件打交道,比如像Window,WindowManager,那么这些东西到底 与我们的页面布局有什么关系,今天就来学习一下,以便对整体窗口有个更清楚的认知。

布局是一颗View tree

先从一个最简单的例子出发,平时我们写一个页面,都从一个布局文件出发。这其实是在构建一个View tree,为啥一定是tree呢,因为我们的布局文件,无论有多么的复杂,都是从一个根(通常是一个ViewGroup对象)开始的,父布局里面再写子布局,比如这样的:

<LinearLayout id="app_root">
  <TextView id="label"/>
  <Button id="submit"/>
</LinearLayout>

这会形成一个树状结构:

| app_root

   |- label

   |- submit

作为一个开发者,写布局是我们再熟悉不过的了,主要就是用所熟悉的各种Layout和View一起来构建想要的页面。

所写的布局,最终会生成一颗View tree,是一个树状的数据结构,每一个节点都是一个View对象(ViewGroup和View)。因此,布局优化的一个是感觉重要的点就是要先减少View tree的深度(也即平时所说的减少布局的嵌套),再想办法减少广度(减少个数)。

那么,我们写的布局的父布局又是哪里呢?这就又涉及两个东西,一个叫做decorView和contentView的东西。

DecorView与ContentView

我们平常所见的屏幕窗口的根布局是一个叫做DecorView的东西,它是我们通常意义上整个屏幕的根节点,它包含了上面的Status bar和下方的Navigation bar,以及属于应用程序的中间部分。它的源码路径是frameworks/base/core/java/com/android/internal/policy/DecorView.java。它是一个真实的view,它是FrameLayout的子类。

它下面有一个id为android.R.id.content的FrameLayout,我们平时在Activity中调用setContent时所传过去的布局文件所生成的View tree都是添加在这个FrameLayout下面,所以,通常对于我们一个Activity来说,这个FrameLayout是直接意义上的根节点,我们所写的布局都是添加它下面的。

ContentView所引申出来的奇技淫巧

布局优化技巧

首先,一个是布局的优化技巧,可以减少View tree的层级:假如你写的布局中根节点也是一个FrameLayout,那么可以直接用merge节点,把子view全部都直接加挂到前面提到的系统创建的Activity的根布局上面。

<merge>
  <Text />
  <Button />
</merge>

这可以把View tree减少一个层级(深度减1)。

页面内即插即用的弹窗

每个Activity都被回挂在一个id是android.R.id.content的FrameLayout下面,利用这一点,可以做一些即插即用的弹窗,即插即用的意思是,不用写在布局里面,而且显示的时间是不固定的,可能很多时候都不显示,在某个特定的逻辑或者时间才显示。就好比某些电商特定节日的弹窗一样,这种东西,一年也显示不了几回,如果直接添加在布局里面(哪怕你用ViewStub),不够优雅,毕竟不是常规逻辑下会出现的页面,这时可以利用content来做一些即时弹窗:

FrameLayout container = activity.findViewById(android.R.id.content);
View pop = <create or inflate your own view>;
container.addView(pop);

只要你能获得到Activity的实例(这个并不难),那么就可以非常优雅的添加弹窗,逻辑代码和布局文件都会相当独立,甚至可以用插件形式来异步加载。再进一步,如果 添加一个WebView,那么就可以做得更加的前端化,实时化和定制化,好多电商的弹窗就是这么干的。

Window与WindowManager

作为应用开发者,我们看一个View tree其实就是一坨布局,这是站在一个非常小的角度去看的,但如果站在整体系统架构角度来看的话,就会发现应用程序所在的view tree仅是系统可视化窗口架构中的末端,View只是用来构建视图的基本砖块而已。对于整体View tree是如何渲染的,何时渲染,这就涉及到了整体系统架构层面的重量级组件了。

对于现化代的视图窗口架构(Modern GUI),都有一个window server,作来管理视图窗口的核心组件,比如X11,Android当中也不例外。在Android里面,WindowManager就是专门用于管理视图窗口的,它是系统级别的server叫window manager server是一个系统级别的常驻进程,由init.rc启动。而Window则是一个基本的窗口的逻辑上的抽象。关于Window以及WindowManager本身就是相当大的话题,都可以单独写本书,这时不做过多的探讨,对于我们应用开发者来说,了解一下基本的知识就够用了。

每一个Activity,都有一个Window对象,所有一切与GUI有关的事情,都委派给了Window对象,Actvity本身并不参与GUI的具体流程,比如像上面提到的DecorView,ContentView等View tree的构建与管理,View tree的渲染,以及像事件的处理,都是Window对象处理的。Window是WindowManager的基本对象,与其server之间通过IPC通信,Window是供应用程序端使用的,其实真正一切都掌握在window server手中。Activity和Dialog使用的对象都是PhoneWindow,它在frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java,Window对象会具体负责创建像DecorView之类的一些基础设施。最为关键的一个方法就是其PhoneWindow#installDecor()方法,这个方法里面会先调用generateDecor()创建mDecor,它就是前面讲到的DecorView对象,再通过generateLayout()创建mContentParent对象,它就是前面讲到的id是android.R.id.content的那个FrameLayout,Activity或者Dialog通过setContentView送过来的View tree就是加在它的下面的。

WindowManager是一个接口(Android系统的代码接口用的特别多,很多关键的架构层面的组件 都是接口,实际使用的都是其一个实现。)实际使用的是WindowManagerImpl对象,而它也没干啥,它把事情 又委派给另外一个叫做WindowManagerGlobal的对象,这个WindowManagerGlobal则是GUI端的最后一站,它负责与wms(WindowManagerServer)通信。它在frameworks/base/core/java/android/view/WindowManagerGlobal.java

需要注意WindowManagerGlobal是一个单例,也就是说每一个应用程序(严格来说是每一个进程只有一个实例,但安卓上面带有GUI的应用程序只能存活在一个进程,所以可以理解 为一个应用程序)只有一个实例,所以它管理着一个应用程序中的所有的View tree。从它的成员中便可看出,它有一坨ViewRootImpl对象(一个列表),而每一个ViewRoot对象管理着一颗View tree。

最为关键的一个方法就是WindowManagerGlobal#addView,每一个Window的持有者对象(如Activity或者Dialog)都是通过这个方法将其DecorView对象添加给WindowManager的。addView方法,会先创建一个ViewRootImpl对象,然后把要添加的view以及刚创建出来的ViewRootImpl都放进它的列表中,最后再调用ViewRootImpl#setView(view),这就把几大关键对象建立好了连接,接下来的事情就归ViewRootImpl了。这里还有一个相当关键的对象,那就是LayoutParams,WindowManagerGlobal也有一个列表里面存着每个Viewtree根节点(也就是Decor view)的LayoutParams。

ViewRootImpl又是个啥

Window是从手机系统角度来看待的窗口的概念,而View tree则是从应用程序角度构建GUI页面的概念,view tree是Window的一部分,Window对象持有mView,而这个mView就是上面提到的DecorView,也即是View tree的根节点。这里又要涉及另外一个对象ViewRootImpl,它并非是View tree的一部分,虽然名字上比较容易混淆,因为它并不是View的子类,所以它不是任何一个View tree的节点,它的职责是管理View tree,像渲染以及事件派发,都是Window直接通过ViewRootImpl来进行的。在代码中实际使用的是ViewRootImpl对象,它实现了ViewParent接口。

所以,ViewRootImpl对象是值得细细研究的,因为实际上是它在管理着GUI系统–view tree的管理,渲染的三大步(measure, layout和draw)以及事件的派发,最源头的逻辑都在这个对象里面,当然 它也是非常复杂的,源码大概有1万行左右。

ViewParent又是个啥

它是一个接口,行使的职责是管理子View,也就是说在View tree当中管理子View的行为的集合便是ViewParent接口。View tree的节点都是View的子类,所以,你看ViewRootImpl实现了ViewParent接口,它是负责管理Window里面的View tree的。另外一个就是ViewGroup,ViewGroup是View的子类,所以它是Viewtree的一部分,父节点都是ViewGroup,它核心就两样东西一个是子View的列表,另外就是ViewGroup也实现了ViewParent的接口,因为它也要管理它的子节点(也即子View)。

Activity到底是个啥东西

它是系统的四大核心组件之一,如果想构建GUI页面,则Activity是绕不开的。如果再详细一点,Activity是一个系统给你的融合了应用生命周期管理,组件级别复用(Intent相关)和窗口管理的组件,生命周期也即ActivityManager干的事情,它通过Activity的回调告诉你;而GUI则是通过Activity的Window对象帮你实现(Activity的布局和事件的处理都是委派给其持有的Window对象来处理)。

如果,把Activity的Window对象拿掉,那么它跟一个Service组件就基本上没有差别了。如果把Activity的Intent相关拿掉,那么它跟一个Dialog就没啥区别了。

Fragment又是个啥

坦白说,Fragment是Google挖的一个大坑,这玩意儿不符合Android的核心设计思想,因为Android出世的时候并没有它,是后来Google跟水果平台抄来的一个不伦不类的东西,结果全是坑。在它刚出来的一些年,Google极力的推荐使用Fragment,但是近一两年,又不推荐了。

Fragment本质上就是一个强加了生命周期函数回调的View,因为显示Fragmeng时,都是把它替换一个View或者添加到一个ViewGroup上面,所以它就是一个View,或者说一个View tree中的节点。但是强加了生命周期的回调。光是这两点,其实也没有啥,毕竟生命周期对于View是重要的,一般时候我们要在onResume与onPause之间才让View处于active状态。

Fragment最大的问题在于它的异步机制和状态恢复机制,也就是说用FragmentManager#commit了以后,具体啥时候Fragment会真正显示出来,我们是无法控制的,这是相当的坑;它的状态恢复机制就更加的坑,状态恢复这个东西如果全让程序员来负责也还好,就像Activity的设计一样,但是如果框架帮你做了一些事情,但又不完整,这就坑了,关于状态恢复的坑可以参考这篇文章来详细的了解。

DialogFragment

这个本质上是Dialog,但是被包了一层Fragment,所以它会有Fragment的特性,但是Window和View tree则是属于Dialog的。

**注意:**FragmentTransaction#add(Fragment fragment, String tag)有一个方法是不需要提供父布局,这是为没有常规布局准备的,因为无法把布局添加到Activity的现有View tree之中。一般情况下,我们是不会使用这个方法的,目前看仅在DialogFramgment中使用这个方法,那是因为Dialog本身有Window和view tree。

不在Activity view tree里面的窗口控件

一般来讲,我们想要显示的页面都会放进布局里面,也就是说大部分时候我们的页面都由Activity的view tree来实现。但是有些特殊的场景,却不是在view tree里面,比如弹窗,像Dialog,PopupWindow以及Toast,这些东西一般是用于弹出式的页面,由特定的逻辑触发,它与常规页面最为显著的区别就是,它们与Activity的Window和View tree是独立开来的,它们并不是添加在当前Activity的view tree上面的。它们自己有独立的view tree,或者换句话说,它们是独立的Window。

我们这里重点探讨它们与Window和当前Activity之间的关系,至于它们的基本使用方法,可以参阅其他文章。

Dialog

这里不说基本使用方法。

通过查阅源码,可以发现Dialog与Activity的实现相当类似,它内部也有一个独立的Window,也是通过WindowManager#addView把其ContentView(我们提供的布局)加到屏幕上去的。因此,它与Activity也是相互独立的,是两个Window,两棵View tree。Dialog类里面还有getActionBar,OptionsMenu等相关的方法,但似乎在实际使用当中比较少用到。

Dialog最为核心的两个方法一个是其构造方法,这其中会创建Window对象,另外一个就是#show,里面可以看到,它是通过WindowManager#addView()方法,来把它的mDecorView添加到窗口体系当中的,这与Activity其实是一样的。

为啥显示Dialog一定需要Activity,一般Context却不可以

使用过Dialog的人都知道,创建Dialog时一定要传递Activity为其参数,尽管构造方法里面声明的是Context。前面提到,Dialog有自己的Window和View tree,理论上它跟Activity是没有关系的。

如果,用一个非Activity作为Context传给Dialog,报错,是WindowManager抛出来的异常,说:

android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
        at android.view.ViewRootImpl.setView(ViewRootImpl.java:1093)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:409)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:110)
        at android.app.Dialog.show(Dialog.java:342)

而Dialog#setOwnerActivity(Activity)方法在创建Dialog之后再把相关Activity塞过去,也是不行的,必须传入的Context参数要是一个Activity实例才可以。

最初以为,可以从它的构造方法中看出为啥一定需要Activity,就是因为需要theme.但其实并不是,因为theme是可以通过resource id传进去的。

关键点仍在于ViewRootImpl对象,因为这个异常是ViewRootImpl在其setView方法中抛出的,前面讲过,向一个Window添加布局最终会走到WindowManagerGlobal#addView,而它又是通过ViewRootImpl#setView来做具体事情 的,这个方法里面,会先获取当前的WindowSession,然后再把当前的Viewtree转化为窗口对象,添加给wms。所以最核心的地方还得看WindowManagerService#addWindow()这个方法,这个方法也相当之复杂,但是还是能大概看懂它的意思。

通俗的来理解这是安卓系统本身加的限制,也就是说窗口本身也是有逻辑关系的,可以简单理解 为树状关系,一个Activity是主Window,而由此Activity衍生出来的属于此Window的子Window,因此在添加子Window的时候,必须 要知道它从属于哪个父Window,因此,你必须 传Activity实例给Dialog的构造方法,因为只有Activity才是有主Window的。但是这个具体的逻辑连接却比较奇怪,从上面的过程描述来看,WindowManager#addView到ViewRootImpl#setView,其实,都没有明确的把父Window,也就是Activity的Window传进来,那么WindowManagerService又是从哪里去找这个父Window呢?

WindowManagerService#addWindow方法,并没有传递父Window参数 进来,那就只能是它从传进来的参数获得的。这里一个很重要的东西就是token,它是一个IBinder对象,它是一个Window的标识,它存在Window的attris对象里面,这个就是WindowManager#LayoutParams对象,它的作用就是存储Window的特征参数,比如你要改变Window的一些特性(通俗来说就是定制一下Window),那么通过改变LayoutParams,就可以了。这个其实不难理解,我们对View不就是通过其LayoutParams来改变View的特征参数 么。都 是一样的。

Dialog对象在show()时会把其mDecor添加到WindowManagerService中去,其并未传父Window,只传了一个LayoutParams过去,其实玄机也就在LayoutParams之中,窗口的token,父token(标识着父窗口)以及像窗口的type都是在LayoutParams中。那么这个LayoutParams是哪里创建的呢?它是来自于Window对象的,而Dialog的mWindow成员实例是在构造时创建的,创建的是一个PhoneWindow对象,并且把构造Dialog传进来的Context对象传给了PhoneWindow的对象,LayoutParams对象则是通过mWindow.getAttributes()得来的。因此啊,可以断定,PhoneWindow在生成LayoutParams时,会从传给其构造的上下文对象mContext中获取一些信息,如窗口的类型或者父窗口信息,而只有Activity对象才有窗口信息,并且可以作为父窗口,而普通 的Context对象是没有窗口的,由此可以解答我们的疑惑了。

也可以显示独立于任何Activity的Dialog

窗口是有很类型的,WindowManagerService为了方便管理,所以针对Activity及其从属于子窗口(Dialog和PopWindow)做了类似tree结构的逻辑上的整理,所以普通 的Dialog必须要能找到其主窗口(或者叫父窗口)。

但其实,我们经常能见到一些非常牛逼的Dialog,可以显示 在任何Activity之上,如电源没了,或者音量调节,等等。这些是叫作system dialog,需要特殊权限 才能显示出来的。管理来理解,系统级别的组件 才有权限 显示system dialog。

其实,想一想也合理,作为一个应用程序,你在自己的生命周期内,显示内容给用户足够的信息就可以了。当用户离开了你的应用,你也没有必要再显示Dialog了。

:应用在后台时,想在前台显示信息有其他的方式,如Notification等,这属于另外的话题,不做过多讨论。

可以弄个全屏的Dialog吗?

一般来讲呢,Actiivty都是全屏的,Dialog一般是非全屏的,可以把一个Activity弄成非全屏的,长的像Dialog一样,当成Dialog来使用,就在设置Activity的Theme时,用Theme.Dialog就可以了。

那么,反过来搞可不可呢,就是可不可以把常规的Dialog弄成一个全屏的呢?

从Dialog的实现上来看,它有Window对象,甚至连Actionbar和OptionsMenu都有,所以从实现上来看,Dialog并不一定非要像我们平常所使用的那样是一个对话框,它能做的事情 不比Activity少。默认Dialog的style就是一个平常的对话框,但其实,设置不同的style,就可以得到全屏的dialog。

     private void showFullscreenDialog() {
        // Theme_Material_NoActionBar_Fullscreen is real full screen, i.e. hide the status bar.
        Dialog dialog = new Dialog(this, android.R.style.Theme_Material_NoActionBar);
        dialog.setContentView(R.layout.fullscreen_dialog);
        dialog.findViewById(R.id.okay).setOnClickListener(view -> {
            dialog.dismiss();
        });
        dialog.show();
    }

:这里有点歧义,全屏意思是指铺满整个父Activity,严格意义上的全屏是要把状态栏也要隐藏掉。

PopupWindow

PopupWindow是一个独立的类,并不是View的子类,因此,它跟常规的widget不一样,无法直接添加到现有的View tree之中,这也导致它的实现方式比较复杂。

PopupWindow它并没有创建Window对象,但是它有一个类似于Window对象的DecorView的东西,它的根节点是一个叫做PopupDectorView的东西,其实是一个FrameLayout,我们让PopupWindow显示的布局就是加在这个PopupDectorView下面。最重要的两个方法一个是preparePopup() 这个方法会创建根节点PopupDecorView,然后把我们需要显示的mContentView以及还有一个PopupBackgroundView(也是一个FrameLayout,包裹在要显示的ContentView外面),放在PopupDecorView的下面,所以真实的结构是根节点是PoupDecorView,包了PopupBackgroundView,再包上要显示的mContentView,一共三层。

另外,一个方法就是invokePopup,核心逻辑是调用WindowManager#addView,把mDecorView添加到窗口系统中以显示出来,后面的过程跟上面提到的Dialog的显示过程是一样的。那么PopupWindow又是如何找到Activity的主Window的呢?答案还是在LayoutParams中,方法preparePopup()的参数 是LayoutParams,如前面所述LayoutParams是最终会传递给WindowManagerService的,而这里面就包含了主窗口的信息。而这个LayoutParams对象是通过方法createPopupLayoutParams()得来的,而这个方法的参数 是一个IBinder对象,我们知道这个IBinder对象就标识着一个主窗口。那么PopupWindow的IBinder对象又从何而来呢?是通过View.getWindowToken()得来的,PopupWindow的显示 方法都要提供一个View如showAsDropDown,里面的参数是一个View,而这个View必须 是已显示的View tree中的一个节点,现在应该知道一个窗口有一颗View tree,那么此View tree中的节点肯定 知道自己属于哪个窗口啊,由此便找到了主窗口。

另注意,PopupMenu,也是基于PopupWindow的,只不过弄成了Menu的样子(其实就是一个ListView)。

可以弄个全屏的PopupWindow吗?

当然 可以,只需要在构造PopupWindow时传入MATCH_PARENT作为其宽和高就可以了,不过这样做以后后面再选择哪种show方式就不影响了,都是铺满Activity来显示。

     private void showFullscreenPopup() {
        final View content = LayoutInflater.from(this).inflate(R.layout.fullscreen_dialog, null, false);
        PopupWindow popup = new PopupWindow(content, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        final View anchor = findViewById(R.id.fullscreen_popup);
        // Key is the width and height passed to constructor, show does not affect anything.
//        popup.showAtLocation(anchor, Gravity.NO_GRAVITY, 0, 0);
        popup.showAsDropDown(anchor, 100, 200);
        content.findViewById(R.id.okay).setOnClickListener(view -> {
            popup.dismiss();
        });
    }

Toast又是个啥

这个大家都非常熟悉了,每天都用到,用以给出一些非常弱的提示。

它其实也是有独立Window的。Toast类本身比较简单,但它也是有一个专门的Server的叫NotificationManager,Toast也是一个客户端,直接做工作的是另一端的服务,这也是为何即使我们的应用退到了后台依然可以show一个Toast。我们用的最多的就是让其显示一段文字,但其实那只是它的一个非常基础的用法。从Toast的方法就可以看出来,它是可以接受一个View的,所以把一个布局的根节点传进去,那这个布局不就可以显示了么?

Toast可以显示复杂布局吗?

虽然,通常我们都是使用Toast.makeText方法,但这并意味着它只能显示纯文字,它是可发接收一个View作为其Content的,就通过其setView方法:

     private void showComplexToast() {
        Toast toast = new Toast(this);
        final View dialog = LayoutInflater.from(this).inflate(R.layout.fullscreen_dialog, null, false);
        toast.setDuration(Toast.LENGTH_LONG);
        toast.setView(dialog);
        // This does not work, Toast cannot receive focus, i.e. it won't receive events from WMS
        dialog.findViewById(R.id.okay).setOnClickListener(view -> {
            toast.cancel();
        });
        toast.show();
    }

不过呢,虽然Toast可以展示更为复杂的布局,但是它是无法接收用户事件,也就是说它是无法处理点击事件的,你想有用户交互的话,是不可以的。

如此,假如你想显示一个类似Toast的,但是可以交互 的,那只能用PopupWindow或者Dialog来模拟,但这又只能是在应用在前台时显示;假如在后台时,又想要有交互行为,那只能用Notification和PendingIntent了。

综合结论

说了这么多,希望还没有看晕,总结一下:

  1. Window也是有结构 关系的,类似于View一样,像一样tree
  2. 每一个Window都有一颗View tree,DecorView是其根节点
  3. ViewRootImpl是用来管理View tree的
  4. Dialog和PopupWindow可以用以显示铺满Activity,甚至全屏的View
  5. Toast也可以展示复杂布局

实战建议

Activity应该只用于显示一个页面内的主要的,逻辑上都可以触达的布局,比如一上来用户就可见的所有东西,以及常规操作可以触发的(如折叠展开等)。

Activity的View tree要尽可能的小,这样才能保证最好的渲染性能,其余的,很多一次性的,即插即用的,鲜有逻辑才会有触发的,这种布局,要尽可能的独立于Activity的View tree之外,以保证其布局和逻辑上的独立,也更方便维护,更能减少Activity的view tree的体积。因为Dialog和PopupWindow也可以铺满整个Activity,所以,像一些用户引导,新人引导,运营活动,分享,等等一些常规逻辑走不到的页面,都可以考虑用Dialog和PopupWindow来实现。

原创不易,打赏点赞在看收藏分享 总要有一个吧

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

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

相关文章

【MySql】MySql索引的作用索引的理解

【MySql】MySql索引的作用&&索引的理解 文章目录 索引的作用认识磁盘MySql 与磁盘交互基本单位Page共识索引的理解主键有序问题理解单个Page理解多个Page页目录单页情况多页情况 索引的作用 索引是与效率挂钩的&#xff0c;所以没有索引&#xff0c;可能会存在问题 索…

从0到1搭建属于自己的Gitlab CI/CD平台

文章目录 持续集成(CI)持续交付(CD)Gitlab CI/CD功能和架构本地搭建GitLab CI/CD平台 MCNU云原生&#xff0c;文章首发地&#xff0c;欢迎微信搜索关注&#xff0c;更多干货&#xff0c;第一时间掌握&#xff01; CI和CD是软件开发中常用的缩写&#xff0c;分别代表持续集成&am…

【Unity】 HTFramework框架(四十五)【进阶篇】指令系统-自定义指令

更新日期&#xff1a;2023年6月19日。 Github源码&#xff1a;[点我获取源码] Gitee源码&#xff1a;[点我获取源码] 索引 指令系统1.使用指令系统2.自定义指令1.新建类 MyInstruction2.标记 CustomInstruction 特性3.编写指令的逻辑4.执行自定义指令5.自定义指令的参数6.结尾 …

C进阶 - 数组和指针

Declare an array manually VS malloc or calloc an array 用英文是因为有些东西得用他们的语言才能表达不失真 栈和堆 In C, a heap is a region of memory that is dynamically allocated during runtime. It is used to store variables and data structures that have a …

学习HCIP的day.14

目录 STP生成树协议 生成树协议 一、802.1D 1、根网桥 2、根端口 3、指定端口 4、非指定端口 5、接口状态 802.1D的收敛时间 802.1D算法的缺点 以上cost值的解释 二、PVST 三、PVST 四、快速生成树 五、MSTP/MST/802.1S STP生成树协议 网络三层架构…

南京企业所得税高怎么办?

南京企业所得税高怎么办&#xff1f; 《税筹顾问》专注于园区招商&#xff0c;您的贴身节税小能手&#xff0c;合理合规节税&#xff01; 众所周知&#xff0c;企业所得税是按利润来计算的&#xff0c;按照不同的利润阶梯计算适用的税率也会有所不同&#xff0c;那么当企业利润…

launch文件的编写及ROS配置文件的详细介绍

launch文件的编写及ROS配置文件的详细介绍 1 launch文件介绍及简单应用1.1 launch文件介绍1.2 launch文件简单应用 2 package.xml文件介绍及配置3 CMakeLists.txt文件介绍及配置 1 launch文件介绍及简单应用 1.1 launch文件介绍 根据ROS的架构和通信机制来看&#xff0c;ROS的…

讯飞星火大模型详细内测体验:它能否应对这些挑战?

名人说&#xff1a;一花独放不是春&#xff0c;百花齐放花满园。——《增广贤文》 作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、简要介绍二、分类问题测试0️⃣自我介绍1️⃣语言理解2️⃣知识问答3️⃣逻辑推…

Vue中如何进行文件转换与格式转换

Vue中如何进行文件转换与格式转换 在Web应用程序中&#xff0c;经常需要进行文件转换和格式转换。例如&#xff0c;将PDF文件转换为图像文件、将音频文件转换为不同的格式或将视频文件转换为不同的分辨率和编解码格式。Vue作为一种流行的前端框架&#xff0c;提供了许多实用工…

网络管理与维护(二)网络用户设置管理

网络用户设置管理 2.1 用户帐户安全管理 用户账户的分类 管理员帐户。拥有管理本台计算机的所有权限和权利。系统内置的Administrator用户帐户 和Administrators组帐户的成员就属于管理员帐户 标准帐户。通常分配给最终用户使用&#xff0c;适用于日常工作&#xff0c;对操作…

GeoServer中地图可视化提升利器之SLD知识简介

目录 前言 一、SLD简介 1、介绍 2、SLD的版本 3、SLD的Schema说明 二、SLD中相关知识解析 1、Scheme简要说明 2、一个SLD实例 总结 前言 在互联网上有很多精美的地图&#xff0c;在地图从shp或者gdb等矢量文件&#xff0c;经过设计人员的加工&#xff0c;配色&#xff0…

【论文阅读】Graph-less Collaborative Filtering

【论文阅读】Graph-less Collaborative Filtering 文章目录 【论文阅读】Graph-less Collaborative Filtering1. 来源2. 介绍3. 模型解读3.1 协同过滤3.2 模型3.2.1 对比知识精馏 3.2.2 自适应对比正则化3.2.3 SimRec的参数学习 4. 实验5. 总结 1. 来源 2023WWW CCFA原文地址co…

【Linux】linux | 服务响应慢、问题排查 | 带宽问题导致 | 网速

一、说明 1、项目使用云服务器&#xff0c;服务器配置&#xff1a;5M带宽、4核、32G&#xff0c;1T&#xff0c;CentOS7 2、CPU、内存、磁盘IO都没有达到瓶颈&#xff0c;猜测是带宽问题 3、应用比较多&#xff0c;应用中间件&#xff0c;十几个差不多 4、同时在线人数30 5、已…

“暗网议会”如今已成为现实

图片来源:Marcin Balcerzak 最近&#xff0c;“暗网议会”已经成为了网络犯罪分子试图证明自己影响力的最新流行语&#xff0c;安全内部人士对这个词也很感兴趣。 上周五&#xff0c;臭名昭著的亲俄黑客组织Killnet在其电报威胁帖子中使用了这个词语。随后&#xff0c;twitte…

d2l_第五章学习_Multilayer Perceptrons多层感知机

x.1 Hidden Layers 线性模型的基本假设是单调&#xff0c;即任何特征的增大都会导致模型的输出增大&#xff08;权重正时&#xff0c;负值时亦&#xff09;。但是现实中很多的关系并不仅仅是简单的线性关系&#xff0c;这个时候就需要引入非线性关系&#xff0c;而非线性关系由…

Verilog基础之七、译码器实现

目录 一、前言 二、工程实现 2.1 工程代码 2.2 仿真结果 2.3 参考 一、前言 ​译码器的实现为编码器的逆过程&#xff0c;以3-8译码器为例&#xff0c;真值表如下。 二、工程实现 ​实现同时使用for循环和case两种方式。 2.1 工程代码 module Decoder(in,out,out_case )…

【菜单折叠效果】这菜单效果千万别让领导看了,一不小心就升职加薪(附源码)

【写在前面】 上周想着冲一波粉丝量&#xff08;周冲700&#xff09;&#xff0c;但是事与愿违&#xff0c;没办法我只能不断的督促自己多分享&#xff0c;多总结了&#xff0c;那么今天晚上我就好好整理了一篇常见后台管理系统的菜单收缩动态效果&#xff0c;主要是用于后台管…

基于深度学习的高精度打电话检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度打电话检测识别系统可用于日常生活中或野外来检测与定位打电话目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的打电话目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检…

Web3通过ganache运行起一个本地虚拟区块链

通过文章 Web3开发准备工作 手把手带你创建自己的 MetaMask 账号大家简单的对网络 有了个比较模糊的概念 不同的网络连接这不同的区块链 那么 我们就要搞清楚 我们切换不同的网络 我们的数字资产是不一样的 在这里 我们需要先安装一个插件工具 ganache 我们先在本地创建一个文…