Android 9.0系统源码_通知服务(二)应用发送状态栏通知的流程

news2024/9/23 19:17:43

前言

应用发送一个显示在状态栏上的通知,对于移动设备来说是很常见的一种功能需求,本篇文章我们将会结合Android9.0系统源码具体来分析一下,应用调用notificationManager触发通知栏通知功能的源码流程。

一、应用触发状态栏通知

应用可以通过调用如下notity方法可以发送一个显示在状态栏上的通知。

    /**
     * 发送通知(支持8.0+)
     */
    public void nofify() {
        // 1. Set the notification content - 创建通知基本内容
        // https://developer.android.google.cn/training/notify-user/build-notification.html#builder
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setContentTitle("My notification")
                // 这是单行
                //.setContentText("Much longer text that cannot fit one line...")
                // 这是多行
                .setStyle(new NotificationCompat.BigTextStyle()
                        .bigText("Much longer text that cannot fit one line..." +
                                "Much longer text that cannot fit one line..." +
                                "Much longer text that cannot fit one line..."))
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setAutoCancel(true);

        // 2. Create a channel and set the importance - 8.0后需要设置Channel
        // https://developer.android.google.cn/training/notify-user/build-notification.html#builder
        createNotificationChannel();
        // 3. Set the notification's tap action - 创建一些点击事件,比如点击跳转页面
        // https://developer.android.google.cn/training/notify-user/build-notification.html#click
        
        // 4. Show the notification - 展示通知
        // https://developer.android.google.cn/training/notify-user/build-notification.html#notify
        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

		// 5.调用notificationManager的notify方法
        // notificationId is a unique int for each notification that you must define
        notificationManager.notify((int) System.currentTimeMillis(), builder.build());
    }

    private void createNotificationChannel() {
        // Create the NotificationChannel, but only on API 26+ because
        // the NotificationChannel class is new and not in the support library
        CharSequence name = getString(R.string.app_name);
        String description = getString(R.string.app_name);
        int importance = NotificationManager.IMPORTANCE_HIGH;
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
        channel.setDescription(description);
        // Register the channel with the system; you can't change the importance
        // or other notification behaviors after this
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }

notify方法先是构建Notification对象,然后调用NotificationManager的notify方法发送构建的这个对象。

二、NotificationManager的相关源码

1、NotificationManager的notify方法如下所示:

frameworks/base/core/java/android/app/NotificationManager.java

public class NotificationManager {

  //如果应用发送了一个相同id的通知,并且没有被取消,它将被更新的信息所取代。
  public void notify(int id, Notification notification)
    {
        notify(null, id, notification);
    }
 
   //应用发送了一个相同tag和id的通知,并且没有被取消,它将被更新的信息所取代。   
    public void notify(String tag, int id, Notification notification)
    {
        notifyAsUser(tag, id, notification, mContext.getUser());
    }
}

2、notify方法最终都会进一步调用notifyAsUser。

public class NotificationManager {
    /**
     * @hide
     */
    public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
    {
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        // Fix the notification as best we can.
        Notification.addFieldsFromContext(mContext, notification);
        ...代码省略...
    }
}

notifyAsUser方法首先获取NotificationManagerService服务,然后调用了Notification的addFieldsFromContext方法。

3、Notification的addFieldsFromContext方法如下所示:

frameworks/base/core/java/android/app/Notification.java

public class Notification implements Parcelable
{
    public Bundle extras = new Bundle();
    /**
     * @hide
     */
    public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
    /**
     * @hide
     */
    public static void addFieldsFromContext(Context context, Notification notification) {
        addFieldsFromContext(context.getApplicationInfo(), notification);
    }
    /**
     * @hide
     */
    public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
        notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
    }
}

主要是在Notification对象中类型为Bundle的属性变量extras中保存了当前应用所对应的ApplicationInfo对象。

4、继续往下看NotificationManager的notifyAsUser方法

