后台Service限制

news2024/11/17 5:46:16

每次在后台运行时,应用都会消耗一部分有限的设备资源,例如 RAM。 这可能会影响用户体验,如果用户正在使用占用大量资源的应用(例如玩游戏或观看视频),影响会尤为明显。 为了提升用户体验,Android 8.0(API 级别 26)对应用在后台运行时可以执行的操作施加了限制。
应用在两个方面受到限制:

  • 后台 Service 限制:处于空闲状态时,应用可以使用的后台 Service 存在限制。 这些限制不适用于前台 Service,因为前台 Service 更容易引起用户注意。
  • 广播限制:除了有限的例外情况,应用无法使用清单注册隐式广播。 它们仍然可以在运行时注册这些广播,并且可以使用清单注册专门针对它们的显式广播。

在 Android 8.0 之前,创建前台 Service 的方式通常是先创建一个后台 Service,然后将该 Service 推到前台。 Android 8.0 有一项复杂功能:系统不允许后台应用创建后台 Service。 因此,Android 8.0 引入了一种全新的方法,即 startForegroundService(),以在前台启动新 Service。 在系统创建 Service 后,应用有五秒的时间来调用该 Service 的 startForeground() 方法以显示新 Service 的用户可见通知。 如果应用在此时间限制内_未_调用 startForeground(),则系统将停止此 Service 并声明此应用为 ANR。

本文总结:

  • App处于后台且不在临时白名单中1min后会:
    • 限制运行后台Service
    • 限制启动Service(受限app同时限制启动FGS)
  • 临时白名单常见添加场景:
    • 短信/彩信
    • 通知

前台判断

如果满足以下任意条件,应用将被视为处于前台:

  • 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
  • 具有前台 Service。
  • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个 Service,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的 Service,那么该应用处于前台:
    • IME
    • 壁纸 Service
    • 通知侦听器
    • 语音或文本 Service

临时白名单

在这些情况下,后台应用将被置于一个临时白名单中并持续数分钟。 位于白名单中时,应用可以无限制地启动 Service,并且其后台 Service 也可以运行。 处理对用户可见的任务时,应用将被置于白名单中,例如:

  • 处理一条高优先级 Firebase 云消息传递 (FCM)消息。
  • 接收广播,例如短信/彩信消息。
  • 从通知执行 PendingIntent。
  • 在 VPN 应用将自己提升为前台进程前开启 VpnService。

出于特定原因在短时间内将应用程序添加到临时允许列表。 临时允许列表与永久允许列表分开保存,应用程序会在预定时间后自动从临时允许列表中删除。

// 最大临时白名单豁免时间为5min
private static final long DEFAULT_MAX_TEMP_APP_ALLOWLIST_DURATION_MS = 5 * 60 * 1000L;
// 彩信60s
private static final long DEFAULT_MMS_TEMP_APP_ALLOWLIST_DURATION_MS = 60 * 1000L;
// 短信 20s
private static final long DEFAULT_SMS_TEMP_APP_ALLOWLIST_DURATION_MS = 20 * 1000L;
// 通知30s
private static final long DEFAULT_NOTIFICATION_ALLOWLIST_DURATION_MS = 30 * 1000L;
// 由于高优先级消息而暂时允许逃避后台检查的一组应用程序 ID,短信/彩信
@CompositeRWLock({"this", "mProcLock"})
int[] mDeviceIdleTempAllowlist = new int[0];
// 暂时绕过省电模式的白名单,通知
@CompositeRWLock({"this", "mProcLock"})
final PendingTempAllowlists mPendingTempAllowlist = new PendingTempAllowlists(this);

