Window的创建

Window的创建

上一篇说到了Window和WindowManager的关系并且讲述了WindowManager如何添加Window与Window内部的三个方法的实现

这篇主要讲几个常见的Window的创建比如Activity,Dialog和Toast

其中Activity属于应用Window

Dialog属于子Window

Toast属于系统Window

z-ordered越来越大,它的优先级就越来越大

Activity的Window的创建

Activity的启动最终是由ActivityThreadperformLaunchActivity,这个方法在内部会通过内加载器创建一个Activity的实例变量。

然后调用attach,将一系列上下文环境变量关联起来,确保 Activity 可以正常运行并与应用程序的其他组件进行通信。

我们可以看看attach关联了哪些

我们看看它的源码

通过ctrl+n

 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.activityConfigCallback,
                        r.assistToken, r.shareableActivityToken);

并且在attach的过程中还会创建Activity所属的Window

记得我们在View的工作原理的时候说到过:

Activity创建完成后,会把DecorView添加到Window中,并创建相应的ViewRootImpl,再把ViewRootImpl与DecorView关联起来

而Activity的Window的创建就是在performLaunchActivity中创建Activity后调用attach来创建的

1.Window的创建

问题:2个Window的不同

attach

《艺术开发探索》中说在attach中通过PolicyManagermakeNewWindow获得window,但我找了半天没找到那段源码

只在attach里面找到了

 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);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mAssistToken = assistToken;
        mShareableActivityToken = shareableActivityToken;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }

        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 = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
        mWindow.setPreferMinimalPostProcessing(
                (info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);

        getAutofillClientController().onActivityAttached(application);
        setContentCaptureOptions(application.getContentCaptureOptions());
    }

即它没有用**PolicyManager.makeNewWindow(this)**来创建Window的

而是

mWindow = new PhoneWindow(this, window, activityConfigCallback);

其中这里面的window对象来自

抽象类

public abstract class Window

chatGPT的回答是:

实际的 Window 创建是在 PhoneWindow 中进行的,而不是在 Activityattach() 方法中直接调用 PolicyManager.makeNewWindow(this)

final void attach(...) {
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    ...
}

private void performLaunchActivity(...) {
    ...
    // 创建 Activity 的实例
    Activity activity = instantiateActivity(cl, component);
    ...
    // 创建 PhoneWindow 对象
    Window window = policyManager.makeNewWindow(activity);
    ...
    // 调用 attach() 方法
    activity.attach(..., window, ...);
    ...
}

总结起来,mWindow = new PhoneWindow(this, window, activityConfigCallback); 创建了一个具体的 PhoneWindow 对象,用于管理 Activity 的窗口。而 Window window = policyManager.makeNewWindow(activity); 创建了一个基本的 Window 对象,作为系统策略管理器与 Activity 的窗口之间的接口。

第一个Window是具体的窗口实现,用于管理 Activity 的窗口。

第二个Window是系统级别的Window对象,并不是直接与 Activity 关联的窗口对象。

并且给出的最终回复,在 Activity 的创建过程中,主要是创建了一个具体的 PhoneWindow 对象,用于表示和管理 Activity 的窗口。系统级别的 Window 对象由策略管理器创建,用于处理窗口的管理和操作。


我们创建完成PhoneWindow后,Activity实现了windowcallBack接口,可以把Activity自己设置为window的观察者。然后我们就开始初始化WindowManager,记得之前我们说过的Window没办法直接访问,我们只能通过访问WindowManager来访问Window

mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);     

我们继续看看setWindowManager的源码

