Android8.1 開關VOLTE流程分析

来源:https://www.cnblogs.com/cczheng-666/archive/2019/05/10/10846190.html
-Advertisement-
Play Games

前言 最近有需求需要實現插卡預設打開Volte功能,順帶研究了下Volte的流程,在此做個記錄 開始 從Settings設置界面入手,網路和互聯網 移動網路 VoLTE高清通話(電信卡)/增強型4G LTE模式(移動卡) 找到網路和互聯網載入對應的Fragment為NetworkDashboardF ...


前言

最近有需求需要實現插卡預設打開Volte功能,順帶研究了下Volte的流程,在此做個記錄

開始

從Settings設置界面入手,網路和互聯網-->移動網路-->VoLTE高清通話(電信卡)/增強型4G LTE模式(移動卡)

找到網路和互聯網載入對應的Fragment為NetworkDashboardFragment,

源碼位置 vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\network\NetworkDashboardFragment.java

NetworkDashboardFragment載入的佈局xml為 network_and_internet.xml

源碼位置 vendor\mediatek\proprietary\packages\apps\MtkSettings\res\xml\network_and_internet.xml

<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:title="@string/network_dashboard_title">

...

<com.android.settingslib.RestrictedPreference
    android:key="mobile_network_settings"
    android:title="@string/network_settings_title"
    android:summary="@string/summary_placeholder"
    android:icon="@drawable/ic_network_cell"
    android:dependency="toggle_airplane"
    android:order="-15"
    settings:keywords="@string/keywords_more_mobile_networks"
    settings:userRestriction="no_config_mobile_networks"
    settings:useAdminDisabledSummary="true">
    <intent
        android:action="android.intent.action.MAIN"
        android:targetPackage="com.android.phone"
        android:targetClass="com.android.phone.MobileNetworkSettings"/>
</com.android.settingslib.RestrictedPreference>

從上面可以看出移動網路對應的目標啟動類為MobileNetworkSettings

源碼位置 vendor\mediatek\proprietary\packages\services\Telephony\src\com\android\phone\MobileNetworkSettings.java

代碼不算多,2400多行,別被嚇到了,只需找我們關心的即可

private void addEnhanced4GLteSwitchPreference(PreferenceScreen preferenceScreen) {
        int phoneId = SubscriptionManager.getPhoneId(mPhone.getSubId());
        log("[addEnhanced4GLteSwitchPreference] volteEnabled :"
                + isVolteEnabled());
        if (mButton4glte != null) {
            log("[addEnhanced4GLteSwitchPreference] Remove mButton4glte!");
            //移除Google原生的volte開關
            preferenceScreen.removePreference(mButton4glte);
        }
        //是否包含CT插件
        boolean isCtPlugin = ExtensionManager.getMobileNetworkSettingsExt().isCtPlugin();
        log("[addEnhanced4GLteSwitchPreference] ss :" + isCtPlugin);
        if (isVolteEnabled() && !isCtPlugin) {
            int order = mButtonEnabledNetworks.getOrder() + 1;
            //實例化volte開關
            mEnhancedButton4glte = new Enhanced4GLteSwitchPreference(this, mPhone.getSubId());
            /// Still use Google's key, title, and summary. 將原來的key依舊設置給新的volte開關,用於處理點擊事件
            mEnhancedButton4glte.setKey(BUTTON_4G_LTE_KEY);
            /// M: [CT VOLTE]
            // show "VOLTE" for CT VOLTE SIM
            if (TelephonyUtilsEx.isCtVolteEnabled()
                    && TelephonyUtilsEx.isCtSim(mPhone.getSubId())) {
                mEnhancedButton4glte.setTitle(R.string.hd_voice_switch_title);//VoLTE高清通話
                mEnhancedButton4glte.setSummary(R.string.hd_voice_switch_summary);//啟用前應先向運營商確認已開通此功能,否則可能影響正常通話
            } else {
                PersistableBundle carrierConfig =
                PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
                boolean useVariant4glteTitle = carrierConfig.getBoolean(
                        CarrierConfigManager.KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL);
                int enhanced4glteModeTitleId = useVariant4glteTitle ?
                        R.string.enhanced_4g_lte_mode_title_variant :
                        R.string.enhanced_4g_lte_mode_title;//增強型 4G LTE 模式
                mEnhancedButton4glte.setTitle(enhanced4glteModeTitleId);
            }
            /// M: [CT VOLTE]
            // show "VOLTE" for CT VOLTE SIM
            if (!TelephonyUtilsEx.isCtVolteEnabled()
                    || !TelephonyUtilsEx.isCtSim(mPhone.getSubId())) {
            /// @}
                mEnhancedButton4glte.setSummary(R.string.enhanced_4g_lte_mode_summary);//使用 LTE 服務改進語音和其他通信功能(推薦)
            }
            mEnhancedButton4glte.setOnPreferenceChangeListener(this);
            mEnhancedButton4glte.setOrder(order);
            /// M: Customize the LTE switch preference. @{
            ExtensionManager.getMobileNetworkSettingsExt()
                    .customizeEnhanced4GLteSwitchPreference(this, mEnhancedButton4glte);
            /// @}
        } else {
            mEnhancedButton4glte = null;
        }
    }