短信/彩信

        @Override
        public void updateDeviceIdleTempAllowlist(@Nullable int[] appids, int changingUid,
                boolean adding, long durationMs, @TempAllowListType int type,
                @ReasonCode int reasonCode, @Nullable String reason, int callingUid) {
            synchronized (ActivityManagerService.this) {
                synchronized (mProcLock) {
                    if (appids != null) {
                        // 更新临时白名单
                        mDeviceIdleTempAllowlist = appids;
                    }
                    if (adding) {
                        if (type == TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
                            // Note, the device idle temp-allowlist are by app-ids, but here
                            // mFgsStartTempAllowList contains UIDs.
                            mFgsStartTempAllowList.add(changingUid, durationMs,
                                    new FgsTempAllowListItem(durationMs, reasonCode, reason,
                                    callingUid));
                        }
                    } else {
                        mFgsStartTempAllowList.removeUid(changingUid);
                    }
                    setAppIdTempAllowlistStateLSP(changingUid, adding);
                }
            }
        }
    @GuardedBy({"mService", "mProcLock"})
    void setAppIdTempAllowlistStateLSP(int uid, boolean onAllowlist) {
        boolean changed = false;
        for (int i = mActiveUids.size() - 1; i >= 0; i--) {
            final UidRecord uidRec = mActiveUids.valueAt(i);
            if (uidRec.getUid() == uid && uidRec.isCurAllowListed() != onAllowlist) {
                // 设置当前uid的curAllowListed,如果是加入白名单,这里为true
                // 可用作判断当前uid的状态是否为idle
                uidRec.setCurAllowListed(onAllowlist);
                changed = true;
            }
        }
        if (changed) {
            updateOomAdjLSP(OOM_ADJ_REASON_ALLOWLIST);
        }
    }

主要是调用power相关的接口设置,具体设置流程如下时序图
在这里插入图片描述

通知

   // 这里的reason为NotificationManagerService
    // reasonCode为REASON_NOTIFICATION_SERVICE
    // type为TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED
    // duration为30s
    @GuardedBy("this")
    void tempAllowlistUidLocked(int targetUid, long duration, @ReasonCode int reasonCode,
            String reason, @TempAllowListType int type, int callingUid) {
        synchronized (mProcLock) {
            // The temp allowlist type could change according to the reasonCode.
            if (mLocalDeviceIdleController != null) {
                type = mLocalDeviceIdleController.getTempAllowListType(reasonCode, type);
            }
            if (type == TEMPORARY_ALLOW_LIST_TYPE_NONE) {
                return;
            }
            // 添加到临时白名单中
            mPendingTempAllowlist.put(targetUid,
                    new PendingTempAllowlist(targetUid, duration, reasonCode, reason, type,
                            callingUid));
            setUidTempAllowlistStateLSP(targetUid, true);
            mUiHandler.obtainMessage(PUSH_TEMP_ALLOWLIST_UI_MSG).sendToTarget();

            if (type == TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
                mFgsStartTempAllowList.add(targetUid, duration,
                        new FgsTempAllowListItem(duration, reasonCode, reason, callingUid));
            }
        }
    }
        // temporarily allow apps to perform extra work when their pending intents are launched
        if (notification.allPendingIntents != null) {
            final int intentCount = notification.allPendingIntents.size();
            if (intentCount > 0) {
                // 30s
                final long duration = LocalServices.getService(
                        DeviceIdleInternal.class).getNotificationAllowlistDuration();
                for (int i = 0; i < intentCount; i++) {
                    PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
                    if (pendingIntent != null) {
                        // 设置duration
                        mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(),
                                ALLOWLIST_TOKEN, duration,
                                TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
                                REASON_NOTIFICATION_SERVICE,
                                "NotificationManagerService");
                    }
                }
            }
        }
    // allowlistToken为target
    // 这里的reason为NotificationManagerService
    // reasonCode为REASON_NOTIFICATION_SERVICE
    // type为TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED
    // duration为30s
	void setAllowlistDurationLocked(IBinder allowlistToken, long duration, int type,
            @ReasonCode int reasonCode, @Nullable String reason) { 
        if (duration > 0) {
            if (mAllowlistDuration == null) {
                mAllowlistDuration = new ArrayMap<>();
            }
            mAllowlistDuration.put(allowlistToken,
                    new TempAllowListDuration(duration, type, reasonCode, reason));
        } else if (mAllowlistDuration != null) {
            mAllowlistDuration.remove(allowlistToken);
            if (mAllowlistDuration.size() <= 0) {
                mAllowlistDuration = null;
            }
        }
        this.stringName = null;
    }