2.WindowManager的初始化过程

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    mAppToken = appToken;
    mAppName = appName;
    mHardwareAccelerated = hardwareAccelerated;
    if (wm == null) {
        //获取到应用服务的WindowManager
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

mAppToken,mAppName, mHardwareAccelerated这三个就是给它们赋了个值m主要还是下面的

先判断传进去的WindowManager为不为空,为空的话进行初始化

,这个初始化与上一篇博客我进行完WindowManager的布局后的初始化一样都是

WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

然后将**((WindowManagerImpl)wm).createLocalWindowManager(this)赋给mWindowManager**

话说mWindowManager是什么,我们点击它,会发现它是一个全局变量

private WindowManager mWindowManager;

而((WindowManagerImpl)wm).createLocalWindowManager(this)是在创建一个本地的窗口管理器对象。

本地窗口管理器是一个与设备本地窗口系统交互的对象,它提供了与底层窗口系统通信的功能。通过创建本地窗口管理器,可以在应用程序中实现对窗口的创建、显示、布局、交互等操作。


PhoneWindow类型的mWindow已经通过

mWindow = new PhoneWindow();

创建好了

刚才在setWindowManager中的内部也成功创建好了WindowManager

便可以成功绑定WindowWindowManager

又因为我们之前说过WindowManager主要是由WindowManagerImpl实现的,

所以刚才那句话也就相当于WindowWindowManagerImpl进行成功的绑定

我们就可以通过调用WindowManagerImpl的那三个方法来实现Windowadd,remove,update

3.流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVB1PIU0-1685279766886)(../../assets/流程图-导出 (3)]-1685262524947-1.png)

4.把Activity的布局文件设置给PhoneWindow

上面提到调用Activityattach方法之后,会回调ActivityonCreate方法,在其中会调用setContentView来设置布局,如下:

public void setContentView(View view, ViewGroup.LayoutParams params) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

Activity将setContentView具体实现交给了Window处理,这里的getWindow返回我们上面创建的PhoneWindow对象。我们继续看下去:

// 注意他有多个重载的方法,要选择参数对应的方法
public void setContentView(int layoutResID) {
    // 创建DecorView
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
            getContext());
    transitionTo(newScene);
} else {
    // **这里根据布局id加载布局,把Activity的布局加载到DecorView的**mContentParent**中**
    mLayoutInflater.inflate(layoutResID, mContentParent);

}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
    // 回调activity的方法,**通知Activity视图已经发生改变**
    cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

首先我们先看第一个if

虽然不知道它在干什么但是我们看到了

  installDecor();

就可以明白它创建了一个DecorView

首先判断 mContentParent 是否为 null。mContentParent 是 Window 的内容视图的父级容器,如果为 null,表示 DecorView 还未创建,因此需要调用 installDecor() 来创建并安装 DecorView。

如果 mContentParent 不为 null,则进一步判断是否存在 FEATURE_CONTENT_TRANSITIONS 特性。如果不存在该特性,表示不需要进行内容转场动画,那么可以通过 mContentParent.removeAllViews() 清空已有的内容视图。

之后第二个if就是判断是否存在 FEATURE_CONTENT_TRANSITIONS 特性,如果开启了该特性,则会使用场景切换的方式来加载布局

没开启的话会直接使用 mLayoutInflater.inflate(layoutResID, mContentParent) 方法将指定布局文件加载到 DecorView 的 mContentParent 容器中,完成 Activity 的布局加载。

之后就是 回调Activity的callBack方法

4.1简单流程

总之把Activity的布局文件加载到PhoneWindow就以下几个流程

1.判断DecorView是否创建,没有创建就创建

2.将Activity的布局加载到DecorView的mContentParent

3.进行Activity的回调

所以我们就可以知道了Activity的加载布局文件为什么是setContentView了,因为Activity的加载布局文件最后加载到了Window的DecorWindow的**ContentView**中


5.总结前三步

现在回顾以下

我们刚才做的

1是创建了Window,(Window是在ActivityThread中调用performLaunchActivity中创建的Activity调用attach中创建的)

2.如何创建WindowManager,并且进行了绑定(在Window调用setWindowManager中创建的WindowManager)

3.如何将Activity的布局文件设置给PhoneWindow(1.判断DecorView是否创建

​ 2.将布局文件传给DecorView的ContentParent

​ 3.进行Activity的回调,给Activity说DecorView已经创建好了)

现在就差最后一步了,那就是把DecorView作为window添加到屏幕上。

肯定想的是那很简单啊,WindowManager已经和Window绑定了,那么我们直接WindowManager.add不就好了

而且在Activity的创建的时候我们说过:

Activity创建完成后,会把DecorView添加到Window中,并创建相应的ViewRootImpl,再把ViewRootImpl与DecorView关联起来

但是

