【笔记】Android Telephony 漫游SPN显示定制(Roaming Alpha Tag)

一、功能名词简介和显示规则

Alpha Tag:运营商名称标识符,也是用于标识运营商的一个名称。客户需求描述常用名词,对开发而言都是SPN/PLMN功能模块的内容,状态栏左上角的运营商名称显示。

SPN相关文章:

【笔记】SPN和PLMN 运营商网络名称显示_spn plmn-CSDN博客

Android U 配置 WiFiCalling 场景下PLMN/SPN 显示的代码逻辑介绍

网络运营商名称显示规则:

MTK平台的设计,对运营商名称的显示rule 是基于sim相关协议来实现的。优先级是Eons>nitz> XML配置(spn-con.xml)。EONS具有最高优先级,如果拿不到的EONS的情况下,要去读NITZ里的name。(Reference :FAQ08919)

  • EONS (Enhanced Operator Name String,“增强型运营商名称字符串”):是在 GSM 网络中引入的一种机制,用于向移动设备发送关于当前所处位置和网络状态的更详细和更准确的信息,以便于移动设备更好地显示和呈现运营商信息。在传统的 GSM 网络中,运营商名称(即 SPN)通常只包含运营商的品牌名称或简称,例如“China Mobile”或“AT&T”。这种信息的显示可能无法反映出当前所处的具体位置或网络状态,例如是否在漫游状态、网络类型、是否处于特殊服务状态等。为了解决这个问题,EONS 引入了更多的信息,以便于移动设备能够更好地显示和呈现运营商信息。
  • NITZ (Network Identity and Time Zone,“网络识别码和时区”):它是一种用于向移动设备发送网络识别码和时区信息的协议,通常在移动设备启动时或网络状态发生变化时进行同步更新。

二、代码逻辑

基于 Android T&U 版本分析。

(一)T和U代码差异

两个版本有差异,主要是Android U 删除了 SubscriptionController类,新增SubscriptionManagerService类。在Android T代码中也有备注setPlmnSpn接口适用maxSDK是R,有过渡提示。

  • T:SubscriptionController.java setPlmnSpn
  • U:SubscriptionManagerService.java getCarrierName 
【Android T】SubscriptionController/setPlmnSpn接口
【Android T】SubscriptionController/setPlmnSpn接口

在ServiceStateTracker.java中,T上同事更新plmn,但U上已经不再更新SPN,也没有setPlmnSpn接口。

【Android T】ServiceStateTracker- onSubscriptionsChanged()
【Android T】ServiceStateTracker- onSubscriptionsChanged()-setPlmnSpn
【Android U】ServiceStateTracker- onSubscriptionsChanged()
【Android U】ServiceStateTracker- onSubscriptionsChanged()
【Android U】updateSpn
【Android U】updateSpnDisplay

(二)【Android T】setPlmnSpn

onSubscriptionsChanged() => setPlmnSpn() => setCarrierText() =>refreshCachedActiveSubscriptionInfoList() & notifySubscriptionInfoChanged()

Android T 更新SPN代码流程:

  1. onSubscriptionsChanged()
  2. setPlmnSpn()
  3. setCarrierText()
  4. refreshCachedActiveSubscriptionInfoList() & notifySubscriptionInfoChanged()

ServiceStateTracker.java​

frameworks/opt/telephony/src/java/com/android/internal/telephony/ServiceStateTracker.java

在网络状态变化时SST内部类SstSubscriptionsChangedListener监听收到onSubscriptionsChanged() 回调,去更新PLMN和SPN。