整个设置duration的流程如下时序图
在这里插入图片描述

PendingIntent发送时会通过校验,通知AMS设置临时白名单

    public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken,
            IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo,
            String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) {
        .......
        synchronized (controller.mLock) {
           ......
            // 如果在通知入队时已经设置了duration
            if (mAllowlistDuration != null) {
                duration = mAllowlistDuration.get(allowlistToken);
            }
            .......
        }
       ......
        try {
            // 调用AMS接口tempAllowlistForPendingIntent设置临时白名单
            if (duration != null) {
                StringBuilder tag = new StringBuilder(64);
                tag.append("setPendingIntentAllowlistDuration,reason:");
                tag.append(duration.reason == null ? "" : duration.reason);
                tag.append(",pendingintent:");
                UserHandle.formatUid(tag, callingUid);
                tag.append(":");
                if (finalIntent.getAction() != null) {
                    tag.append(finalIntent.getAction());
                } else if (finalIntent.getComponent() != null) {
                    finalIntent.getComponent().appendShortString(tag);
                } else if (finalIntent.getData() != null) {
                    tag.append(finalIntent.getData().toSafeString());
                }
                controller.mAmInternal.tempAllowlistForPendingIntent(callingPid, callingUid,
                        uid, duration.duration, duration.type, duration.reasonCode, tag.toString());
            } else if (key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE
                    && options != null) {
                // If this is a getForegroundService() type pending intent, use its BroadcastOptions
                // temp allowlist duration as its pending intent temp allowlist duration.
                BroadcastOptions brOptions = new BroadcastOptions(options);
                if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
                    controller.mAmInternal.tempAllowlistForPendingIntent(callingPid, callingUid,
                            uid, brOptions.getTemporaryAppAllowlistDuration(),
                            brOptions.getTemporaryAppAllowlistType(),
                            brOptions.getTemporaryAppAllowlistReasonCode(),
                            brOptions.getTemporaryAppAllowlistReason());
                }
            }

          ....
    }

设置白名单的流程如下时序图
在这里插入图片描述

Uid空闲状态

如果 UID 现在在后台(不在临时白名单上), 它之前是在前台(或在临时白名单上);那么1min后当前UID将会处于idle状态。

// procState >= PROCESS_STATE_TRANSIENT_BACKGROUND 8
if (ActivityManager.isProcStateBackground(uidRec.getCurProcState())
        && !uidRec.isCurAllowListed()) {
    // UID 现在在后台(不在临时许可名单上)。 它之前是否在前台(或在临时许可名单上)?
    if (!ActivityManager.isProcStateBackground(uidRec.getSetProcState())
        || uidRec.isSetAllowListed()) {
        // 这里传入的elapsedRealtime而不是uptimeMillis,谷歌代码曾出过问题,
        // 具体见:http://gerrit.pt.mioffice.cn/c/platform/frameworks/base/+/1633705
        uidRec.setLastBackgroundTime(nowElapsed);
        if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
            // post 1min后的消息到handler中去执行idle操作
            mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
                    mConstants.BACKGROUND_SETTLE_TIME);
        }
    }
    if (uidRec.isIdle() && !uidRec.isSetIdle()) {
        uidChange |= UidRecord.CHANGE_IDLE;
        becameIdle.add(uidRec);
    }