開關的UI分析完了,我們接著看下volte開關的點擊事件

    public boolean onPreferenceChange(Preference preference, Object objValue) {
        final int phoneSubId = mPhone.getSubId();
        if (onPreferenceChangeMTK(preference, objValue)) {//新實例化的volte開關處理點擊事件
            return true;
        }
        if (preference == mButtonPreferredNetworkMode) {
        ....
        } else if (preference == mButton4glte) {//Google原生的volte點擊事件
            SwitchPreference enhanced4gModePref = (SwitchPreference) preference;
            boolean enhanced4gMode = !enhanced4gModePref.isChecked();
            enhanced4gModePref.setChecked(enhanced4gMode);
            Log.e("NetworkSettings", "enhanced4gMode="+enhanced4gMode + " phoneId="+mPhone.getPhoneId());
            MtkImsManager.setEnhanced4gLteModeSetting(this, enhanced4gModePref.isChecked(),
                    mPhone.getPhoneId());
        }

新實例化的volte開關處理點擊事件

private boolean onPreferenceChangeMTK(Preference preference, Object objValue) {
        String volteTitle = getResources().getString(R.string.hd_voice_switch_title);
        String lteTitle = getResources().getString(R.string.enhanced_4g_lte_mode_title);
        log("[onPreferenceChangeMTK] Preference = " + preference.getTitle());

        if ((mEnhancedButton4glte == preference) || preference.getTitle().equals(volteTitle)
                || preference.getTitle().equals(lteTitle)) {
            Enhanced4GLteSwitchPreference ltePref = (Enhanced4GLteSwitchPreference) preference;
            log("[onPreferenceChangeMTK] IsChecked = " + ltePref.isChecked());
            /// M: [CT VOLTE] @{
            if (TelephonyUtilsEx.isCtVolteEnabled() && TelephonyUtilsEx.isCtSim(
                    mPhone.getSubId())
                    && !ltePref.isChecked()) {
                int type = TelephonyManager.getDefault().getNetworkType(mPhone.getSubId());
                log("network type = " + type);
                if (TelephonyManager.NETWORK_TYPE_LTE != type
                        && !TelephonyUtilsEx.isRoaming(mPhone)
                        && (TelephonyUtilsEx.getMainPhoneId() == mPhone.getPhoneId()
                        || TelephonyUtilsEx.isBothslotCt4gSim(mSubscriptionManager))) {
                    if (!TelephonyUtilsEx.isCtAutoVolteEnabled()) {
                        showVolteUnavailableDialog();
                        return false;
                    }
                }
            }
            ltePref.setChecked(!ltePref.isChecked());
            Log.e("NetworkSettings", "ltePref.isChecked()="+ltePref.isChecked() + " phoneId="+mPhone.getPhoneId());
            MtkImsManager.setEnhanced4gLteModeSetting(this, ltePref.isChecked(),
                    mPhone.getPhoneId());
            return true;
        }
        return false;
    }

這個函數處理如果點擊的是新實例化的vlote開關,或者volte整個條目,其它返回false,由原來的onPreferenceChange接著處理事件。

可以看到,最終通過MtkImsManager.setEnhanced4gLteModeSetting(this, ltePref.isChecked(), mPhone.getPhoneId()); 來控制Volte的打開和關閉。

繼續跟進 源碼位置 vendor\mediatek\proprietary\frameworks\opt\net\ims\src\java\com\mediatek\ims\internal\MtkImsManager.java

public static void setEnhanced4gLteModeSetting(Context context, boolean enabled, int phoneId) {
    int value = enabled ? 1 : 0;

    if (isSupportMims() == false) {
        phoneId = getMainPhoneIdForSingleIms(context);
        android.provider.Settings.Global.putInt(
                context.getContentResolver(),
                android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED, value);

        if (isNonTtyOrTtyOnVolteEnabled(context, phoneId)) {
            ImsManager imsManager = ImsManager.getInstance(context, phoneId);
            if (imsManager != null) {
                try {
                    imsManager.setAdvanced4GMode(enabled);
                } catch (ImsException ie) {
                    // do nothing
                }
            }
        }
    } else {
        sImsManagerExt = getImsManagerPluginInstance(context);
        if (sImsManagerExt != null) {
            phoneId = sImsManagerExt.getImsPhoneId(context, phoneId);
        }
        ImsManager imsManager = ImsManager.getInstance(context, phoneId);
        if(imsManager != null) {
            imsManager.setEnhanced4gLteModeSettingForSlot(enabled);
        } else {
            loge("setEnhanced4gLteModeSetting");
            loge("getInstance null for phoneId=" + phoneId);
        }
    }

}

這裡需要關註下isSupportMims()的返回結果

//private static final String MULTI_IMS_SUPPORT = "persist.mtk_mims_support";
public static boolean isSupportMims() {
    return (SystemProperties.getInt(MULTI_IMS_SUPPORT, 1) > 1);
}

SystemProperties讀取的是build.prop中的值,經查找persist.mtk_mims_support不存在,則為預設值1, isSupportMims()結果為false,

那麼回到上面的邏輯中,走if代碼塊,將volte的狀態保存到Settings.Global.ENHANCED_4G_MODE_ENABLED中,

方便當下次進入界面時查詢結果以顯示開關的狀態。繼續看isNonTtyOrTtyOnVolteEnabled()結果

public static boolean isNonTtyOrTtyOnVolteEnabled(Context context, int phoneId) {

    if (isSupportMims() == false) {
        if (ImsManager.getBooleanCarrierConfig(context,
                CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL)) {
            return true;
        }

        return Settings.Secure.getInt(context.getContentResolver(),
                Settings.Secure.PREFERRED_TTY_MODE, TelecomManager.TTY_MODE_OFF)
                == TelecomManager.TTY_MODE_OFF;
    }

    ImsManager imsManager = ImsManager.getInstance(context, phoneId);
    if(imsManager != null) {
        return imsManager.isNonTtyOrTtyOnVolteEnabledForSlot();
    } else {
        loge("isNonTtyOrTtyOnVolteEnabled");
        loge("getInstance null for phoneId=" + phoneId);
    }

    return false;
}

從剛剛結論isSupportMims()為false,主要看ImsManager.getBooleanCarrierConfig()結果

源碼位置 frameworks\opt\net\ims\src\java\com\android\ims\ImsManager.java

public static boolean getBooleanCarrierConfig(Context context, String key) {
    CarrierConfigManager configManager = (CarrierConfigManager) context.getSystemService(
            Context.CARRIER_CONFIG_SERVICE);
    PersistableBundle b = null;
    if (configManager != null) {
        b = configManager.getConfig();
    }
    if (b != null) {
        return b.getBoolean(key);
    } else {
        // Return static default defined in CarrierConfigManager.
        return CarrierConfigManager.getDefaultConfig().getBoolean(key);
    }
}

從上面的代碼可以看出,不論CarrierConfigManager是否為null,最終都是通過getBoolean()來讀取key對應的結果,有點類似SharedPrenference
還得繼續往下深入,

CarrierConfigManager 源碼位置 frameworks\base\telephony\java\android\telephony\CarrierConfigManager.java

 static {
    sDefaults = new PersistableBundle();
    ...
    sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
    sDefaults.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false);
    sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);

}