public class NotificationManager {
    /**
     * @hide
     */
    public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
    {
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        // Fix the notification as best we can.
        Notification.addFieldsFromContext(mContext, notification);
        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
            if (StrictMode.vmFileUriExposureEnabled()) {
                notification.sound.checkFileUriExposed("Notification.sound");
            }
        }
        //通过包名获取对应包名的应用图标设置为通知的小图标
        fixLegacySmallIcon(notification, pkg);
        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
        	//Android5.1之后,会判断notification是否有small icon,没有则抛出异常
            if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
            }
        }
    	...代码省略...
        try {
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                    copy, user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}

会调用fixLegacySmallIcon方法通,过包名获取对应包名的应用图标设置为通知的小图标,在Android5.1之后的版本中,然后会判断notification是否有small icon,如果没有设icon或small icon,用notify方法时会抛出异常,最终会调用NotificationManagerService的enqueueNotificationWithTag方法。

三、NotificationManagerService和发送通知相关的源码

1、NotificationManagerService的enqueueNotificationWithTag方法如下所示。

frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java

public class NotificationManagerService extends SystemService {
        @Override
        public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
                Notification notification, int userId) throws RemoteException {
            enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
                    Binder.getCallingPid(), tag, id, notification, userId);
        }
}

2、enqueueNotificationWithTag方法会继续调用了enqueueNotificationInternal方法,该方法发送通知的核心。

public class NotificationManagerService extends SystemService {

    void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
            final int callingPid, final String tag, final int id, final Notification notification,
            int incomingUserId) {
        if (DBG) {
            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
                    + " notification=" + notification);
        }
        checkCallerIsSystemOrSameApp(pkg);
        // 校验UID
        final int userId = ActivityManager.handleIncomingUser(callingPid,
                callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
        final UserHandle user = new UserHandle(userId);

        if (pkg == null || notification == null) {
            throw new IllegalArgumentException("null not allowed: pkg=" + pkg
                    + " id=" + id + " notification=" + notification);
        }

        // The system can post notifications for any package, let us resolve that.
        final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId);

      	...代码省略...
      	
        //上面会进行一系列验证,验证之后,会将传递进来的Notification封装成一个StatusBarNotification对象
        final StatusBarNotification n = new StatusBarNotification(
                pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                user, null, System.currentTimeMillis());
        // 封装NotificationRecord对象
        final NotificationRecord r = new NotificationRecord(getContext(), n, channel);

      	...代码省略...

        mHandler.post(new EnqueueNotificationRunnable(userId, r));
    }
}

enqueueNotificationInternal会进行一些列验证,待验证完成之后,会调用Handler的post方法开启线程,发起异步操作,触发EnqueueNotificationRunnable对象。

3、EnqueueNotificationRunnable对象如下所示。

public class NotificationManagerService extends SystemService {
    protected class EnqueueNotificationRunnable implements Runnable {
        private final NotificationRecord r;
        private final int userId;

        EnqueueNotificationRunnable(int userId, NotificationRecord r) {
            this.userId = userId;
            this.r = r;
        };

        @Override
        public void run() {
            synchronized (mNotificationLock) {
                //将当前通知相关的NotificationRecord对象放到集合中
                mEnqueuedNotifications.add(r);
                scheduleTimeoutLocked(r);

                final StatusBarNotification n = r.sbn;
                if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
                //获取是否存在相同的NotificationRecord
                NotificationRecord old = mNotificationsByKey.get(n.getKey());
                if (old != null) {
                    // Retain ranking information from previous record
                    r.copyRankingInformation(old);
                }

                final int callingUid = n.getUid();
                final int callingPid = n.getInitialPid();
                final Notification notification = n.getNotification();
                final String pkg = n.getPackageName();
                final int id = n.getId();
                final String tag = n.getTag();

                // Handle grouped notifications and bail out early if we
                // can to avoid extracting signals.
                handleGroupedNotificationLocked(r, old, callingUid, callingPid);

                // if this is a group child, unsnooze parent summary
                if (n.isGroup() && notification.isGroupChild()) {
                    mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey());
                }

                // This conditional is a dirty hack to limit the logging done on
                //     behalf of the download manager without affecting other apps.
                if (!pkg.equals("com.android.providers.downloads")
                        || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
                    int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
                    if (old != null) {
                        enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
                    }
                    EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
                            pkg, id, tag, userId, notification.toString(),
                            enqueueStatus);
                }