查找最近不活动的应用程序,并在宽限期后将它们标记为空闲。 如果空闲,停止任何后台服务并通知听众。

  • idle处理逻辑是在system server的main线程中进行的
  • idle时间是在处于后台或不在临时白名单的1min后
  • 遍历active uid时,时间到了就执行doStopUidLocked
  • 时间未到计算更新为最近的一次idle时间并再次发送IDLE_UIDS_MSG消息
    @GuardedBy("mService")
    void idleUidsLocked() {
        ......
        final long nowElapsed = SystemClock.elapsedRealtime();
        final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
        ......
    	// 1min后执行消息
        for (int i = N - 1; i >= 0; i--) {
            final UidRecord uidRec = mActiveUids.valueAt(i);
            final long bgTime = uidRec.getLastBackgroundTime();
            // 距离设置后台时间超过1min且当前uid不是idle则设置idle
            if (bgTime > 0 && !uidRec.isIdle()) {
                if (bgTime <= maxBgTime) {
                    EventLogTags.writeAmUidIdle(uidRec.getUid());
                    synchronized (mProcLock) {
                        uidRec.setIdle(true);
                        uidRec.setSetIdle(true);
                    }
                    // 停止当前uid下的后台Services
                    mService.doStopUidLocked(uidRec.getUid(), uidRec);
                } .......
            }
        }

后台Service限制

处于前台时,应用可以自由创建和运行前台与后台 Service。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用 Service。 在该时间窗结束后,应用将被视为处于_空闲_状态。 此时,系统将停止应用的后台 Service,就像应用已经调用 Service 的 Service.stopSelf() 方法一样。

停止后台运行Service

因为当前uid进入后台1min后处于idle状态,停止与此 uid 关联的所有服务;所以我们会经常在bugreport中看到类似如下log。

08-27 20:08:12.038 1588 5729 I am_uid_active: 10135
08-27 20:09:13.517 1588 2114 I am_uid_idle: 10135
08-27 20:09:13.517 1588 2114 I am_stop_idle_service: [10135,com.android.htmlviewer/com.android.settings.services.MemoryOptimizationService]

void stopInBackgroundLocked(int uid) {
    ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
    ArrayList<ServiceRecord> stopping = null;
    if (services != null) {
        for (int i = services.mServicesByInstanceName.size() - 1; i >= 0; i--) {
            ServiceRecord service = services.mServicesByInstanceName.valueAt(i);
            if (service.appInfo.uid == uid && service.startRequested) {
                if (mAm.getAppStartModeLOSP(service.appInfo.uid, service.packageName,
                        service.appInfo.targetSdkVersion, -1, false, false, false)
                        service.mRecentCallingPackage)
                        != ActivityManager.APP_START_MODE_NORMAL) {
                    if (stopping == null) {
                        stopping = new ArrayList<>();
                    }
                    String compName = service.shortInstanceName;
                    EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
                    StringBuilder sb = new StringBuilder(64);
                    sb.append("Stopping service due to app idle: ");
                    UserHandle.formatUid(sb, service.appInfo.uid);
                    sb.append(" ");
                    TimeUtils.formatDuration(service.createRealTime
                            - SystemClock.elapsedRealtime(), sb);
                    sb.append(" ");
                    sb.append(compName);
                    Slog.w(TAG, sb.toString());
                    stopping.add(service);
                	// 如果应用程序受到后台限制,还要确保取消任何通知
                    if (appRestrictedAnyInBackground(
                            service.appInfo.uid, service.packageName)) {
                        cancelForegroundNotificationLocked(service);
                    }
                }
            }
        }
        if (stopping != null) {
            final int size = stopping.size();
            for (int i = size - 1; i >= 0; i--) {
                ServiceRecord service = stopping.get(i);
                service.delayed = false;
                services.ensureNotStartingBackgroundLocked(service);
                stopServiceLocked(service, true);
            }
            if (size > 0) {
                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
            }
        }
    }
}

注意:stopInBackgroundLocked回调后,service不一定会被stop

    private final void bringDownServiceIfNeededLocked(ServiceRecord r, boolean knowConn,
            boolean hasConn) {

        if (isServiceNeededLocked(r, knowConn, hasConn)) {
            return;
        }

        // 如果有新拉起service的需求,本次不会stop该service
        if (mPendingServices.contains(r)) {
            return;
        }

        bringDownServiceLocked(r);
    }

限制后台启动Service