但是这个时候由于DecorView并没有被WindowManager识别,所以这个时候的Window无法提供具体功能,因为它还无法接收外界的输入信息

6.将DecorView添加到Window中

还是在ActivityThread中

我们调用

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
    // 调用Activity的onResume方法
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ...
    // 让decorView显示到屏幕上
	if (r.activity.mVisibleFromClient) {
        r.activity.makeVisible();
}

首先第一步是performResumeActivity进行了onResume方法的回调

第二步是**makeVisible()**让decorView显示到屏幕上

我们点击makeVisible()看里面的源码

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

会发现它在这个地方调用了addView,并且将mDecor设置为可见

7.Activity创建的流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9W5bacP-1685279766887)(../../assets/流程图-导出 (4)]-1685265053644-3.png)

Dialog的Window创建

只要你把Activity的Window的创建好了,Dialog的Window创建就很容易了

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    ...
    // 获取windowManager
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//mWindowManager其实是Activity的WindowManager,这里的context一般是activity
	// 构造PhoneWindow
    final Window w = new PhoneWindow(mContext);
    mWindow = w;
    // 初始化PhoneWindow
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    w.setOnWindowSwipeDismissedCallback(() -> {
        if (mCancelable) {
            cancel();
        }
    });
    w.setWindowManager(mWindowManager, null, null);
    w.setGravity(Gravity.CENTER);
    mListenersHandler = new ListenersHandler(this);
}

public Object getSystemService(@ServiceName @NonNull String name) {
    if (getBaseContext() == null) {
        throw new IllegalStateException(
                "System services not available to Activities before onCreate()");
    }
	// 获取的是activity的windowManager
    if (WINDOW_SERVICE.equals(name)) {
        return mWindowManager;
    } else if (SEARCH_SERVICE.equals(name)) {
        ensureSearchManager();
        return mSearchManager;
    }
    return super.getSystemService(name);
}

1.创建Window

这步和Activity的Window创建一样,就没必要讲了

2.初始化DecorView并将Dialog视图添加到DecorView中

  public void setContentView(int layoutResID) {
      mWindow.setContentView(layoutResID);
  }

3.将DecorView添加到Window并显示

public void show() {
   ...
    // 回调onStart方法,获取前面初始化好的decorview
    onStart();
    mDecor = mWindow.getDecorView();
    ...
    WindowManager.LayoutParams l = mWindow.getAttributes();
    ...
    // 利用windowManager来添加window    
    mWindowManager.addView(mDecor, l);
//这里的mWindowManager是Activity的WindowManager
    ...
    mShowing = true;
    sendShowMessage();
}

Dialog的Window创建和Activity的Window创建过程有很多类似的地方,二者几乎没有区别

Dialog在被关闭的时候,会通过WindowManager来移除DecorView:

mWindowManager.removeViewImmediate(mDecor);

removeViewImmediate是同步方法

removeView是异步方法

普通的Dialog有一个特殊之处,那就是必须采用Activity的Context,如果采用Application的Context,那么就会报错

是没有应用token导致的,而应用token一般只有Activity拥有,所以只需要用Activity作为Context来显示对话框。

系统Window比较特殊,它可以不要token

我们只需要这么改就可以运行了

dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM.ERROR);

还有

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Toast的Window创建

Toast比Dialog稍微难一点,虽然Toast也是基于Window来实现的,但是Toast有定时取消这一功能。

所以系统采用了Handler

Toast内部有2类IPC过程:

1.Toast访问NotificationManagerService

2.NotificationManagerService回调Toast里的TN接口

Toast属于系统Window,它内部的视图由两种方式指定,一种是系统默认的样式另一种是通过setView方法来指定一个自定义View,视图都对应Toast的一个View类型的内部成员mNextView。Toast提供了show和cancel分别用于显示和隐藏Toast,show和cancel的内部是IPC过程,

Toast的show()

首先看Toast的显示过程,它调用了NMS中的enqueueToast方法,如下所示。

public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }
 	INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;

    try {
        service.enqueueToast(pkg, tn, mDuration);
    } catch (RemoteException e) {
        // Empty
    }
}

NMSenqueueToast方法的第一个参数表示当前应用的包名,第二个参数tn表示远程回调,第三个参数表示Toast的时长。