                mRankingHelper.extractSignals(r);

                // tell the assistant service about the notification
                if (mAssistants.isEnabled()) {
                    mAssistants.onNotificationEnqueued(r);
                    //开启延时线程
                    mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
                            DELAY_FOR_ASSISTANT_TIME);
                } else {
                    //开启线程
                    mHandler.post(new PostNotificationRunnable(r.getKey()));
                }
            }
        }
    }

}    

NotificationManagerService 的run方法会将当前NotificationRecord存放到类型为ArrayList的mEnqueuedNotifications集合中,最终会再次调用Handler的post方法开启线程,发起异步操作,触发PostNotificationRunnable对象。

4、PostNotificationRunnable对象如下所示。

public class NotificationManagerService extends SystemService {

    private NotificationListeners mListeners;
    private GroupHelper mGroupHelper;

    protected class PostNotificationRunnable implements Runnable {
        private final String key;

        PostNotificationRunnable(String key) {
            this.key = key;
        }

        @Override
        public void run() {
            synchronized (mNotificationLock) {
                try {
                    NotificationRecord r = null;
                    //从类型为ArrayList<NotificationRecord>的mEnqueuedNotifications集合中
                    //取当前key所对应的NotificationRecord对象
                    int N = mEnqueuedNotifications.size();
                    for (int i = 0; i < N; i++) {
                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                        if (Objects.equals(key, enqueued.getKey())) {
                            r = enqueued;
                            break;
                        }
                    }
                    if (r == null) {
                        Slog.i(TAG, "Cannot find enqueued record for key: " + key);
                        return;
                    }

                    r.setHidden(isPackageSuspendedLocked(r));
                    NotificationRecord old = mNotificationsByKey.get(key);
                    final StatusBarNotification n = r.sbn;
                    final Notification notification = n.getNotification();
                    // 判断是否是已经发送过此notification
                    int index = indexOfNotificationLocked(n.getKey());
                    if (index < 0) {
                        //如果是新发送的notification,就走新增流程.
                        mNotificationList.add(r);
                        mUsageStats.registerPostedByApp(r);
                        r.setInterruptive(isVisuallyInterruptive(null, r));
                    } else {
                        //如果有发送过,就获取已经存在的NtificationRecord,
                        // 后面走更新流程 mStatusBar.updateNotification(r.statusBarKey, n)
                        old = mNotificationList.get(index);
                        mNotificationList.set(index, r);
                        mUsageStats.registerUpdatedByApp(r, old);
                        // Make sure we don't lose the foreground service state.
                        notification.flags |=
                                old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
                        r.isUpdate = true;
                        r.setTextChanged(isVisuallyInterruptive(old, r));
                    }
                    //将当前NotificationRecord对象以StatusBarNotification为键存放到mNotificationsByKey中
                    mNotificationsByKey.put(n.getKey(), r);

                    // Ensure if this is a foreground service that the proper additional
                    // flags are set.
                    if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
                        notification.flags |= Notification.FLAG_ONGOING_EVENT
                                | Notification.FLAG_NO_CLEAR;
                    }

                    applyZenModeLocked(r);
                    mRankingHelper.sort(mNotificationList);

                    if (notification.getSmallIcon() != null) {
                        // 如果notification设置了smallIcon,调用所有NotificationListeners的notifyPostedLocked方法,
                        // 通知有新的notification,传入的参数为上面的NotificationRecord对象
                        StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                        //通知监听者回调方法
                        mListeners.notifyPostedLocked(r, old);
                        if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) {
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mGroupHelper.onNotificationPosted(
                                            n, hasAutoGroupSummaryLocked(n));
                                }
                            });
                        }
                    } else {
                        //移除已经存在的
                        Slog.e(TAG, "Not posting notification without small icon: " + notification);
                        if (old != null && !old.isCanceled) {
                            mListeners.notifyRemovedLocked(r,
                                    NotificationListenerService.REASON_ERROR, null);
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mGroupHelper.onNotificationRemoved(n);
                                }
                            });
                        }
                        // ATTENTION: in a future release we will bail out here
                        // so that we do not play sounds, show lights, etc. for invalid
                        // notifications
                        Slog.e(TAG, "WARNING: In a future release this will crash the app: "
                                + n.getPackageName());
                    }

                    if (!r.isHidden()) {
                        //如果不是隐藏,则触发振动/铃声/呼吸灯
                        buzzBeepBlinkLocked(r);
                    }
                    maybeRecordInterruptionLocked(r);
                } finally {
                    int N = mEnqueuedNotifications.size();
                    for (int i = 0; i < N; i++) {
                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                        if (Objects.equals(key, enqueued.getKey())) {
                            mEnqueuedNotifications.remove(i);
                            break;
                        }
                    }
                }
            }
        }
    }
}

