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

news2024/11/15 1:25:19

前言

应用发送一个显示在状态栏上的通知,对于移动设备来说是很常见的一种功能需求,本篇文章我们将会结合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/377804.html

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

相关文章

专题:一看就会的C++类模板讲解 (1)

目录 一.类模板的作用 二.类模板的定义&#xff1a; 三.类模板的声明格式&#xff1a; 四.类模板对象 五.再举一个例子 一.类模板的作用 面向对象的程序设计编程实践中&#xff0c;我们可能会面临这样的问题&#xff1a;要实现比较两个数的大小。明明比较两个数的方法都一样…

小米mix2s刷win11和android双系统

在给电脑安装系统的过程中&#xff0c;可能会因为各种原因出现windows无法安装的情况&#xff0c;我在给小米mix2s安装win11时发现出现了“计算机意外地重新启动或遇到错误&#xff0c;windows无法安装”的情况&#xff0c;下面就来教一下大家两种解决方法&#xff0c;希望可以…

进行新类检测以及概念漂移适应的半监督分类框架SACCOS论文阅读笔记

介绍 本文主要想解决基于聚类的概念漂移检测方法中常做出的强假设问题&#xff0c;即假设同类比较接近&#xff0c;异类比较远离&#xff0c;并且还假设新类别出现时通常会连续大量出现。 针对这些问题&#xff0c;本文提出了一种基于数据流的半监督自适应分类框架SACCOS&…

程序员看过都说好的资源网站,看看你都用过哪些?

程序员必备的相关资源网站一.图片专区1.表情包&#xff08;1&#xff09;发表情&#xff08;2&#xff09;逗比拯救世界&#xff08;3&#xff09;搞怪图片生成&#xff08;4&#xff09;哇咔工具2.图标库&#xff08;1&#xff09;Font Awesome&#xff08;2&#xff09;iconf…

基于vscode开发vue项目的详细步骤教程 2 第三方图标库FontAwesome

1、Vue下载安装步骤的详细教程(亲测有效) 1_水w的博客-CSDN博客 2、Vue下载安装步骤的详细教程(亲测有效) 2 安装与创建默认项目_水w的博客-CSDN博客 3、基于vscode开发vue项目的详细步骤教程_水w的博客-CSDN博客 目录 六、第三方图标库FontAwesome 1 安装FontAwesome 解决报…

vulnhub靶场NAPPING: 1.0.1教程

靶场搭建靶机下载地址&#xff1a;Napping: 1.0.1 ~ VulnHub直接解压双击ova文件即可使用软件&#xff1a;靶机VirtualBox&#xff0c;攻击机VMware攻击机&#xff1a;kali信息收集arp-scan -l上帝之眼直接来看看网站可以注册账号&#xff0c;那就先试试。注册完后登入哦。要输…

阿里面试之Hr面,这个套路把我坑惨了......

作为技术类的测试工程师面试&#xff0c;往往要经过多次面试才能拿到心仪的offer&#xff0c;这里面有技术一面、二面…&#xff0c;甚至总监面等&#xff0c;还有一个必不可少的就是HR面&#xff0c;一般HR会出现在你面试的最前面和最后面&#xff0c;前面是了解你的基本情况&…

NCRE计算机等级考试Python真题(六)

第六套试题1、算法的时间复杂度是指A.执行算法程序所需要的时间B.算法程序的长度C.算法程序中的指令条数D.算法执行过程中所需要的基本运算次数正确答案&#xff1a; D2、下列关于栈的叙述中正确的是A.在栈中只能插入数据B.在栈中只能删除数据C.栈是先进先出的线性表D.栈是先进…

JavaEE简单示例——MyBatis多对多关联映射

简单介绍&#xff1a; 在实际的开发中&#xff0c;多对多的查询也是非常常见的。比如一个订单可以有很多的商品&#xff0c;而一个商品也可以被很多的订单所包含&#xff0c;而这种时候&#xff0c;如果我们有需求查询某一个订单内包含的所有商品&#xff1b;或者查询购买这个…

本地启动nacos注册服务

1.下载启动nacos(我的路径2.D:\nacos-server-2.0.0\nacos\bin) 2.单点模式启动 startup.cmd -m standalone 3.打开本地服务mysql、redis 4.配置nacos Nacos <1>创建命名空间&#xff0c;名称和项目pom一致 <2>ncaos导入配置或新建配置 <3>修改配置&#x…