enqueueToast会先将Toast请求封装成为ToastRecord对象并将它添加到一个名为mToastQueue的ArrayList集合中

对于非系统应用来说,mToastQueue最多同时存在50个ToastRecord,这样做是为了防止DOS,如果不这样做,使用大量循环弹出Toast会导致其他应用没机会弹出Toast,那么对于其他应用的Toast请求,系统的行为就是拒绝服务

// Limit the number of toasts that any given package except the android
// package can enqueue.  Prevents DOS attacks and deals with leaks.
if (! isSystemToast) {
    int count = 0;
    final int N = mToastQueue.size();
    for (int i=0; i<N; i++) {
        final ToastRecord r = mToastQueue.get(i);
        if (r.pkg.equals(pkg)) {
                  count++;
                  if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                      Slog.e(TAG, "Package has already posted " + count
                              + " toasts. Not showing more. Package=" + pkg);
                      return;
                  }
              }
          }
      }
}

正常情况下,一个app的ToastRecord不会到达上限,当ToastRecord被添加到mToastQueue后,NMS会通过showNextToastLocked方法显示当前的Toast

void showNextToastLocked() {
    ToastRecord record = mToastQueue.get(0);
    while (record ! = null) {
        if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.
        callback);
        try {
            record.callback.show();
            scheduleTimeoutLocked(record);
            return;
        } catch (RemoteException e) {
            Slog.w(TAG, "Object died trying to show notification " + record.
            callback
                    + " in package " + record.pkg);
            // remove it from the list and let the process die
            int index = mToastQueue.indexOf(record);
            if (index >= 0) {
                mToastQueue.remove(index);
            }
            keepProcessAliveLocked(record.pid);
            if (mToastQueue.size() > 0) {
                record = mToastQueue.get(0);
            } else {
                record = null;
                  }
              }
          }
      }

需要注意的是Toast的显示是由ToastRecord的callback完成的,这个callback是Toast中的TN对象的远程Binder

通过callback访问TN中的方法需要跨进程完成,最终被调用的TN中的方法会运行在发起Toast请求的应用的Binder线程池

Toast显示以后,NMS还会通过scheduleTimeoutLocked方法发送一个延迟消息,消息的延迟取决于Toast的时长

private void scheduleTimeoutLocked(ToastRecord r)
{
    mHandler.removeCallbacksAndMessages(r);
    Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
    long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
    mHandler.sendMessageDelayed(m, delay);
}

LONG_DELAY为3.5s,而 SHORT_DELAY为2.5s

我最开始以为这个就决定着

Toast.makeText(MainActivity.this,"nihao",Toast.LENGTH_LONG).show()

的这个的持续时间,后来搜了一下发现:

实际上,delay 的值是用于处理 Toast 消息的显示时间间隔,而不是 Toast 消息的总显示时间。在 Android 中,LONG_DELAYSHORT_DELAY 是表示延迟时间的常量,具体的值可能会根据系统的设置而有所不同。

Toast的show()总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R23kHBJI-1685279766888)(../../assets/流程图-导出 (5)].png)

我对show方法的理解就是Toast把它封装到ToastRecord并把它放入ToastQueue集合中

然后NMS会进行回调ToastRecord中的callback即Toast中的TN方法

Toast的hide()

try {
    record.callback.hide();
} catch (RemoteException e) {
    Slog.w(TAG, "Object died trying to hide notification " + record.callback
            + " in package " + record.pkg);
    // don't worry about this, we're about to remove it from
    // the list anyway
}

Toast的显示和隐藏的本质

可以看出来Toast的显示和隐藏过程实际上是通过Toast的TN这个类实现的,它有一个**show()方法和一个hide()**方法

分别对应Toast的显示和隐藏

由于这两个方法是被NMS跨进程调用的,因此它们都在Binder线程池中,为了将执行环境切换到Toast所在的线程,内部使用Handler

/**
 * schedule handleShow into the right thread
 */
@Override
public void show() {
    if (localLOGV) Log.v(TAG, "SHOW: " + this);
    mHandler.post(mShow);
}

/**
 * schedule handleHide into the right thread
 */