【Android T】SubscriptionController/setPlmnSpn接口
【Android T】setPlmnSpn
onSubscriptionsChanged()
    private class SstSubscriptionsChangedListener extends OnSubscriptionsChangedListener {
        /**
         * Callback invoked when there is any change to any SubscriptionInfo. Typically
         * this method would invoke {@link SubscriptionManager#getActiveSubscriptionInfoList}
         */
        @Override
        public void onSubscriptionsChanged() {
            if (DBG) log("SubscriptionListener.onSubscriptionInfoChanged");

            final int curSubId = mPhone.getSubId();

            // If the sub info changed, but the subId is the same, then we're done.
            if (mSubId == curSubId) return;

            // If not, then the subId has changed, so we need to remember the old subId,
            // even if the new subId is invalid (likely).
            mPrevSubId = mSubId;
            mSubId = curSubId;

            mPhone.notifyPhoneStateChanged();
            setDataNetworkTypeForPhone(mSS.getRilDataRadioTechnology());

            //setPlmnSpn 更新PLMN/SPN
            if (mSpnUpdatePending) {
                mSubscriptionController.setPlmnSpn(mPhone.getPhoneId(), mCurShowPlmn,
                        mCurPlmn, mCurShowSpn, mCurSpn);
                mSpnUpdatePending = false;
            }

            //更新相关Settings设置内容。
            // Remove old network selection sharedPreferences since SP key names are now
            // changed to include subId. This will be done only once when upgrading from an
            // older build that did not include subId in the names.
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
                    context);
            String oldNetworkSelection = sp.getString(
                    Phone.NETWORK_SELECTION_KEY, "");
            String oldNetworkSelectionName = sp.getString(
                    Phone.NETWORK_SELECTION_NAME_KEY, "");
            String oldNetworkSelectionShort = sp.getString(
                    Phone.NETWORK_SELECTION_SHORT_KEY, "");
            if (!TextUtils.isEmpty(oldNetworkSelection)
                    || !TextUtils.isEmpty(oldNetworkSelectionName)
                    || !TextUtils.isEmpty(oldNetworkSelectionShort)) {
                SharedPreferences.Editor editor = sp.edit();
                editor.putString(Phone.NETWORK_SELECTION_KEY + mSubId,
                        oldNetworkSelection);
                editor.putString(Phone.NETWORK_SELECTION_NAME_KEY + mSubId,
                        oldNetworkSelectionName);
                editor.putString(Phone.NETWORK_SELECTION_SHORT_KEY + mSubId,
                        oldNetworkSelectionShort);
                editor.remove(Phone.NETWORK_SELECTION_KEY);
                editor.remove(Phone.NETWORK_SELECTION_NAME_KEY);
                editor.remove(Phone.NETWORK_SELECTION_SHORT_KEY);
                editor.commit();
            }

            // Once sub id becomes valid, we need to update the service provider name
            // displayed on the UI again. The old SPN update intents sent to
            // MobileSignalController earlier were actually ignored due to invalid sub id.
            updateSpnDisplay();
        }
    };