從中我們找到靜態代碼塊設置初始值 KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL,預設值為true,回到 MtkImsManager.java 中,

isNonTtyOrTtyOnVolteEnabled()結果為true,則調用imsManager.setAdvanced4GMode(enabled)來打開或關閉volte。

進入ImsManager中,源碼位置 frameworks\opt\net\ims\src\java\com\android\ims\ImsManager.java

public void setAdvanced4GMode(boolean turnOn) throws ImsException {
    checkAndThrowExceptionIfServiceUnavailable();//bind IMS_SERVICE,如果IMS服務不可用則拋出異常

    // if turnOn: first set feature values then call turnOnIms()
    // if turnOff: only set feature values if IMS turn off is not allowed. If turn off is
    // allowed, first call turnOffIms() then set feature values
    if (turnOn) {
        setLteFeatureValues(turnOn);
        log("setAdvanced4GMode: turnOnIms");
        turnOnIms();//打開IMS 服務
    } else {
        if (isImsTurnOffAllowed()) {
            log("setAdvanced4GMode: turnOffIms");
            turnOffIms();//關閉IMS 服務
        }
        setLteFeatureValues(turnOn);
    }
}

打開IMS服務

public void turnOnIms() throws ImsException {
    checkAndThrowExceptionIfServiceUnavailable();

    try {
        mImsServiceProxy.turnOnIms();
    } catch (RemoteException e) {
        throw new ImsException("turnOnIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
    }
}

通過mImsServiceProxy代理對象調用,代理對象的創建過程在createImsService()

/**
 * Binds the IMS service to make/receive the call. Supports two methods of exposing an
 * ImsService:
 * 1) com.android.ims.ImsService implementation in ServiceManager (deprecated).
 * 2) android.telephony.ims.ImsService implementation through ImsResolver.
 */
protected void createImsService() {
    if (!mConfigDynamicBind) {
        // Old method of binding  
        Rlog.i(TAG, "Creating ImsService using ServiceManager");
        mImsServiceProxy = getServiceProxyCompat();
    } else {
        Rlog.i(TAG, "Creating ImsService using ImsResolver");
        mImsServiceProxy = getServiceProxy();
    }
    // We have created a new ImsService connection, signal for re-registration
    synchronized (mHasRegisteredLock) {
        mHasRegisteredForProxy = false;
    }
}

此處創建mImsServiceProxy代理對象有兩種方式,mConfigDynamicBind的值在framework/core/res/res/values/config.xml中定義,

通過查看該值為false,則通過getServiceProxyCompat()獲取mImsServiceProxy對象。

private ImsServiceProxyCompat getServiceProxyCompat() {
    IBinder binder = ServiceManager.checkService(IMS_SERVICE);

    if (binder != null) {
        try {
            binder.linkToDeath(mDeathRecipient, 0);
        } catch (RemoteException e) {
        }
    }

    return new ImsServiceProxyCompat(mPhoneId, binder);
}

ImsServiceProxyCompat的turnOnIms()方法

@Override
public void turnOnIms() throws RemoteException {
    checkBinderConnection();
    getServiceInterface(mBinder).turnOnIms(mSlotId);
}

實際上通過mBinder獲取到IImsService對象,繼續跟進,實際上最終調用了IImsService.aidl的turnOnIms()

源碼位置 frameworks\base\telephony\java\com\android\ims\internal\IImsService.aidl

interface IImsService {
     ....

    /**
     * Config interface to get/set IMS service/capability parameters.
     */
    IImsConfig getConfigInterface(int phoneId);

    /**
     * Used for turning on IMS when its in OFF state.
     */
    void turnOnIms(int phoneId);

    /**
     * Used for turning off IMS when its in ON state.
     * When IMS is OFF, device will behave as CSFB'ed.
     */
    void turnOffIms(int phoneId);

    ....
}

回到上面在ImsManager.java中setAdvanced4GMode()方法,不管打開或關閉都會調用setLteFeatureValues(turnOn),來看下做了什麼操作

protected void setLteFeatureValues(boolean turnOn) {
    log("setLteFeatureValues: " + turnOn);
    try {
        ImsConfig config = getConfigInterface();
        if (config != null) {
            config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE,
                    TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, mImsConfigListener);

            if (isVolteEnabledByPlatformForSlot()) {
                boolean ignoreDataEnabledChanged = getBooleanCarrierConfig(mContext,
                        CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS);
                boolean enableViLte = turnOn && isVtEnabledByUserForSlot() &&
                        (ignoreDataEnabledChanged || isDataEnabled());
                config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
                        TelephonyManager.NETWORK_TYPE_LTE,
                        enableViLte ? 1 : 0,
                        mImsConfigListener);
            }
        }
    } catch (ImsException e) {
        loge("setLteFeatureValues: exception ", e);
    }
}

