網站首頁 編程語言 正文
概要
初識Android?PowerManagerService省電模式對省電模式進行了一個初步認識,介紹了一些概念,以及對省電模式環境初始化的代碼進行了簡單分析。讀者需要仔細閱讀第一篇文章,再來看這一篇文章。
打開省電模式,有三種方式:
- 手動模式,也就是用戶手動打開省電模式。
- 自動模式,用戶設置一個電量百分比閾值,當電量低于這個閾值,自動觸發省電模式。
- 動態模式,這種模式其實就是自動模式。根據文檔,這個模式是提供給應用,根據情況自動調整觸發省電模式的閾值。
本文只關注如下內容:
- 省電模式的打開過程。
- 什么是?battery saver sticky?模式。
只要掌握了上面2點內容,自動模式、動態模式,都可以自行分析。
打開省電模式
現在以手動打開省電模式為例,分析省電模式的打開過程。
從初識Android?PowerManagerService省電模式可知,在 Settings->Battery->Battery Saver 界面,可以手動打開省電模式,調用代碼如下
最終會調用?PowerManagerService?對應的方法:
public boolean setPowerSaveModeEnabled(boolean enabled) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.POWER_SAVER) != PackageManager.PERMISSION_GRANTED) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); } final long ident = Binder.clearCallingIdentity(); try { return setLowPowerModeInternal(enabled); } finally { Binder.restoreCallingIdentity(ident); } } private boolean setLowPowerModeInternal(boolean enabled) { synchronized (mLock) { // 充電狀態下,不允許打開/關閉省電模式 if (mIsPowered) { return false; } mBatterySaverStateMachine.setBatterySaverEnabledManually(enabled); return true; } }
在 AOSP 的設計中,省電模式和充電狀態是沖突的。如果設備處于省電模式狀態,此時插入充電器,那么一定會關閉省電模式。如果設備處于充電狀態,那么是不允許打開省電模式的。
說實話,我不是很認同這種設計。我認為省電模式是用戶的強烈個人意愿,只能由用戶自己決定打開或者關閉。
BatterySaverStateMachine狀態管理
從上面代碼可知,打開省電模式時,通過?BatterySaverStateMachine#setBatterySaverEnabledManually()?方法,把指令傳給狀態機
public void setBatterySaverEnabledManually(boolean enabled) { synchronized (mLock) { updateStateLocked(true, enabled); } }
狀態機通過?updateStateLocked()?更新內部狀態,然后根據狀態執行相應的操作。 注意,這里的第一個參數表示是否是用戶手動打開省電模式,值為 true,第二個參數表示是否打開省電模式,根據我們分析的例子,這里的值為 true。
private void updateStateLocked(boolean manual, boolean enable) { if (!manual && !(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) { return; // Not fully initialized yet. } switch (mState) { case STATE_OFF: { if (!mIsPowered) { // 非充電模式,才允許操作省電模式 if (manual) { // 手動操作 if (!enable) { return; } // 用戶手動打開省電模式 enableBatterySaverLocked(/*enable*/ true, /*manual*/ true, BatterySaverController.REASON_MANUAL_ON); hideStickyDisabledNotification(); // 狀態切換為 STATE_MANUAL_ON mState = STATE_MANUAL_ON; } else if (isAutomaticModeActiveLocked() && isInAutomaticLowZoneLocked()) { // ... 自動模式 } else if (isDynamicModeActiveLocked() && isInDynamicLowZoneLocked()) { // ... 動態模式 } } break; } // ... } }
狀態機里的默認狀態是?STATE_OFF,表示省電模式默認關閉。
通過?enableBatterySaverLocked(/*enable*/ true, /*manual*/ true, BatterySaverController.REASON_MANUAL_ON);
?打開省電模式,然后把狀態切換為?STATE_MANUAL_ON。對于每一次狀態切換,我們都要注意,因此這會影響下一次狀態切換。
private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason) { enableBatterySaverLocked(enable, manual, intReason, reasonToString(intReason)); } private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason, String strReason) { final boolean wasEnabled = mBatterySaverController.isFullEnabled(); // 已經處于省電模式狀態 if (wasEnabled == enable) { return; } // 充電中,是不允許打開省電模式的 if (enable && mIsPowered) { return; } mLastChangedIntReason = intReason; mLastChangedStrReason = strReason; mSettingBatterySaverEnabled = enable; // 1. 保存省電模式的狀態 putGlobalSetting(Settings.Global.LOW_POWER_MODE, enable ? 1 : 0); // 2. 打開 battery saver sticky 模式 if (manual) { // 用戶手動操作省電模式 // mBatterySaverStickyBehaviourDisabled 默認為 false,表示支持 battery saver sticky 模式 setStickyActive(!mBatterySaverStickyBehaviourDisabled && enable); } // 3. 通過 BatterySaverController 打開省電模式 mBatterySaverController.enableBatterySaver(enable, intReason); // 動態省電模式相關 if (intReason == BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON) { triggerDynamicModeNotification(); } else if (!enable) { hideDynamicModeNotification(); } }
在打開省電模式之前,首先把數據庫?Settings.Global.LOW_POWER_MODE?字段的值保存為 1。
low power 應該翻譯為低功耗,俗稱省電模式,而 low battery 才應該翻譯為低電量,不要混淆了。 源碼中?BatteryManagerService#getBatteryLevelLow()?表示電量是否低于自動省電模式的電量百分比,這個函數的命名非常差勁,一度讓我誤以為是低電量(電量低于15%),其實它表示是否觸發了自動省電模式。
第二步,我們要注意了,這里涉及了?battery saver sticky?功能。根據判斷條件可知,只有在用戶手動操作省電模式的情況下,才會觸發?battery saver sticky?功能,來看下?setStickyActive()
private void setStickyActive(boolean active) { // 表示 battery saver sticky 模式已經打開 mSettingBatterySaverEnabledSticky = active; // Settings.Global.LOW_POWER_MODE_STICKY 代表 battery saver sticky功能的狀態 putGlobalSetting(Settings.Global.LOW_POWER_MODE_STICKY, mSettingBatterySaverEnabledSticky ? 1 : 0); }
很簡單,就是保存狀態,表示?battery saver sticky?功能已經打開。
第三步,把打開省電模式的實際操作,交給了省電模式控制器?BatterySaverController。
BatterySaverController切換省電模式
現在來看下?BatterySaverController#enableBatterySaver()?如何打開省電模式
public void enableBatterySaver(boolean enable, int reason) { synchronized (mLock) { if (getFullEnabledLocked() == enable) { return; } // 1. 保存省電模式的狀態 setFullEnabledLocked(enable); // 2. 更新省電模式策略 if (updatePolicyLevelLocked()) { // 3. 處理省電模式狀態的改變 mHandler.postStateChanged(/*sendBroadcast=*/ true, reason); } } } private boolean getFullEnabledLocked() { return mFullEnabledRaw; } private void setFullEnabledLocked(boolean value) { if (mFullEnabledRaw == value) { return; } // 刷新省電模式的緩存,客戶端可以通過 PowerManager 獲取省電模式狀態 PowerManager.invalidatePowerSaveModeCaches(); mFullEnabledRaw = value; }
首先使用?mFullEnabledRaw?保存省電模式狀態。
然后,更新省電模式的策略。省電模式會影響很多模塊的功能,例如,最直觀的就是影響屏幕亮度。因此打開省電模式,必須得有一個策略,這些策略影響某些模塊的功能。
最后,處理省電模式狀態的改變。其中包括切換省電模式,通知省電模式的監聽者。
mFullEnabledRaw?表示?full battery saver,其實就是用戶用到的省電模式。其實還有一種省電模式?adaptive battery saver,這種省電模式,是通過命令行設置的,應該是與測試相關,執行?
adb shell power set-adaptive-power-saver-enabled true
?來開啟,具體可以參考?PowerManagerShellCommand?類。
BattterySaverPolicy控制省電策略
現在來看下?BatterySaverController#updatePolicyLevelLocked()?如何更新省電模式策略
private boolean updatePolicyLevelLocked() { if (getFullEnabledLocked()) { // 設置省電模式 policy level return mBatterySaverPolicy.setPolicyLevel(BatterySaverPolicy.POLICY_LEVEL_FULL); } else if (getAdaptiveEnabledLocked()) { return mBatterySaverPolicy.setPolicyLevel(BatterySaverPolicy.POLICY_LEVEL_ADAPTIVE); } else { return mBatterySaverPolicy.setPolicyLevel(BatterySaverPolicy.POLICY_LEVEL_OFF); } }
原來是給?BatterySaverPolicy?設置了?policy level,值為?BatterySaverPolicy.POLICY_LEVEL_FULL。
boolean setPolicyLevel(@PolicyLevel int level) { synchronized (mLock) { if (mPolicyLevel == level) { return false; } if (mPolicyLevel == POLICY_LEVEL_FULL) { mFullPolicy = mDefaultFullPolicy; } switch (level) { case POLICY_LEVEL_FULL: case POLICY_LEVEL_ADAPTIVE: case POLICY_LEVEL_OFF: // 1. 保存 level mPolicyLevel = level; break; default: Slog.wtf(TAG, "setPolicyLevel invalid level given: " + level); return false; } // 2. 根據 level,更新有效的 policy updatePolicyDependenciesLocked(); return true; } }
BatterSaverPolicy?保存了?policy level,并且調用?updatePolicyDependenciesLocked()?來更新有效的?battery saver policy。
private void updatePolicyDependenciesLocked() { // 1. 根據 policy level, 獲取對應的 policy final Policy rawPolicy = getCurrentRawPolicyLocked(); // 刷新省電模式緩存 invalidatePowerSaveModeCaches(); // 車載 final int locationMode; if (mAutomotiveProjectionActive.get() && rawPolicy.locationMode != PowerManager.LOCATION_MODE_NO_CHANGE && rawPolicy.locationMode != PowerManager.LOCATION_MODE_FOREGROUND_ONLY) { // If car projection is enabled, ensure that navigation works. locationMode = PowerManager.LOCATION_MODE_FOREGROUND_ONLY; } else { locationMode = rawPolicy.locationMode; } // 2. 根據獲取的策略,來更新有效的策略 // mEffectivePolicyRaw 表示實際生效的 policy // mEffectivePolicyRaw 的數據,基本上都是從 rawPolicy 中復制過來的 // 只有幾項是需要調整的,例如 車載 或者 無障礙,這兩個特殊的情況,在使用時注意下即可 mEffectivePolicyRaw = new Policy( rawPolicy.adjustBrightnessFactor, rawPolicy.advertiseIsEnabled, rawPolicy.cpuFrequenciesForInteractive, rawPolicy.cpuFrequenciesForNoninteractive, rawPolicy.deferFullBackup, rawPolicy.deferKeyValueBackup, rawPolicy.disableAnimation, rawPolicy.disableAod, rawPolicy.disableLaunchBoost, rawPolicy.disableOptionalSensors, // Don't disable vibration when accessibility is on. rawPolicy.disableVibration && !mAccessibilityEnabled.get(), rawPolicy.enableAdjustBrightness, rawPolicy.enableDataSaver, rawPolicy.enableFirewall, // Don't force night mode when car projection is enabled. rawPolicy.enableNightMode && !mAutomotiveProjectionActive.get(), rawPolicy.enableQuickDoze, rawPolicy.forceAllAppsStandby, rawPolicy.forceBackgroundCheck, locationMode, rawPolicy.soundTriggerMode ); // ... } // 默認省電模式策略 private Policy mFullPolicy = DEFAULT_FULL_POLICY; private Policy getCurrentRawPolicyLocked() { switch (mPolicyLevel) { case POLICY_LEVEL_FULL: return mFullPolicy; case POLICY_LEVEL_ADAPTIVE: return mAdaptivePolicy; case POLICY_LEVEL_OFF: default: return OFF_POLICY; } }
getCurrentRawPolicyLocked()?會獲取默認的省電模式策略?DEFAULT_FULL_POLICY,然后根據一些情況調整省電模式策略,最后形成有效的省電模式策略?mEffectivePolicyRaw。
現在讓我們看看這個默認的省電策略?DEFAULT_FULL_POLICY?到底是何方神圣
private static final Policy DEFAULT_FULL_POLICY = new Policy( 0.5f, /* adjustBrightnessFactor */ true, /* advertiseIsEnabled */ new CpuFrequencies(), /* cpuFrequenciesForInteractive */ new CpuFrequencies(), /* cpuFrequenciesForNoninteractive */ true, /* deferFullBackup */ true, /* deferKeyValueBackup */ false, /* disableAnimation */ true, /* disableAod */ true, /* disableLaunchBoost */ true, /* disableOptionalSensors */ true, /* disableVibration */ false, /* enableAdjustBrightness */ false, /* enableDataSaver */ true, /* enableFirewall */ true, /* enableNightMode */ true, /* enableQuickDoze */ true, /* forceAllAppsStandby */ true, /* forceBackgroundCheck */ PowerManager.LOCATION_MODE_FOREGROUND_ONLY, /* locationMode */ PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY /* soundTriggerMode */ );
Policy?就是一個數據封裝類,看下它構造函數的參數,我們就能大致猜測出省電模式影響哪些模塊的功能。
這里注意下第三個和第四個參數,它表示省電模式下,需要限制頻率的 CPU 的編號以及限制的頻率值,這里默認是空,后面會用到。
處理省電模式狀態改變
現在讓我們回到打開省電模式的代碼
public void enableBatterySaver(boolean enable, int reason) { synchronized (mLock) { if (getFullEnabledLocked() == enable) { return; } // 1. 保存省電模式的狀態 setFullEnabledLocked(enable); // 2. 更新省電模式策略 if (updatePolicyLevelLocked()) { // 3. 處理省電模式狀態的改變 mHandler.postStateChanged(/*sendBroadcast=*/ true, reason); } } }
前兩步已經分析完畢,現在來看看第三步,它最終會調用?BatterySaverController#handleBatterySaverStateChanged()?來處理省電模式狀態改變
void handleBatterySaverStateChanged(boolean sendBroadcast, int reason) { final LowPowerModeListener[] listeners; final boolean enabled; // 獲取設備是否處于交互狀態 // 一般來說,如果屏幕熄滅,設備處于非交互狀態,屏幕電量,設備處于交互狀態 final boolean isInteractive = getPowerManager().isInteractive(); final ArrayMap<String, String> fileValues; synchronized (mLock) { // 獲取省電模式的狀態 enabled = getFullEnabledLocked() || getAdaptiveEnabledLocked(); // 保存前一個 full battery saver狀態 mFullPreviouslyEnabled = getFullEnabledLocked(); // 保存前一個adaptive battery saver狀態 mAdaptivePreviouslyEnabled = getAdaptiveEnabledLocked(); listeners = mListeners.toArray(new LowPowerModeListener[0]); mIsInteractive = isInteractive; if (enabled) { // 1. 打開省電模式情況下,獲取頻率受限的CPU的編號以及受限的值 fileValues = mBatterySaverPolicy.getFileValues(isInteractive); } else { fileValues = null; } } // 2. 通過 PowerManagerService 向底層設置省電模式 final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); if (pmi != null) { pmi.setPowerMode(Mode.LOW_POWER, isEnabled()); } // 用 BatterySavingStats 記錄數據 updateBatterySavingStats(); // 3. 根據策略,限制或恢復CPU頻率 if (ArrayUtils.isEmpty(fileValues)) { // CPU 策略為空,表示需要恢復 CPU 之前的頻率 mFileUpdater.restoreDefault(); } else { // CPU 頻率策略不為空,表示需要限制 CPU 頻率 mFileUpdater.writeFiles(fileValues); } if (sendBroadcast) { // 4. 發送廣播 Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); // 可以在 frameworks-res 的配置文件中配置一個應用的包名 // 這個應用可以在manifest.xml中注冊廣播接收器,接收省電模式狀態改變 if (getPowerSaveModeChangedListenerPackage().isPresent()) { intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED) .setPackage(getPowerSaveModeChangedListenerPackage().get()) .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_FOREGROUND); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } // 發送一個內部版本的廣播,但是接收者需要權限 intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL, Manifest.permission.DEVICE_POWER); // 5. 通知監聽者 for (LowPowerModeListener listener : listeners) { final PowerSaveState result = mBatterySaverPolicy.getBatterySaverPolicy(listener.getServiceType()); listener.onLowPowerModeChanged(result); } } }
第一步和第三步,是在省電模式下限制 CPU 頻率的。根據前面分析可知,目前默認策略是沒有配置CPU頻率的,因此這兩步不分析了。我將在后面的文章中,分析如何控制省電模式策略,到時候再來分析這里的代碼邏輯。
第二步,通過?PowerManagerService?向底層設置省電模式,底層稱之為低功耗模式(low power mode)。
第四步,發送省電模式狀態改變的廣播。
第五步,通知監聽者。誰會是監聽者呢?當然是那些受省電模式影響的模塊。
讓我們看下返回給監聽者的數據到底是什么?看下mBatterySaverPolicy.getBatterySaverPolicy(listener.getServiceType())
public PowerSaveState getBatterySaverPolicy(@ServiceType int type) { synchronized (mLock) { final Policy currPolicy = getCurrentPolicyLocked(); final PowerSaveState.Builder builder = new PowerSaveState.Builder() .setGlobalBatterySaverEnabled(currPolicy.advertiseIsEnabled); switch (type) { case ServiceType.LOCATION: boolean isEnabled = currPolicy.advertiseIsEnabled || currPolicy.locationMode != PowerManager.LOCATION_MODE_NO_CHANGE; return builder.setBatterySaverEnabled(isEnabled) .setLocationMode(currPolicy.locationMode) .build(); case ServiceType.ANIMATION: return builder.setBatterySaverEnabled(currPolicy.disableAnimation) .build(); // ... case ServiceType.VIBRATION: return builder.setBatterySaverEnabled(currPolicy.disableVibration) .build(); case ServiceType.FORCE_ALL_APPS_STANDBY: return builder.setBatterySaverEnabled(currPolicy.forceAllAppsStandby) .build(); case ServiceType.FORCE_BACKGROUND_CHECK: return builder.setBatterySaverEnabled(currPolicy.forceBackgroundCheck) .build(); // ... default: return builder.setBatterySaverEnabled(currPolicy.advertiseIsEnabled) .build(); } } }
原來,根據監聽者的類型,返回一個?PowerSaveState?對象,這個對象中只包含了監聽者關心的數據。
從這里,我們應該有所領悟,如果我們自己開發了一個功能模塊
- 如果受省電模式策略影響,必須注冊一個監聽器,獲取省電模式下策略,然后調整模塊的功能。
- 如果這個模塊是個耗電大戶,那么必須監聽省電模式,在省電模式下執行相應的操作。
現在很多項目都關注電量消耗問題,省電模式到底能讓手機待機多長時間,也是一個考核的指標。
battery saver sticky 模式
根據前面的分析,只有在用戶手動操作省電模式的時候,才會相應的打開或者關閉?battery saver sticky?模式。
我先總結下什么是?battery saver sticky?模式? 當手機已經處于省電模式,插入電源,系統會默認關閉省電模式,如果此時拔掉電源或者手機重啟,當?battery saver sticky?功能已經打開的情況下,系統會重新打開省電模式。
現在讓我們從代碼角度分析,繼續使用上面的例子分析,假如現在已經打開了省電模式,此時插入了電源,來看下狀態機的切換動作?BatterySaverStateMachine#updateStateLocked()
private void updateStateLocked(boolean manual, boolean enable) { if (!manual && !(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) { return; // Not fully initialized yet. } switch (mState) { case STATE_OFF: { if (!mIsPowered) { // 充電狀態下,不允許打開省電模式 if (manual) { // 手動模式 if (!enable) { Slog.e(TAG, "Tried to disable BS when it's already OFF"); return; } enableBatterySaverLocked(/*enable*/ true, /*manual*/ true, BatterySaverController.REASON_MANUAL_ON); hideStickyDisabledNotification(); // 1. 用戶打開省電模式,狀態切換為 STATE_MANUAL_ON mState = STATE_MANUAL_ON; } else if (isAutomaticModeActiveLocked() && isInAutomaticLowZoneLocked()) { // 自動模式 ... } else if (isDynamicModeActiveLocked() && isInDynamicLowZoneLocked()) { // 動態模式 ... } } break; } case STATE_MANUAL_ON: { if (manual) { // ... } else if (mIsPowered) { // 2. 插入電源 // 關閉省電模式 enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, BatterySaverController.REASON_PLUGGED_IN); // 手動打開省電模式時,mSettingBatterySaverEnabledSticky 設置為 true // mBatterySaverStickyBehaviourDisabled 默認為 false,表示支持這個 feature if (mSettingBatterySaverEnabledSticky && !mBatterySaverStickyBehaviourDisabled) { // 插入電源,狀態切換為 STATE_PENDING_STICKY_ON mState = STATE_PENDING_STICKY_ON; } else { mState = STATE_OFF; } } break; } // ... case STATE_PENDING_STICKY_ON: { // 3. battery saver sticky 模式操作 if (manual) { return; } // mSettingBatterySaverStickyAutoDisableEnabled 對應 Battery Saver界面下的 Turn off when charging 開關 // mSettingBatterySaverStickyAutoDisableThreshold 默認值為 90 final boolean shouldTurnOffSticky = mSettingBatterySaverStickyAutoDisableEnabled && mBatteryLevel >= mSettingBatterySaverStickyAutoDisableThreshold; // 手動打開省電模式,再插入電源,此時 isStickyDisabled 值為 false final boolean isStickyDisabled = mBatterySaverStickyBehaviourDisabled || !mSettingBatterySaverEnabledSticky; if (isStickyDisabled || shouldTurnOffSticky) { // 3.2 如果Turn off when charging 開關被打開,并且電量大于90%,那么不會重新打開省電模式 mState = STATE_OFF; setStickyActive(false); triggerStickyDisabledNotification(); } else if (!mIsPowered) { // Re-enable BS. // 3.1 斷開電源,重新打開省電模式 enableBatterySaverLocked(/*enable*/ true, /*manual*/ true, BatterySaverController.REASON_STICKY_RESTORE); mState = STATE_MANUAL_ON; } break; } // ... } }
首先看下第一步,它打開了省電模式,并且狀態切換為?STATE_MANUAL_ON。
如果此時,插入電源,那么會進入第二步, 關閉省電模式, 并把狀態切換為?STATE_PENDING_STICKY_ON。
如果關閉了設置中 Battery Saver 界面的 Turn off when Charging 開關,此時拔掉電源,那么進入 3.1 步,又會再次打開省電模式,這就是?sticky?的含義。
如果打開了設置中 Battery Saver 界面的 Turn off when Charging 開關,那么進入 3.2 步,不會再次打開省電模式。
設置中 Battery Saver 界面的 Turn off when Charging 開關就是?battery saver sticky auto disable?功能。
結束
通讀了整個省電模式的代碼,給我的感覺是很多功能都非常雞肋,限于偏于原因,我只分析了核心的代碼,那就是如何切換省電模式。剩下的其他功能,留給讀者自行分析。
原文鏈接:https://juejin.cn/post/7134650398425481223
相關推薦
- 2022-11-25 Python?Django教程之模型中字段驗證詳解_python
- 2022-12-05 一文深入了解Python中的繼承知識點_python
- 2022-03-18 處理Oracle監聽程序當前無法識別連接描述符中請求的服務異常(ORA-12514)_oracle
- 2022-09-24 Python?Matplotlib通過plt.subplots創建子繪圖_python
- 2022-06-12 C#實現基于任務的異步編程模式_C#教程
- 2022-03-11 UE4 添加自己項目的AutomationProject,解決報錯Failed to find co
- 2022-08-20 解決WPF繪制矢量圖形模糊的問題_C#教程
- 2023-07-26 vite項目中處理各種靜態資源的引入方式介紹
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支