SubscriptionController.java

    /**
     * Generate and set carrier text based on input parameters
     * @param showPlmn flag to indicate if plmn should be included in carrier text
     * @param plmn plmn to be included in carrier text
     * @param showSpn flag to indicate if spn should be included in carrier text
     * @param spn spn to be included in carrier text
     * @return true if carrier text is set, false otherwise
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn,
                              String spn) {
        synchronized (mLock) {
            int subId = getSubIdUsingPhoneId(slotIndex);
            if (mContext.getPackageManager().resolveContentProvider(
                    SubscriptionManager.CONTENT_URI.getAuthority(), 0) == null ||
                    !SubscriptionManager.isValidSubscriptionId(subId)) {
                // No place to store this info. Notify registrants of the change anyway as they
                // might retrieve the SPN/PLMN text from the SST sticky broadcast.
                // TODO: This can be removed once SubscriptionController is not running on devices
                // that don't need it, such as TVs.
                if (DBG) logd("[setPlmnSpn] No valid subscription to store info");
                notifySubscriptionInfoChanged();
                return false;
            }
            String carrierText = "";
            if (showPlmn) {
                carrierText = plmn;
                if (showSpn) {
                    //当PLMN和SPN不相同时,就会显示PLMN-SPN
                    // Need to show both plmn and spn if both are not same.
                    if(!Objects.equals(spn, plmn)) {
                        String separator = mContext.getString(
                                com.android.internal.R.string.kg_text_message_separator).toString();
                        carrierText = new StringBuilder().append(carrierText).append(separator)
                                .append(spn).toString();
                    }
                }
            } else if (showSpn) {
                carrierText = spn;
            }
            setCarrierText(carrierText, subId);
            return true;
        }
    }

    /**
     * Set carrier text by simInfo index
     * @param text new carrier text
     * @param subId the unique SubInfoRecord index in database
     * @return the number of records updated
     */
    private int setCarrierText(String text, int subId) {
        if (DBG) logd("[setCarrierText]+ text:" + text + " subId:" + subId);

        enforceModifyPhoneState("setCarrierText");

        // Now that all security checks passes, perform the operation as ourselves.
        final long identity = Binder.clearCallingIdentity();
        try {
            boolean update = true;
            int result = 0;
            SubscriptionInfo subInfo = getSubscriptionInfo(subId);
            if (subInfo != null) {
                update = !TextUtils.equals(text, subInfo.getCarrierName());
            }
            if (update) {
                ContentValues value = new ContentValues(1);
                value.put(SubscriptionManager.CARRIER_NAME, text);

                result = mContext.getContentResolver().update(
                        SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);

                // Refresh the Cache of Active Subscription Info List
                refreshCachedActiveSubscriptionInfoList();

                notifySubscriptionInfoChanged();
            } else {
                if (DBG) logd("[setCarrierText]: no value update");
            }
            return result;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

(三) 【Android U】SetCarrierName

pollStateDone() 等 => updateSpnDisplay() => updateSpnDisplayCdnr() => notifySpnDisplayUpdate() => mSubscriptionManagerService.setCarrierName =>  mSubscriptionDatabaseManager.setCarrierName(subId, carrierName);

Android U 更新SPN代码流程:

  1. updateSpnDisplay()
  2. updateSpnDisplayCdnr()
  3. notifySpnDisplayUpdate()——先获取spn再set
  4. setCarrierName() 从manager到database

ServiceStateTracker.java

/frameworks/opt/telephony/src/java/com/android/internal/telephony/ServiceStateTracker.java

    private void notifySpnDisplayUpdate(CarrierDisplayNameData data) {
        int subId = mPhone.getSubId();
        // Update ACTION_SERVICE_PROVIDERS_UPDATED if any value changes
        if (mSubId != subId
                || data.shouldShowPlmn() != mCurShowPlmn
                || data.shouldShowSpn() != mCurShowSpn
                || !TextUtils.equals(data.getSpn(), mCurSpn)
                || !TextUtils.equals(data.getDataSpn(), mCurDataSpn)
                || !TextUtils.equals(data.getPlmn(), mCurPlmn)) {

            final String log = String.format("updateSpnDisplay: changed sending intent, "
                            + "rule=%d, showPlmn='%b', plmn='%s', showSpn='%b', spn='%s', "
                            + "dataSpn='%s', subId='%d'",
                    getCarrierNameDisplayBitmask(mSS),
                    data.shouldShowPlmn(),
                    data.getPlmn(),
                    data.shouldShowSpn(),
                    data.getSpn(),
                    data.getDataSpn(),
                    subId);
            mCdnrLogs.log(log);
            if (DBG) log("updateSpnDisplay: " + log);

            Intent intent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
            intent.putExtra(TelephonyManager.EXTRA_SHOW_SPN, data.shouldShowSpn());
            intent.putExtra(TelephonyManager.EXTRA_SPN, data.getSpn());
            intent.putExtra(TelephonyManager.EXTRA_DATA_SPN, data.getDataSpn());
            intent.putExtra(TelephonyManager.EXTRA_SHOW_PLMN, data.shouldShowPlmn());
            intent.putExtra(TelephonyManager.EXTRA_PLMN, data.getPlmn());
            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
            mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);

            if (SubscriptionManager.isValidSubscriptionId(subId)) {
                mSubscriptionManagerService.setCarrierName(subId, TextUtils.emptyIfNull(
                        getCarrierName(data.shouldShowPlmn(), data.getPlmn(),
                                data.shouldShowSpn(), data.getSpn())));
            }
        }
        mCurShowSpn = data.shouldShowSpn();
        mCurShowPlmn = data.shouldShowPlmn();
        mCurSpn = data.getSpn();
        mCurDataSpn = data.getDataSpn();
        mCurPlmn = data.getPlmn();
    }

    @NonNull
    private String getCarrierName(boolean showPlmn, String plmn, boolean showSpn, String spn) {
        String carrierName = "";
        if (showPlmn) {
            carrierName = plmn;
            if (showSpn) {
                // Need to show both plmn and spn if both are not same.
                if (!Objects.equals(spn, plmn)) {
                    String separator = mPhone.getContext().getString(
                            com.android.internal.R.string.kg_text_message_separator).toString();
                    carrierName = new StringBuilder().append(carrierName).append(separator)
                            .append(spn).toString();
                }
            }
        } else if (showSpn) {
            carrierName = spn;
        }
        return carrierName;
    }

    private void updateSpnDisplayCdnr() {
        log("updateSpnDisplayCdnr+");
        CarrierDisplayNameData data = mCdnr.getCarrierDisplayNameData();
        notifySpnDisplayUpdate(data);
        log("updateSpnDisplayCdnr-");
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @VisibleForTesting
    public void updateSpnDisplay() {
        if (mCarrierConfig.getBoolean(
                CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL)) {
            updateSpnDisplayCdnr();
        } else {
            updateSpnDisplayLegacy();
        }
    }

会触发SPN更新的场景(即调用updateSpnDisplay)
  •  BroadcastReceiveronReceive()
    • Intent.ACTION_LOCALE_CHANGED
    • TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED
  • handleMessage()
    • EVENT_ICC_CHANGED
    • EVENT_SIM_RECORDS_LOADED
    • EVENT_IMS_CAPABILITY_CHANGED
    • EVENT_RUIM_RECORDS_LOADED
  • setImsRegistrationState()
  • pollStateDone()

三、开发方案

  • T上只用subscriptions状态变化的时候会通过setPlmnSpn(其showPlmn和showSpn的逻辑再U上的getCarrierName接口中)更新名称内容,因此可以在设置spn的入口定制。
  • U上SST中包含很多carriername更新的场景,都是在notifySpnDisplayUpdate生效SPN更新 ,而此接口中都是通过get获取后再set设置信的,因此可以在getCarrierName获取的时候再定制内容。
【Android U】SST  notifySpnDisplayUpdate()
【Android U】SST  notifySpnDisplayUpdate()

(一)Android T 定制在 setPlmnSpn

Android T 上,可以在  SubscriptionController.java 中修改setPlmnSpn()接口内部逻辑,定制CarrierText(最终显示的字符串内容)。

    /**
     * Generate and set carrier text based on input parameters
     * @param showPlmn flag to indicate if plmn should be included in carrier text
     * @param plmn plmn to be included in carrier text
     * @param showSpn flag to indicate if spn should be included in carrier text
     * @param spn spn to be included in carrier text
     * @return true if carrier text is set, false otherwise
     */
    @UnsupportedAppUsage
    public boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn,
                              String spn) {
        synchronized (mLock) {
            int subId = getSubIdUsingPhoneId(slotIndex);

            //原生逻辑 
            if (showPlmn) {//...
            }

            //在最终更新运营商字符串之前实现定制
            carrierText = customizeCarrierText(carrierText, subId);

            setCarrierText(carrierText, subId); //原生逻辑
            return true;
        }
    }


    //客制化
    private String customizeCarrierText(String carrierText, int subId) {
        String customizeCarrierText= carrierText;
        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);

        if (tm != NULL) {
            String simNumeric = tm.getSimOperatorNumeric(subId); //卡本身的mccmnc,区别于漫游的网络
            final ServiceState serviceState = tm.getServiceState();
            boolean isRoaming =  (serviceState != null)  ? serviceState.getRoaming() : false;
            //根据卡和网络状态定制举例
            if(serviceState != null && "46001".equals(simNumeric) && isInService(serviceState)){
                customizeCarrierText= "CCC";
            }
        }
        
        return customizeCarrierText;
    }