調用ImsConfig的setFeatureValue()保存值

源碼位置 frameworks\base\telephony\java\com\android\ims\ImsConfig.java

public void setFeatureValue(int feature, int network, int value,
        ImsConfigListener listener) throws ImsException {
    if (DBG) {
        Rlog.d(TAG, "setFeatureValue: feature = " + feature + ", network =" + network +
                ", value =" + value + ", listener =" + listener);
    }
    try {
        miConfig.setFeatureValue(feature, network, value, listener);
    } catch (RemoteException e) {
        throw new ImsException("setFeatureValue()", e,
                ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
    }
}

發現又調用的miConfig,繼續接著找吧。調用過程:

ImsConfig.Java中setFeatureValue()--->IImsConfig.aild--->

--->ImsConfigImplBase.java(繼承IImsConfig.aild)-->ImsConfigImpl中的setFeatureValue(繼承ImsConfigImplBase)-->ImsConfigStorage中的setFeatureValue

vendor\mediatek\proprietary\packages\services\Ims\src\com\mediatek\ims\config\internal\ImsConfigStorage.java

public void setFeatureValue(int featureId, int network, int value)
        throws ImsException {
    synchronized(mFeatureLock) {
        mFeatureHelper.updateFeature(featureId, network, value);
    }
}

//當前類中的內部類 FeatureHelper
private static class FeatureHelper {

private void updateFeature(int featureId, int network, int value) {
        int curValue = -1;
        boolean result = false;
        ContentValues cv = new ContentValues();
        cv.put(ImsConfigContract.Feature.PHONE_ID, mPhoneId);
        cv.put(ImsConfigContract.Feature.FEATURE_ID, featureId);
        cv.put(ImsConfigContract.Feature.NETWORK_ID, network);
        cv.put(ImsConfigContract.Feature.VALUE, value);

        // Check exist or not
        try {
            curValue = getFeatureValue(featureId, network);
            if (DEBUG) Log.d(TAG, "updateFeature() comparing: curValue: " +
                    curValue + ", value:" + value);
            if (!checkIfBroadcastOnce(featureId, mPhoneId) || curValue != value || curValue == -1) {
                mContentResolver.update(
                        ImsConfigContract.Feature.getUriWithFeatureId(mPhoneId, featureId, network),
                        cv, null, null);
            }
        } catch (ImsException e) {
            Log.e(TAG, "updateFeature() ImsException featureId:" + featureId +", value:" + value);
            mContentResolver.insert(ImsConfigContract.Feature.CONTENT_URI, cv);
        }
    }

}

參考鏈接

IMS的註冊流程分析

開關VoLTE流程分析(一)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 很多人都知道大數據很火,就業很好,薪資很高,想往大數據方向發展。但該學哪些技術,學習路線是什麼樣的呢? 其實就是想告訴你的大數據的三個發展方向,平臺搭建/優化/運維/監控、大數據開發/設計/架構、數據分析/挖掘。請不要問我哪個好學,哪個錢多。 先說一下大數據的4V特征: 數據量大,TB->PB 數據 ...
  • 指定一個日期,獲取其所屬周的開始日期和結束日期。以及前一周和後一周的開始日期和結束日期。 Insus.NET把這個功能寫成一個自定義函數Table-values Functions。 如何使用上面這個函數,列舉例子說明: ...
  • 安裝步驟可借鑒https://www.cnblogs.com/qfb620/p/4577255.html 1、安裝後發現用Navicat無法連接資料庫顯示報錯ORA-28547:connection to server failed,probable Oracle Net admin error 上 ...
  • 進銷存的訂單表設計這裡只討論最簡單的業務流程,採購申請等業務流程不考慮包括主表(表頭)和子表(明細表)主表:主要信息包括單據流水編碼、供應商信息、訂單類型、單據日期、制單人等等子表:主要信息是物料、數量以及價格主表相關設計如下()子表相關設計如下 ...
  • 最近發現一個非常奇怪的阻塞問題,如下截圖所示(來自監控工具DPA),會話583被會話1036阻塞,而且阻塞發生在tempdb,被阻塞的SQL如下截圖所示,會話等待類型為LCK_M_S 因為DPA工具不好截取全部信息,使用下麵SQL語句獲取了阻塞會話的詳細信息如下,來自Microsoft SQL Se... ...
  • [20190510]rman備份的疑問7.txt--//上午測試rman備份時備份文件大小回縮的測試.鏈接:--//http://blog.itpub.net/267265/viewspace-2643804/ => [20190509]rman備份的疑問5.txt --//自己又認真會看自己以前寫 ...
  • 根據需求,需要把某一些數字或字元串進行格式化,前導或後導字元串。Insus.NET把這個功能寫成一個自定義函數。需要時,直接使用即可。 SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- -- Author: Insus.NET -- Create ...
  • 先上效果圖 使用註意事項 1:註意在app.json中註冊頁面路徑 2:如果要增加新的Item,可到js中對listService數組進行增加 3:listService參數[ title:分類標題 items:這個分類下的所有Item[ name:這個Item的名字 url:這個Item點擊跳轉路 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...