5、如果验证通过,还会调用buzzBeepBlinkLocked方法触发震动、铃声、呼吸灯

public class NotificationManagerService extends SystemService {
    
   //触发振动/铃声/呼吸灯
   void buzzBeepBlinkLocked(NotificationRecord record) {
        boolean buzz = false;//震动
        boolean beep = false;//铃声
        boolean blink = false;//呼吸灯

        final Notification notification = record.sbn.getNotification();
        final String key = record.getKey();

        // Should this notification make noise, vibe, or use the LED?
        final boolean aboveThreshold =
                record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;

        // Remember if this notification already owns the notification channels.
        boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
        boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
        // These are set inside the conditional if the notification is allowed to make noise.
        boolean hasValidVibrate = false;
        boolean hasValidSound = false;
        boolean sentAccessibilityEvent = false;
        // If the notification will appear in the status bar, it should send an accessibility
        // event
        if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
            sendAccessibilityEvent(notification, record.sbn.getPackageName());
            sentAccessibilityEvent = true;
        }

        if (aboveThreshold && isNotificationForCurrentUser(record)) {

            if (mSystemReady && mAudioManager != null) {
                Uri soundUri = record.getSound();
                hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
                long[] vibration = record.getVibration();
                // Demote sound to vibration if vibration missing & phone in vibration mode.
                if (vibration == null
                        && hasValidSound
                        && (mAudioManager.getRingerModeInternal()
                        == AudioManager.RINGER_MODE_VIBRATE)
                        && mAudioManager.getStreamVolume(
                        AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
                    vibration = mFallbackVibrationPattern;
                }
                hasValidVibrate = vibration != null;

                boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
                if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
                    if (!sentAccessibilityEvent) {
                        sendAccessibilityEvent(notification, record.sbn.getPackageName());
                        sentAccessibilityEvent = true;
                    }
                    if (DBG) Slog.v(TAG, "Interrupting!");
                    if (hasValidSound) {
                        mSoundNotificationKey = key;
                        if (mInCall) {
                            playInCallNotification();
                            beep = true;
                        } else {
                            //播放铃声
                            beep = playSound(record, soundUri);
                        }
                    }

                    final boolean ringerModeSilent =
                            mAudioManager.getRingerModeInternal()
                                    == AudioManager.RINGER_MODE_SILENT;
                    if (!mInCall && hasValidVibrate && !ringerModeSilent) {
                        mVibrateNotificationKey = key;
                        //震动
                        buzz = playVibration(record, vibration, hasValidSound);
                    }
                }
            }
        }
        // If a notification is updated to remove the actively playing sound or vibrate,
        // cancel that feedback now
        if (wasBeep && !hasValidSound) {
            clearSoundLocked();
        }
        if (wasBuzz && !hasValidVibrate) {
            clearVibrateLocked();
        }

        // light
        // release the light
        // 呼吸灯
        boolean wasShowLights = mLights.remove(key);
        if (record.getLight() != null && aboveThreshold
                && ((record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_LIGHTS) == 0)) {
            mLights.add(key);
            updateLightsLocked();
            if (mUseAttentionLight) {
                mAttentionLight.pulse();
            }
            blink = true;
        } else if (wasShowLights) {
            updateLightsLocked();
        }
        //应用请求了振动/铃声/呼吸灯,输出notification_alert日志
        if (buzz || beep || blink) {
            record.setInterruptive(true);
            MetricsLogger.action(record.getLogMaker()
                    .setCategory(MetricsEvent.NOTIFICATION_ALERT)
                    .setType(MetricsEvent.TYPE_OPEN)
                    .setSubtype((buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)));
            EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
        }
    }
}