(二)Android U 定制在 getCarrierName

Android U上,可在获取SPN后定制内容,

    private void notifySpnDisplayUpdate(CarrierDisplayNameData data) {
        int subId = mPhone.getSubId();
        // Update ACTION_SERVICE_PROVIDERS_UPDATED if any value changes

            if (SubscriptionManager.isValidSubscriptionId(subId)) {
                mSubscriptionManagerService.setCarrierName(subId, TextUtils.emptyIfNull(
                        getCarrierName(data.shouldShowPlmn(), data.getPlmn(),
                                data.shouldShowSpn(), data.getSpn())));
            }
    }


    @NonNull
    private String getCarrierName(boolean showPlmn, String plmn, boolean showSpn, String spn) {
        String carrierName = "";
        if (showPlmn) {
            carrierName = plmn;
            if (showSpn) {
                // Need to show both plmn and spn if both are not same.
                if (!Objects.equals(spn, plmn)) {
                    String separator = mPhone.getContext().getString(
                            com.android.internal.R.string.kg_text_message_separator).toString();
                    carrierName = new StringBuilder().append(carrierName).append(separator)
                            .append(spn).toString();
                }
            }
        } else if (showSpn) {
            carrierName = spn;
        }
        //定制
        carrierName = customizeCarrierText(carrierName, mPhone.getSubId());

        return carrierName;
    }

    private String customizeCarrierText(String carrierText, int subId) {
        String customizedCarrierText = carrierText;
        TelephonyManager tm = (TelephonyManager) mPhone.getContext().getSystemService(Context.TELEPHONY_SERVICE);
        tm = tm.createForSubscriptionId(subId);    //优化双卡逻辑
        if (tm != null && !tm.isWifiCallingAvailable()) { //不更新WFC场景
            int phoneId = mPhone.getPhoneId();
            String simNumeric = tm.getSimOperatorNumeric(subId);    //卡MCCMNC
            final ServiceState serviceState = tm.getServiceState();
            String operatorNumeric = (serviceState != null) ? serviceState.getOperatorNumeric() : null;    //注册网络的MCCMNC,如果是漫游,会跟simNumeric不同
            String gid1 = tm.getGroupIdLevel1(subId);
            //Avoid NullPointerException
            boolean isRoaming = serviceState == null ? false : serviceState.getRoaming();

            Log.d("customizeCarrierText: operatorNumeric:" + operatorNumeric + ", simNumeric:" + simNumeric);

            if (TextUtils.isEmpty(simNumeric)) {
                simNumeric = mPhone.getOperatorNumeric();
            }
            if("46000".equals(operatorNumeric) && "310260".equals(simNumeric)) {
                //定制期望显示
                customizedCarrierText = "Visible";
            }
            Log.d("customizeCarrierText: simNumeric = " + simNumeric + ", operatorNumeric = " + operatorNumeric + ", customizedCarrierText = " + customizedCarrierText);
        }
        return customizedCarrierText;
    }

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

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