Java培训:深入解读函数式接口

函数式编程是一种编程规范或一种编程思想&#xff0c;简单可以理解问将运算或实现过程看做是函数的计算。 Java8为了实现函数式编程&#xff0c;提出了3个重要的概念&#xff1a;Lambda表达式、方法引用、函数式接口。现在很多公司都在使用lambda表达式进行代码编写&#xff0c…

花7000报了培训班,3个月后我成功“骗”进了阿里,月薪拿16K....

“月薪4000元不如报名学IT&#xff0c;挑战年薪百万”这是大多数培训班在互联网上宣传的口号&#xff0c;简单的16个字却戳中了很多人的痛点&#xff0c;同龄人买车买房&#xff0c;自己却拿着微薄的工资连好一点的房子都租不起&#xff0c;这句口号 彻底激起了底层员工的焦虑&…

Spring上下文生命周期

基于入口来分析 import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration;Configuration ComponentScan public cl…

飞行机器人专栏(十)-- 异构多视角视觉系统

感知系统架构为满足天空端主控制器的诸如RGB-D图像处理等大容量数据吞吐、高速并行计算、实时运动控制以及通信和可视化任务的计算算力需求&#xff0c;同时优化功耗表现&#xff0c;采用了结构紧凑、功耗表现优异的边缘计算硬件NVIDA IJetson AGXOrin 。该开发者套件包含高性能…

Python学习笔记202302

1、numpy.empty 作用&#xff1a;根据给定的维度和数值类型返回一个新的数组&#xff0c;其元素不进行初始化。 用法&#xff1a;numpy.empty(shape, dtypefloat, order‘C’) 2、logging.debug 作用&#xff1a;Python 的日志记录工具&#xff0c;这个模块为应用与库实现了灵…

可用于标记蛋白质216699-36-4,6-ROX,SE,6-羧基-X-罗丹明琥珀酰亚胺酯

一.6-ROX&#xff0c;SE产品描述&#xff1a;6-羧基-X-罗丹明琥珀酰亚胺酯&#xff08;6-ROX&#xff0c;SE&#xff09;是一种用于寡核苷酸标记和自动DNA测序的荧光染料&#xff0c;可用于标记蛋白质&#xff0c;寡核苷酸和其他含胺分子的伯胺&#xff08;-NH2&#xff09;。西…

Git中遇到的问题

文章目录Clone克隆bytes of body are still expectedunable to access此文章是自己在Git操作时遇到的问题&#xff0c;先开个坑记录下来&#xff0c;如果以后还遇到了Git上的其他问题&#xff0c;会在后面进行添加。Clone克隆 bytes of body are still expected Clone失败信息…

【Redis】Java客户端JedisSpringDataRedis入门(三)

&#x1f697;Redis学习第三站~ &#x1f6a9;起始站&#xff1a;【Redis】概述&环境搭建(一) &#x1f6a9;本文已收录至专栏&#xff1a;数据库学习之旅 &#x1f44d;希望您能有所收获 在上一篇中我们学习了Redis常见命令的使用&#xff0c;显然&#xff0c;我们不可能一…

Day21【元宇宙的实践构想07】—— 元宇宙与人工智能

&#x1f483;&#x1f3fc; 本人简介&#xff1a;男 &#x1f476;&#x1f3fc; 年龄&#xff1a;18 &#x1f91e; 作者&#xff1a;那就叫我亮亮叭 &#x1f4d5; 专栏&#xff1a;元宇宙 0.0 写在前面 “元宇宙”在2021年成为时髦的概念。元宇宙到底是什么&#xff1f;元宇…

[架构之路-123]-《软考-系统架构设计师》-操作系统-2-操作系统原理 - 存储层次结构与存储管理(寄存器、Cache、MMU、内存、外存、页表)

前言&#xff1a;操作系统的本质就是创建一个并发的应用程序执行的环境&#xff0c;使得各种应用程序可以动态、共享相同的计算机物理硬件资源&#xff0c;计算机的三大物理资源包括&#xff1a;CPU内存外设应用程序&#xff08;管理应用程序&#xff09;&#xff1a;以进程和现…