@Override
public void hide() {
    if (localLOGV) Log.v(TAG, "HIDE: " + this);
    mHandler.post(mHide);
}

可以发现mShow和mHide是两个Runnable,内部分别调用了handleShow和handleHide,所以它们两个才是真正完成显示和隐藏的

TN的handleShow会将Toast视图加载到Window中

mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams)

而TN的handleHide会将Toast视图从Window中移除

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

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

相关文章

git工作流实践

常见分支命名 远程仓库的分支&#xff1a;主干分支master, 开发分支dev&#xff0c;发布分支release 个人开发分支&#xff1a;特性分支feature, 缺陷修改分支bugfix&#xff0c; 热更新分支 hotfix 一般工作流如下 创建个人本地开发分支&#xff1a; git checkout -b feat…

springboot+vue+elementui计算机专业课程选课管理系统vue

本系统的主要任务就是负责对学生选课。主要用户为老师、学生,其中,学生可对自己的信息进行查询,可以进行选课,也可以进行删除已选课程,教师可对学生和课程的信息进行查询&#xff0c;教师拥有所有的权限,可以添加删除学生信息。系统提供界面,操作简单。 为实现这些功能,系统一个…

JAVA之数组2

添加元素 从后往前进行迭代&#xff0c;最后在末尾插入元素 tip&#xff1a;为避免数字在覆盖过程中丢失&#xff0c;故从后往前覆盖 删除元素 从前往后迭代&#xff0c;最后将末尾赋值为0 tip: 以覆盖的数arr【i】为基准&#xff0c;构造循环 共同处Tip: 范围均为【index1&…

【网络协议详解】——DHCP系统协议(学习笔记)

目录 &#x1f552; 1. DHCP概述&#x1f552; 2. 工作过程&#x1f552; 3. DHCP的报文格式&#x1f552; 4. DHCP中继代理&#x1f552; 5. 实验&#xff1a;DHCP配置 &#x1f552; 1. DHCP概述 动态主机配置协议DHCP&#xff08;Dynamic Host Configuration Protocol&…

SpringCloudAlibaba整合分布式事务Seata

文章目录 1 整合分布式事务Seata1.1 环境搭建1.1.1 Nacos搭建1.1.2 Seata搭建 1.2 项目搭建1.2.1 项目示意1.2.2 pom.xml1.2.2.1 alibaba-demo模块1.2.2.2 call模块1.2.2.3 order模块1.2.2.4 common模块 1.2.3 配置文件1.2.3.1 order模块1.2.3.2 call模块 1.2.4 OpenFeign调用1…

【C++】容器篇(四)—— queue的基本介绍以及模拟实现

前言&#xff1a; 在上期博文中我带大家对stack进行深入的学习&#xff0c;本期我将带领学习的是关于 queue的基本知识&#xff0c;并且还将给大家介绍并实现 priority_queue。接下来&#xff0c;让我们正式本期的内容。 目录 &#xff08;一&#xff09;queue的基本介绍 &…

Eclipse教程 Ⅵ

今天分享Eclipse Java 构建路径、Eclipse 运行配置(Run Configuration)和Eclipse 运行程序 Eclipse Java 构建路径 设置 Java 构建路径 Java构建路径用于在编译Java项目时找到依赖的类&#xff0c;包括以下几项&#xff1a; 源码包项目相关的 jar 包及类文件项目引用的的类…

83.响应式设计原则

什么是响应式设计&#xff1f; ● 使网页根据任何可能的屏幕尺寸&#xff08;窗口或视口尺寸&#xff09;调整其布局和视觉风格的设计技术。 ● 在实践中&#xff0c;这意味着响应式设计使网站可以在所有设备上使用&#xff0c;如台式电脑、平板电脑和手机。 ● 这是一套做法&…

LearnOpenGL-高级OpenGL-9.几何着色器

本人初学者&#xff0c;文中定有代码、术语等错误&#xff0c;欢迎指正 文章目录 几何着色器使用几何着色器造几个房子爆破物体法向量可视化 几何着色器 简介 在顶点和片段着色器之间有一个可选的几何着色器几何着色器的输入是一个图元&#xff08;如点或三角形&#xff09;的一…