相关文章

nyist_acm 个人积分赛1(部分题解会补充)

Mirrored String II 看到题解说是马拉车算法,我赛时并没想到(好吧其实我是比赛完才知道有马拉车这个算法) 因为字符串的长度只有1000,直接暴力跑其实就可以了,但是要注意的是;回文串有俩种形式&#xff0c…

nginx部署前端工程替代方案gateway

nginx部署前端工程替代方案gateway 有市场要求部署的前端vue工程不使用nginx中间件。想弄国产替代的东方通之类的,公司没有购买该产品,我参考了网上的一些java网关框架,springcloud组件:gateway实现代替。 注意后台都是用java编…

shadertoy 游戏《来自星尘》摇杆复刻

正确的做法应该是上 noise 而不是叠加 sin 波,不过如果不想麻烦的话叠波还是一个不错的选择:整体效果如下,已经非常形似 直接上链接:Shader - Shadertoy BETA float radiusScale 0.9; float variation(vec2 v1, vec2 v2, float …

据说这是最细滴,Python接口自动化测试数据驱动DDT使用实战,有这一篇就完全足够了

前言 环境准备 首先,需要安装ddt模块 pip install ddt调用时标准格式 在类下面如下写上:ddt.ddt 在调用的方法下面需要写上:ddt.data(需要传入的多组数据) DDT简单介绍 名称: Data-Driven Tests,数据驱动测试。 作用…

深入理解Servlet

目录: ServletWeb开发历史Servlet简介Servlet技术特点Servlet在应用程序中的位置Tomcat运行过程Servlet继承结构Servlet生命周期Servlet处理请求的原理Servlet的作用HttpServletRequest对象HttpServletResponse对象ServletContext对象ServletConfig对象Cookie对象与…

Keepalived实验

一、 LVSKeepalived 实验:7-1为主; 7-2为备; 7-3和7-4为后端服务器 1.关闭防火墙和selinux [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 02.配置主设备7-1 1.安装ipvsadm和keepalived [rootlocalhost ~]#…

阿珊解说Vue中`$route`和`$router`的区别

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

可视化图表:柱坐标系与对应图表详解

一、柱坐标系及其构成 柱状坐标系是一种常见的可视化图表坐标系,用于显示柱状图(也称为条形图)的数据。它由两个相互垂直的轴组成,一个是水平轴(X轴),另一个是垂直轴(Y轴&#xff0…

评测本地部署的语音识别模型

1 引言 最近,朋友给我发来了一段音频,想转录成文字,并使用大型润色文本。音频中的普通话带有一定的口音,并且讲解内容较为专业,所以一般的语音识别工具很难达到较高的识别率。 于是试用了两个大模型。Whisper 是目前…

AIOps常见问题

AIOps的自动化通常指什么? AIOps 平台的自动化一般包括以下几个方面: 数据收集和整合:AIOps 平台可以从多个 IT 基础架构组件、应用需求与性能监视工具以及服务工单系统等数据源中收集并整合运维数据,形成一个全面的数据平台。数…

Access AR Foundation 5.1 in Unity 2022

如果已经下载安装了ARF但版本是5.0.7 可以通过下面的方式修改 修改后面的数字会自动更新 更新完成后查看版本 官方文档 Access AR Foundation 5.1 in Unity 2021 | AR Foundation | 5.1.2

YOLOv9中train.py与train_dual.py的异同!

专栏介绍:YOLOv9改进系列 | 包含深度学习最新创新,主力高效涨点!!! 首先,train.py(左)与train_dual.py(右)中的损失函数是不一样的,这也解释了为什么使用train.py除了填入…

Visual Studio 2022之Release版本程序发送到其它计算机运行

目录 1、缺少dll​ 2、应用程序无法正常启动 3、This application failed to start because no Qt platform plugin could be initialized. 代码在Debug模式下正常运行,然后切换到Release模式下,也正常运行,把第三方平台的dll拷贝到exe所在…

Chrome浏览器好用的几个扩展程序

Chrome好用的扩展程序 背景目的介绍JsonHandle例子未完待续。。。。。。 背景 偶然在往上看到Chrome有很多好用的扩展程序,比较好用,因此记录下比较实用的扩展程序。 目的 记录Chrome浏览器好用的插件。 介绍 JsonHandle下载以及无法扩展插件的解决…

Vue3_2024_6天【回顾上篇watch常见的前三种场景】另两种待补

第一种情况:监视【ref】定义(基本数据类型) 1.引入watch2.格式:watch(基本数据类型数据,监视变化的回调函数) 注意点: 2.1.watch里面第一个参数,是数据~~【监视的基本类…

基于深度学习的三维重建MVSNet系列

2019年4月15日下午6时50分左右,一场大火席卷了法国巴黎圣母院,持续长达14小时。幸而巴黎圣母院有着高分辨率的3D模型,研究人员可以了解圣母院本身的建造结构,以便修复工程的开展。 多视图立体几何(Multi-View Stereo&a…

unity-urp:视野雾

问题背景 恐怖游戏在黑夜或者某些场景下,需要用雾或者黑暗遮盖视野,搭建游戏氛围 效果 场景中,雾会遮挡场景和怪物,但是在玩家视野内雾会消散,距离玩家越近雾越薄。 当前是第三人称视角,但是可以轻松的…

Linux:kubernetes(k8s)探针LivenessProbe的使用(9)

他做的事情就是当我检测的一个东西他不在规定的时间内存在的话,我就让他重启,这个检测的目标可以是文件或者端口等 我这个是在上一章的基础之上继续操作,我会保留startupProbe探针让后看一下他俩的执行优先的一个效果 Linux:kuber…

【QT】QDialog/ QMessageBox/提示对话框/颜色(文字)------对话框

QDialog—对话框 什么是对话框,如下样式 非模态对话框,即打开以后,我还可以对其他框进行操作。 模态对话框,打开以后,其他框都不能再操作了 模态对话框是阻塞对话框 QDialog dig(this);//显示模态对话框dig.exec();…

ROS2中nav_msgs/msg/Path 数据含义及使用

目录 ROS2中nav_msgs/msg/Path数据含义及使用ROS官方消息说明使用ros2中Path生成路径并显示案例使用ROS2命令创建功能包修改创建功能包中的CMakeLists.txt如下创建发布话题的main函数编译与运行rviz可视化发布的路径 ROS2中nav_msgs/msg/Path数据含义及使用 ROS2官方关于nav_m…