四、总结

在这里插入图片描述

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

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

相关文章

关于HDFS

目录 一、HDFS概述 二、HDFS架构与工作机制 三、HDFS的Shell操作 四、Hdfs的API操作 一、HDFS概述 HDFS&#xff1a;Hadoop Distributed File System&#xff1b;一种分布式文件管理系统&#xff0c;通过目录树定位文件。使用场景&#xff1a;一次写入&#xff0c;多次读出…

java 自定义注解

文章目录前言Annotation包自定义注解自定义注解示例参考文章&#xff1a;java 自定义注解 用处_java注解和自定义注解的简单使用参考文章&#xff1a;java中自定义注解的作用和写法前言 在使用Spring Boot的时候&#xff0c;大量使用注解的语法去替代XML配置文件&#xff0c;十…

SpringAMQP消息队列(SpringBoot集成RabbitMQ)

一、初始配置1、导入maven坐标<!--rabbitmq--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>2、yml配置spring:rabbitmq:host: 你的rabbitmq的ipport: …

4G模块DTU网关远程抄表方案(三):水表188协议

4G模块DTU网关远程抄表方案&#xff08;三&#xff09;&#xff1a;水气电表188协议 1 CTJ 188协议简介 CJ/T188协议规定了户用计量仪表(以下简称仪表)&#xff0c;包括水表、燃气表、热量表等仪表数据传输的基本原则&#xff0c;接口形式及物理性能、数据链路、数据标识及数…

目标检测回归损失函数简介:SmoothL1/IoU/GIoU/DIoU/CIoU Loss

目标检测 回归损失函数1、Smooth L1 Loss2、 IoU Loss3、 GIoU Loss &#xff08;Generalized-IoU Loss&#xff09;4、 DIoU Loss &#xff08;Distance-IoU Loss&#xff09;5、 CIoU Loss &#xff08;Complete-IoU Loss&#xff09;总结&#xff1a;目标检测任务的损失函数…

【计算机网络】数据链路层(下)

文章目录媒体接入控制媒体接入控制-静态划分信道随机接入 CSMACD协议随机接入 CSMACA协议MAC地址MAC地址作用MAC地址格式MAC地址种类MAC地址的发送顺序单播MAC地址广播MAC地址多播MAC地址随机MAC地址IP地址区分网络编号IP地址与MAC地址的封装位置转发过程中IP地址与MAC地址的变…

1.1 硬件与micropython固件烧录及自编译固件

1.ESP32硬件和固件 淘宝搜ESP32模块,20-50元都有,自带usb口,即插即用. 固件下载地址:MicroPython - Python for microcontrollers 2.烧录方法 为简化入门难度,建议此处先使用带GUI的开发工具THonny,记得不是给你理发的tony老师. 烧录的入口是: 后期通过脚本一次型生成和烧…

[软件工程导论(第六版)]第3章 需求分析(课后习题详解)

文章目录1. 为什么要进行需求分析&#xff1f;通常对软件系统有哪些需求&#xff1f;2. 怎样与用户有效地沟通以获取用户的真实需求&#xff1f;3. 银行计算机储蓄系统的工作过程大致如下&#xff1a;储户填写的存款单或取款单由业务员输入系统&#xff0c;如果是存款则系统记录…

C语言经典编程题100例(81~100)

目录81、习题7-7 字符串替换82、习题8-10 输出学生成绩83、习题8-2 在数组中查找指定元素84、习题8-3 数组循环右移85、题8-9 分类统计各类字符个数86、习题9-2 计算两个复数之积87、习题9-6 按等级统计学生成绩88、习题11-1 输出月份英文名89、习题11-2 查找星期90、练习10-1 …

分享113个HTML娱乐休闲模板,总有一款适合您

分享113个HTML娱乐休闲模板&#xff0c;总有一款适合您 113个HTML娱乐休闲模板下载链接&#xff1a;https://pan.baidu.com/s/1aWYO2j2pSTjyqlQPHa0-Jw?pwdbium 提取码&#xff1a;bium Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 海上的沤鸟HTML网页模板…

