【笔记】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}*/@Overridepublic 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/SPNif (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 ListrefreshCachedActiveSubscriptionInfoList();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 changesif (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();}@NonNullprivate 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)@VisibleForTestingpublic 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*/@UnsupportedAppUsagepublic 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 changesif (SubscriptionManager.isValidSubscriptionId(subId)) {mSubscriptionManagerService.setCarrierName(subId, TextUtils.emptyIfNull(getCarrierName(data.shouldShowPlmn(), data.getPlmn(),data.shouldShowSpn(), data.getSpn())));}}@NonNullprivate 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);    //卡MCCMNCfinal ServiceState serviceState = tm.getServiceState();String operatorNumeric = (serviceState != null) ? serviceState.getOperatorNumeric() : null;    //注册网络的MCCMNC,如果是漫游,会跟simNumeric不同String gid1 = tm.getGroupIdLevel1(subId);//Avoid NullPointerExceptionboolean 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.mzph.cn/news/728024.shtml

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

相关文章

重装系统后正版office如何安装

前言 重装系统后,正版office如何安装 登录官网 https://www.microsoft.com 下载office https://account.microsoft.com/services

半监督

实际上就是在加载dataloader那里做了调整,采样器 这段代码定义了一个名为create_data_loaders的函数,用于创建训练集和验证集的数据加载器。 def create_data_loaders(train_transform, eval_transform, datadir, config):traindir os.path.join(data…

OA系统看飞书,能把繁杂场景设计的这么流畅,绝对是高手。

OA系统看飞书,能把繁杂场景设计的这么流畅,绝对是高手。 2023-08-18 23:33贝格前端工场 飞书是一款功能强大、操作流畅的企业协作工具,它提供了丰富的功能和灵活的场景设计,使得用户在使用过程中能够更加高效地协作和沟通。 以…

ChatMASTER部署教程

项目简介 ChatMASTER,基于AI大模型api实现的自建后端Chat服务,支出同步响应及流式响应,完美呈现打印机效果。支持一键切换ChatGPT(3.5、4.0)模型、文心一言(支持Stable-Diffusion-XL作图)、通义千问、讯飞星火、智谱清言(ChatGLM)等主流模型…

IP形象设计是什么设计?如何做?

随着市场竞争的激烈,越来越多的企业开始关注品牌形象的塑造和推广。在品牌形象中,知识产权形象设计是一个非常重要的方面。在智能和互联网的趋势下,未来的知识产权形象设计可能更加关注数字和社交网络。通过数字技术和社交媒体平台&#xff0…

ospf虚链路实验简述

1、ospf虚链路实验简述 ospf虚链路配置 为解决普通区域不在骨干区域旁,通过配置Vlink-peer实现不同区域网络设备之间建立逻辑上的连接。 实验拓扑图 r1: sys sysname r1 undo info enable int loopb 0 ip add 1.1.1.1 32 ip add 200.200.200.200 32 quit int e0/0/…

Leetcode 239:滑动窗口最大值

题意 大小为 k 的滑动窗口从整数数组 nums 的最左侧移到最右侧,只能看到滑动窗口中的 k 个数字,窗口每次向右移动一位。 返回滑动窗口的最大值。 示例 1: 输入:nums [1,3,-1,-3,5,3,6,7], k 3 输出:[3,3,5,5,6,7] …

[leetcode 26][删除有序数组的重复项]

[leetcode 26][删除有序数组的重复项] 给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O…

一家新店怎么快速出体验分?教大家一个简单好用的方法,建议收藏

大家好,我是电商花花。 在现在直播电商时代,抖音电商已经成为了一种新兴的商业模式,在抖音小店的项目上,店铺体验分成为了抖音小店能否成功的一个关键因素之一。 店铺的体验分越高,我们店铺的权重才会更高&#xff0…

04.if判断

04.if判断 01.if判断02.运算符2.比较(关系)运算符3.逻辑运算符4.三目运算符(三元表达式) (03)5.if-else6.if-elif结构 04.if嵌套7.if嵌套 01.if判断 if判断基本格式 基本格式 if 要判断的条件:…

掘根宝典之C语言和C++中的const

const的基本概念 const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。 习惯性的使用const,可以避免在函数中对某些不应修改的变量造成可能的改动。 const修饰基本数据类型 2.1 const修饰一般常量及数组 int const a…

MAth类与Random类

Math类 Math类是Java编程语言中的一个工具类,它包含了一系列用于执行基本数学运算的静态方法;由于Math类中构造方法的访问权限是private,所以无法创建Math类的对象;Math类中的所有方法都是静态方法,可以通过类名直接调…

CSS复合选择器(三)

伪元素选择器 作用:选中元素中的一些特殊位置。 常用伪元素: ::first-letter 选中元素中的第一个文字。::first-line 选中元素中的第一行文字。::selection选中被鼠标选中的内容。::placeholder 选中输入框的提示文字。::before 在元素最开始的位置&…

1.BOM-获取元素(获取元素、修改属性)

web Api基本认知 作用:通过JS去操作html页面和浏览器(实现浏览器中的某些功能) 分类: DOM(网页):Document Object Model(文档对象模型) BOM(浏览器):Borwser Object Model(浏览器对象模型) DOM DOM树 将网页中标签的关系以树状…

利用excel批量修改图片文件名

今天同事提出需求要实现利用excel批量修改某文件夹下的图片重命名,衡量到各种条件,最后还是选择了vbs来实现。代码如下 代码 创建Excel对象 Set objExcel CreateObject("Excel.Application") objExcel.Visible False 隐藏Excel窗口 打开Ex…

react:usecontext使用

useContext返回 调用组件上方最近的 SomeContext.Provider 的 value 如果没有这样的 provider 传递给 createContext 的 defaultValue。返回的值始终是最新的。 如果 context 发生变化&#xff0c;React 会自动重新渲染读取 context 的组件。 <Context.Provider> 需要位于…

【MySQL知识体系】第2章 数据库与表的创建(一)

第2章 数据库与表的创建 2.1 数据库操作 2.2 表操作 文章目录 第2章 数据库与表的创建2.1 数据库操作2.1.1 创建第一个数据库2.1.2 更新数据库名称&#xff08;数据库创建后无法修改名称&#xff09;2.1.3 删除数据库2.1.4 取个合适的数据库名称 第2章 数据库与表的创建 2.1 数…

990-40产品经理:What Is Effective Written Communication? 什么是有效的书面沟通?

The purpose of written communication is to capture your reader’s attention and get your point across clearly. Ultimately, when you communicate in writing, you are helping the reader understand your perspective看法 on a topic. There are certain qualities a…

在 echarts 的 rich 中使用 iconfont 图标库图标作为 backgroundColor.image 值的方法

实现步骤 1、引入 iconfont.js。该脚本执行时&#xff0c;会在 body 下插入一个 svg 标签&#xff0c;标签下包含了图标库中的 svg 图标 path。 <script src"your/iconfont/path/iconfont.js"></script>或者 import your/iconfont/path/iconfont.js2、…

【学习心得】websocket协议简介并与http协议对比

一、轮询和长轮询 在websocket协议出现之前&#xff0c;要想实现服务器和客户端的双向持久通信采取的是Ajax轮询。它的原理是每隔一段时间客户端就给服务器发送请求找服务器要数据。 让我们通过一个生活化的比喻来解释轮询和长轮询假设你正在与一位不怎么主动说话的老大爷&…