Spark入门介绍

目录 一、Spark框架概述 1、Spark简介 2、发展 二、Spark功能及特点 1、定义

K8s进阶7——Sysdig、Falco、审计日志

文章目录 一、分析容器系统调用&#xff1a;Sysdig1.1. 安装1.2 常用参数1.3 采集分析1.4 示例1.4.1 查看某进程系统调用事件1.4.2 查看建立TCP连接事件1.4.3 查看某目录下打开的文件描述符1.4.4 查看容器的系统调用 1.5 Chisels工具1.5.1 网络类1.5.2 硬盘类1.5.3 cpu类1.5.4 …

奇舞周刊第493期:Hook 革命!浅谈 React 新 Hook 的未来与思想

关注前端生态发展&#xff0c;了解行业动向。 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ Hook 革命&#xff01;浅谈 React 新 Hook 的未来与思想 作者阳羡曾写文章对 React 新 Hook use 的设计理念和限制进行了深入分析&#xff0c;并提供了一个可能的实现来帮助读者…

如何在 Alpine Linux 上启用或禁用防火墙?

防火墙是计算机网络安全的重要组成部分&#xff0c;它用于保护计算机和网络免受未经授权的访问和恶意攻击。Alpine Linux 是一种轻量级的 Linux 发行版&#xff0c;常用于构建容器化应用和嵌入式系统。本文将详细介绍如何在 Alpine Linux 上启用或禁用防火墙。步骤 1&#xff1…

Seata-Server安装

1.去哪下 发布说明: https://github.com/seata/seata/releases 2.怎么玩 本地**Transactional**全局**GlobalTransactional** 我们只需要使用一个 GlobalTransactional 注解在业务方法上: 3.Seata-Server安装 官网地址 3.1.版本0.9.0 seata-server-0.9.0.zip解压到指定目…

Linux——进程轮换

目录 一.进程切换 1.定义&#xff1a; 2.硬件上下文&#xff1a; 总结&#xff1a; 一.进程切换 1.定义&#xff1a; 进程切换(process switch),作为抢占式多任务的一个重要功能&#xff0c;其实质就是OS内核挂起正在运行的进程A,然后将先前被挂起的另一个进程B恢复运行。 2.硬…

什么是Redission可重入锁,其实现原理是什么?

一、概述 Redission是一个可重入锁&#xff0c;它可以在分布式系统中用于实现互斥锁。这种锁可以允许多个线程同时获取锁&#xff0c;但在任何给定时间只有一个线程可以执行受保护的代码块。 Redission锁提供了一种简单的方法来保证在分布式系统中的互斥性&#xff0c;同时支…

K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods

K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods 你已了解Pod以及如何通过ReplicaSets等资源部署它们以确保持续运行。虽然某些Pod可以独立完成工作&#xff0c;但现今许多应用程序需要响应外部请求。例如&#xff0c;在微服务的情况…

day42_jsp

今日内容 零、 复习昨日 一、JSP 二、EL 三、JSTL 四、MVC 零、 复习昨日 一、JSP 1.0 引言 现有问题 在之前学习Servlet时&#xff0c;服务端通过Servlet响应客户端页面&#xff0c;有什么不足之处&#xff1f; 开发方式麻烦&#xff1a;继承父类、覆盖方法、配置Web.xml或注…

【Proteus仿真】51单片机+步进电机驱动

【Proteus仿真】51单片机步进电机驱动 &#x1f516;Proteus仿真基础实验-步进电机驱动&#x1f33f;Proteus8.12平台 &#x1f4cb;步进电机简介 步进电机是一种将电脉冲转换为角位移的开环控制元步进电机。一般地&#xff0c;当步进驱动器接收到脉冲信号时&#xff0c;它将根…

【工作流】Activiti工作流简介以及Spring Boot 集成 Activiti7

文章目录 前言一、activiti介绍二、工作流引擎三、BPMN四、数据库五、Spring Boot 集成 Activiti7安装插件引入依赖配置文件 总结 前言 什么是工作流&#xff1f; 工作流指通过计算机对业务流程进行自动化管理&#xff0c;实现多个参与者按照预定义的流程去自动执行业务流程。 …