(三十六)Vue解决Ajax跨域问题

文章目录环境准备vue的跨域问题vue跨域问题解决方案方式一方式二上一篇&#xff1a;&#xff08;三十五&#xff09;Vue之过渡与动画 环境准备 首先我们要借助axios发送Ajax&#xff0c;axios安装命令&#xff1a;npm i axios 其次准备两台服务器&#xff0c;这里使用node.j…

Linux | 网络通信 | 序列化和反序列化的讲解与实现

文章目录为什么要序列化&#xff1f;协议的实现服务端与客户端代码实现为什么要序列化&#xff1f; 由于默认对齐数的不同&#xff0c;不同的平台对相同数据进行内存对齐后&#xff0c;可能得到不同的数据。如果直接将这些数据进行网络传输&#xff0c;对方很可能无法正确的获…

【数据结构】单链表的接口实现(附图解和源码)

单链表的接口实现&#xff08;附图解和源码&#xff09; 文章目录单链表的接口实现&#xff08;附图解和源码&#xff09;前言一、定义结构体二、接口实现&#xff08;附图解源码&#xff09;1.开辟新空间2.头插数据3.头删数据4.打印整个单链表5.尾删数据6.查找单链表中的数据7…

Linux 磁盘挂载

目录 Linux硬盘分区 硬盘设备的文件名 /dev/sd[a-z] 硬盘分区 识别硬盘的文件名 Linux文件系统 文件系统类型 Linux如何保存文件 VFS虚拟文件系统 磁盘挂载命令 lsblk 查看系统的磁盘使用情况 fdisk 硬盘分区 mkfs 格式化文件系统 mount 挂载命令 df 显示磁盘空间…

Java中的链表实现介绍

Java中的链表实现介绍 学习数据结构的的链表和树时&#xff0c;会遇到节点&#xff08;node&#xff09;和链表&#xff08;linked list&#xff09;这两个术语&#xff0c;节点是处理数据结构的链表和树的基础。节点是一种数据元素&#xff0c;包括两个部分&#xff1a;一个是…

pytest总结

这里写目录标题一、pytest的命名规则二、界面化配置符合命名规则的方法前面会有运行标记三、pytest的用例结构三部分组成四、pytest的用例断言断言写法&#xff1a;五、pytest测试框架结构六、pytest参数化用例1、pytest参数化实现方式2、单参数&#xff1a;每一条测试数据都会…

第五十七章 树状数组(二)

第五十七章 树状数组&#xff08;二&#xff09;一、差分的缺陷二、树状数组与差分三、例题题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示样例 1 解释&#xff1a;数据规模与约定代码一、差分的缺陷 差分的作用是能够在O(1)的时间内给一段区间加上相同的数字&am…

【计算机网络】数据链路层(上)

文章目录数据链路层概述封装成帧透明传输差错检测奇偶校验循环冗余校验CRC可靠传输可靠传输基本概念实现机制 — 停止-等待协议实现机制 — 回退N帧协议实现机制 — 选择重传协议点对点协议PPP数据链路层概述 首先我蛮来看看数据链路层在网络体系结构中的地位。如图所示主机h1…

key的作用原理与列表的遍历、追加、搜索、排序

目录 一、key的作用原理 二、实现列表遍历并对在列表最前方进行追加元素 三、实现列表过滤搜索 1、用computed计算属性来实现 2、用watch监听输入值的变化来实现 四、按年龄排序输出列表 一、key的作用原理 1. 虚拟DOM中key的作用&#xff1a; key是虚拟DOM对象的标识&a…

博彩公司 BetMGM 发生数据泄露,“赌徒”面临网络风险

Bleeping Computer 网站披露&#xff0c;著名体育博彩公司 BetMGM 发生一起数据泄露事件&#xff0c;一名威胁攻击者成功窃取其大量用户个人信息。 据悉&#xff0c;BetMGM 数据泄漏事件中&#xff0c;攻击者盗取了包括用户姓名、联系信息&#xff08;如邮政地址、电子邮件地址…