如果Service是间接启动的(例如从 PendingIntent),弄清楚是否正在后台状态下启动一个应用程序。
当Service所在App处于后台(uid为idle)时,会限制启动Service,限制分两种情况:

  1. App处于后台受限模式下,限制启动任何Service(包括FGS);

ActivityManager: Background start not allowed: service Intent { act=geofence_trigered cmp=com.xiaomi.smarthome/.scene.activity.GeoActionService (has extras) } to com.xiaomi.smarthome/.scene.activity.GeoActionService from pid=9233 uid=10270 pkg=com.xiaomi.smarthome startFg?=true

  1. App处于无限制模式下,仅限制启动后台Service;

无限制下android.app.AppOpsManager#OP_RUN_ANY_IN_BACKGROUND 为ALLOWED

09-13 10:30:40.633 1581 2603 W ActivityManager: Background start not allowed: service Intent { cmp=com.android.deskclock/.addition.resource.ResourceLoadService (has extras) } to com.android.deskclock/.addition.resource.ResourceLoadService from pid=14625 uid=10216 pkg=com.android.deskclock startFg?=false

// 当前uid不处于空闲状态
final boolean bgLaunch = !mAm.isUidActiveLOSP(r.appInfo.uid);
boolean forcedStandby = false;
// 如果应用程序有严格的后台限制,我们将任何 bg 服务启动类似于旧版应用程序强制限制情况,
// 无论其目标 SDK 版本如何。
if (bgLaunch && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
    if (DEBUG_FOREGROUND_SERVICE) {
        Slog.d(TAG, "Forcing bg-only service start only for " + r.shortInstanceName
                + " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
    }
    forcedStandby = true;
}
    	.......
if (forcedStandby || (!r.startRequested && !fgRequired)) {
    // 在继续之前——如果这个应用程序不允许在后台启动服务,那么在这一点上我们不会让它运行。    
    final int allowed = mAm.getAppStartModeLOSP(r.appInfo.uid, r.packageName,
            r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
    if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
        Slog.w(TAG, "Background start not allowed: service "
                + service + " to " + r.shortInstanceName
                + " from pid=" + callingPid + " uid=" + callingUid
                + " pkg=" + callingPackage + " startFg?=" + fgRequired);

查看是否允许启动

    @GuardedBy(anyOf = {"this", "mProcLock"})
    int appServicesRestrictedInBackgroundLOSP(int uid, String packageName, int packageTargetSdk) {
        ......
        // Is this app on the battery whitelist?
        if (isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ false)) {
            if (DEBUG_BACKGROUND_CHECK) {
                Slog.i(TAG, "App " + uid + "/" + packageName
                        + " on idle allowlist; not restricted in background");
            }
            return ActivityManager.APP_START_MODE_NORMAL;
        }
        ......
    }
    /**
     * @return whether a UID is in the system, user or temp doze allowlist.
     */
    @GuardedBy(anyOf = {"this", "mProcLock"})
    boolean isOnDeviceIdleAllowlistLOSP(int uid, boolean allowExceptIdleToo) {
        final int appId = UserHandle.getAppId(uid);

        final int[] allowlist = allowExceptIdleToo
                ? mDeviceIdleExceptIdleAllowlist
                : mDeviceIdleAllowlist;

        return Arrays.binarySearch(allowlist, appId) >= 0
                // 临时白名单 for 短信/彩信等
                || Arrays.binarySearch(mDeviceIdleTempAllowlist, appId) >= 0
                // 临时白名单 for 通知等
                || mPendingTempAllowlist.get(uid) != null;
    }

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

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

相关文章

ESP8266-C开发-3串口下载固件失败方案

一、串口下载固件失败 以下是报错信息。 …………………………[2022-12-23 09:01:17,338][ESP8266Loader_spi[1]][espDownloader.pyo][line:351][ERROR]: Chip sync error: Failed to connect to ESP8266: Timed out waiting for packet header [2022-12-23 09:01:17,338][ESP…

Zookeeper 4 Zookeeper JavaAPI 操作 4.1 Curator 介绍 4.2 Curator API 常用操作【建立连接】

Zookeeper 【黑马程序员Zookeeper视频教程&#xff0c;快速入门zookeeper技术】 文章目录Zookeeper4 Zookeeper JavaAPI 操作4.1 Curator 介绍4.1.1 Curator 介绍4.2 Curator API 常用操作4.2.1 Curator API 常用操作4.2.2 建立连接4 Zookeeper JavaAPI 操作 4.1 Curator 介绍…

使用形态学处理相关算法对芯片电路图焊接锡点缺陷进行检测

up目录 一、理论基础 二、核心程序 三、测试结果 一、理论基础 当前我国信息科技的发展&#xff0c; 产业界自动化生产水平越来越高。 具体在电子产品行业&#xff0c; 随着精细化和自动化的发展&#xff0c; 电子产品的焊点质量的好坏&#xff0c; 深刻地影响着产品成品的质…

Unity下如何实现RTMP或RTSP流播放和录制

技术背景 在探讨Unity平台RTMP或RTSP直播流数据播放和录制之前&#xff0c;我们先简单回顾下RTSP或RTMP直播流数据在Unity平台的播放流程&#xff1a; 通过Native RTSP或RTSP直播播放SDK回调RGB/YUV420/NV12等其中的一种未压缩的图像格式&#xff1b;Unity下创建相应的RGB/YU…

工作几年后想转行学编程,如何操作才靠谱?

30岁转行来得及么&#xff1f;继结婚、生子、就业、买房后&#xff0c;又一个对30岁的灵魂拷问来了。 在一档综艺节目里&#xff0c;有位年过30的“实习生”为了能让孩子有更好的生活条件、也为了自己的事业与发展&#xff0c;她选择在而立之年放下自己曾经所擅长的&#xff0c…

微信键盘终于正式发布,张小龙说:其目的并不是为了抢夺输入法市场

自从2021年1月份&#xff0c;张小龙在微信公开课透露&#xff1a;微信将上线属于自己的专属输入法&#xff0c;到现在已经快2年过了。 今天终于正式发布了&#xff0c;下面我们一起来体验下。 1、安装 打开App Store&#xff0c;输入“微信键盘”&#xff0c;点击获取就可以…

刷爆力扣之字符串中的单词数

刷爆力扣之字符串中的单词数 HELLO&#xff0c;各位看官大大好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 今天阿呆继续记录下力扣刷题过程&#xff0c;收录在专栏算法中 &#x1f61c;&#x1f61c;&#x1f61c; 该专栏按照不同类别标签进行刷题&#…

STATIC和静态分析的必要性

​有一款正常工作的软件。那么可以说正常工作的软件都是好软件吗&#xff1f;在漫威的《黑豹》电影中有一个场景我深深地看过。 <图 1> 电影《黑豹》的场景 就算做的好&#xff0c;也有进步的空间! 我认为好的软件也是如此。 即使它可以工作并且没有问题&#xff0c;但总…

Vue2.x中的Vuex

目录 一、vuex是什么 1.1、概念 1.2、Github地址 1.3、使用场景 1.4、同胞传值 1.5、Vuex工作原理 1.6、搭建Vuex环境 二、求和案例 2.1、getters用法 2.2、mapState与mapGetters 2.3、mapActions与mapMutations 2.4、Vuex模块化命名空间namespace 2.5、头插人员案…

Nacos 寻址机制

目录 1. 什么是寻址机制 2. 源码讲解 MemberLookup AbstractMemberLookup 2.1 单机寻址 2.2. 文件寻址 2.3 地址服务器寻址 1. 什么是寻址机制 假设存在一个 Nacos 集群&#xff0c;其内部具有 A , B , C 三个节点。 客户端如何决定向集群中的哪个节点发送请求 在 appl…

基于Springboot+Mybatis+mysql+element-vue高校就业管理系统

基于SpringbootMybatismysqlelement-vue高校就业管理系统一、系统介绍二、功能展示1.用户登陆注册2.个人信息(学生端)3.查看企业岗位信息&#xff08;学生端&#xff09;4.我的应聘(学生端)5.学生信息管理&#xff08;辅导员&#xff09;6.三方协议书审核&#xff08;辅导员&am…

向用户展示推荐算法,TikTok主动“透明化”

获悉&#xff0c;近日TikTok宣布推出一项新功能&#xff0c;用户可以查看推荐主页&#xff08;For You&#xff09;中某个视频被推荐的原因&#xff0c;这一功能让用户拥有了更多的知情权&#xff0c;让TikTok的算法变得更透明化。新功能新功能推出后&#xff0c;用户可以在视频…

Diskless:云与互联网数据中心的下一个大变革

作为数字经济的核心产业&#xff0c;云与互联网数据中心正面临着下一波技术浪潮的冲击&#xff1a;云原生应用和Serverless无服务器计算的全面普及&#xff0c;推动数据中心底层硬件资源彻底解耦池化和重组整合&#xff0c;形成新的扁平分层——新型存算分离硬件架构&#xff0…

定时循环执行Python脚本 —— 定时执行专家

目录 提前准备 方案一、执行DOS命令 方式 1、在《定时执行专家》里新建“执行DOS命令”任务 方案二、执行脚本文件 方式 1、编写 .bat 脚本&#xff0c;用来执行Python脚本 2、在《定时执行专家》里新建“执行脚本文件”任务 本文提供两种使用《定时执行专家》定时循环执…

挑选在线客服系统的七大注意事项

在线客服系统是客户关怀软件&#xff0c;能够为访客和客服提供即时对话&#xff0c;对访客来说&#xff0c;能够为其提供一个快速、高效的沟通方式&#xff0c;即时性的互动提高访客沟通体验&#xff1b;对客服来说&#xff0c;有效提高客服效率,为客服人员节省大量宝贵时间。 …

机器学习 | 支持向量机

一.基本原理 SVM是一种二分类模型 基本思想&#xff1a;在特征空间中寻找间隔最大的分离超平面使数据得到高效的二分类&#xff0c;具体来讲&#xff0c;有三种情况&#xff08;不加核函数的话就是线性模型&#xff0c;加了之后才会升级为一个非线性模型&#xff09; 当训练…

玩以太坊链上项目的必备技能(Constant 和 Immutable 状态变量-Solidity之旅十六)

constant&#xff08;常量&#xff09; 说到常量这一概念&#xff0c;拥有现代编程经历的您&#xff0c;对这一词再熟悉不过了。 常量&#xff0c;常量&#xff0c;顾名思义便是变量值始终不变&#xff0c;这在很多面向对象程序语言中都有。 相对 Solidity 来说&#xff0c;…

Web入门开发【六】- 域名认证

欢迎来到霍大侠的小院&#xff0c;我们来学习Web入门开发的系列课程。 首先我们来了解下这个课程能学到什么&#xff1f; 1、你将可以掌握Web网站的开发全过程。 2、了解基础的HTML&#xff0c;CSS&#xff0c;JavaScript语言。 3、开发自己的第一个网站。 4、认识很多对编…

LabVIEW如何减少下一代测试系统中的硬件过时2

LabVIEW如何减少下一代测试系统中的硬件过时2 HAL最佳实践 从通用测试函数中分离测试逻辑 一个重要的最佳实践是将特定于dut的测试逻辑与更通用的、可重用的测试模块分离&#xff0c;以提高重用性并减少重新验证和文档成本。解耦和使层模块化可以改进系统架构&#xff0c;使…

08 面向对象三大特性

目录 一 封装 1.1 概述 1.2 为什么要进行封装&#xff1f; 1.3 Java中的封装 1.4 四种访问权限修饰符 1.5 练习 二 继承 2.1 继承的由来 2.2 继承的好处 2.3 语法格式 2.4 继承的特点之一&#xff1a;成员变量 2.4.1 父类成员变量私有化 2.4.2 父类和子类成员变量…