Android 12系統源碼_窗口管理(九)深淺主題切換流程源碼分析

前言

上一篇我們簡單介紹了應用的窗口屬性WindowConfiguration這個類,該類存儲了當前窗口的顯示區域、屏幕的旋轉方向、窗口模式等參數,當設備屏幕發生旋轉的時候就是通過該類將具體的旋轉數據傳遞給應用的、而應用在加載資源文件的時候也會結合該類的AppBounds屬性,自動加載特定分辨率的資源文件。而在這些屬性發生變化之后,系統一般都是在onConfigurationChanged方法中作出響應的,這篇文章我們將會結合系統深淺主題發生切換到時候,新的系統配置是如何通過onConfigurationChanged回調給Activity的。

一、用戶行為觸發深淺模式主題切換

這里我們以下拉狀態欄的快捷按鈕深色主題切換為切入口來做具體源碼分析,我們點擊該按鈕,首先會觸發UiModeNightTile的handleClick方法。

1.1 SystemUI階段

frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java

public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implementsConfigurationController.ConfigurationListener,BatteryController.BatteryStateChangeCallback {private UiModeManager mUiModeManager;@Overrideprotected void handleClick(@Nullable View view) {if (getState().state == Tile.STATE_UNAVAILABLE) {return;}boolean newState = !mState.value;//注釋1mUiModeManager.setNightModeActivated(newState);refreshState(newState);}
}

在注釋1處調用UiModeManager的setNightModeActivated方法。

1.2 UiModeManager階段

frameworks/base/core/java/android/app/UiModeManager.java

public class UiModeManager {private IUiModeManager mService;UiModeManager(Context context) throws ServiceNotFoundException {mService = IUiModeManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.UI_MODE_SERVICE));mContext = context;}/*** 激活當前用戶的夜間模式UI視圖*/public boolean setNightModeActivated(boolean active) {if (mService != null) {try {//注釋1,調用UiModeManagerService的setNightModeActivated方法return mService.setNightModeActivated(active);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return false;}
}

在注釋1處調用UiModeManagerService的setNightModeActivated方法。

1.3 UiModeManagerService階段

frameworks/base/services/core/java/com/android/server/UiModeManagerService.java

final class UiModeManagerService extends SystemService {private Configuration mConfiguration = new Configuration();@Overridepublic boolean setNightModeActivated(boolean active) {...代碼省略...synchronized (mLock) {final long ident = Binder.clearCallingIdentity();try {//自動、自定義的主題切換if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {unregisterScreenOffEventLocked();mOverrideNightModeOff = !active;mOverrideNightModeOn = active;mOverrideNightModeUser = mCurrentUser;persistNightModeOverrides(mCurrentUser);} else if (mNightMode == UiModeManager.MODE_NIGHT_NO && active) {//夜間模式mNightMode = UiModeManager.MODE_NIGHT_YES;} else if (mNightMode == UiModeManager.MODE_NIGHT_YES&& !active) {//日間模式mNightMode = UiModeManager.MODE_NIGHT_NO;}//注釋1,更新ConfigurationupdateConfigurationLocked();//注釋2,應用ConfigurationapplyConfigurationExternallyLocked();//為當前用戶對應的Secure添加配置信息persistNightMode(mCurrentUser);return true;} finally {Binder.restoreCallingIdentity(ident);}}}private void updateConfigurationLocked() {...代碼省略...if (!mHoldingConfiguration && (!mWaitForScreenOff || mPowerSave)) {//將新的UIMode賦值給mConfigurationmConfiguration.uiMode = uiMode;}}private void applyConfigurationExternallyLocked() {if (mSetUiMode != mConfiguration.uiMode) {mSetUiMode = mConfiguration.uiMode;//清除窗口戶的快照緩存mWindowManager.clearSnapshotCache();try {//注釋3,調用ATMS的updateConfiguration方法,更新當前用戶對應的ConfigurationActivityTaskManager.getService().updateConfiguration(mConfiguration);} catch (RemoteException e) {Slog.w(TAG, "Failure communicating with activity manager", e);} catch (SecurityException e) {Slog.e(TAG, "Activity does not have the ", e);}}}}

在注釋1處調用updateConfigurationLocked方法,該方法會將新的UIMode賦值給mConfiguration;在注釋2處調用
applyConfigurationExternallyLocked方法,該方法內部也就是注釋3處,會調用ATMS的updateConfiguration方法,將當前用戶包含了新UiMode的Configuration同步給系統中的其他模塊和應用。

二、ActivityTaskManagerService通知ActivityThread

2.1 ActivityTaskManagerService階段

frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

public class ActivityTaskManagerService extends IActivityTaskManager.Stub {@Overridepublic boolean updateConfiguration(Configuration values) {mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");synchronized (mGlobalLock) {if (mWindowManager == null) {Slog.w(TAG, "Skip updateConfiguration because mWindowManager isn't set");return false;}if (values == null) {//從WMS中獲取當前系統默認屏幕設備對應的配置信息values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);}mH.sendMessage(PooledLambda.obtainMessage(ActivityManagerInternal::updateOomLevelsForDisplay, mAmInternal,DEFAULT_DISPLAY));final long origId = Binder.clearCallingIdentity();try {if (values != null) {Settings.System.clearConfiguration(values);}//注釋1,繼續調用updateConfigurationLocked,更新配置信息updateConfigurationLocked(values, null, false, false /* persistent */,UserHandle.USER_NULL, false /* deferResume */,mTmpUpdateConfigurationResult);return mTmpUpdateConfigurationResult.changes != 0;} finally {Binder.restoreCallingIdentity(origId);}}}boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,boolean initLocale, boolean persistent, int userId, boolean deferResume,ActivityTaskManagerService.UpdateConfigurationResult result) {int changes = 0;boolean kept = true;deferWindowLayout();try {if (values != null) {//注釋2,更新全局配置changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);}if (!deferResume) {//注釋4,更新后確保配置和可見性kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);}} finally {continueWindowLayout();}if (result != null) {result.changes = changes;result.activityRelaunched = !kept;}return kept;}int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,boolean persistent, int userId) {...代碼省略...SparseArray<WindowProcessController> pidMap = mProcessMap.getPidMap();for (int i = pidMap.size() - 1; i >= 0; i--) {final int pid = pidMap.keyAt(i);final WindowProcessController app = pidMap.get(pid);ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "+ "config %s", app.mName, mTempConfig);//注釋3,調用app的onConfigurationChanged方法app.onConfigurationChanged(mTempConfig);}final Message msg = PooledLambda.obtainMessage(ActivityManagerInternal::broadcastGlobalConfigurationChanged,mAmInternal, changes, initLocale);mH.sendMessage(msg);mRootWindowContainer.onConfigurationChanged(mTempConfig);return changes;}}    

注釋1處ATMS的updateConfiguration方法繼續調用updateConfigurationLocked;
注釋2處調用updateGlobalConfigurationLocked方法更新當前系統的全局配置;
注釋3獲取當前系統中的每一個窗口管理者,調用app.onConfigurationChanged方法,告知他們系統配置發生了變化。

2.2 WindowProcessController階段

frameworks/base/services/core/java/com/android/server/wm/WindowProcessController.java

public class WindowProcessController extends ConfigurationContainer<ConfigurationContainer>implements ConfigurationContainerListener {private final ActivityTaskManagerService mAtm;@Overridepublic void onConfigurationChanged(Configuration newGlobalConfig) {super.onConfigurationChanged(newGlobalConfig);//調用updateConfiguration方法updateConfiguration();}private void updateConfiguration() {final Configuration config = getConfiguration();if (mLastReportedConfiguration.diff(config) == 0) {if (Build.IS_DEBUGGABLE && mHasImeService) {Slog.w(TAG_CONFIGURATION, "Current config: " + config+ " unchanged for IME proc " + mName);}return;}if (mPauseConfigurationDispatchCount > 0) {mHasPendingConfigurationChange = true;return;}//調用dispatchConfiguration方法dispatchConfiguration(config);}void dispatchConfiguration(Configuration config) {mHasPendingConfigurationChange = false;if (mThread == null) {if (Build.IS_DEBUGGABLE && mHasImeService) {Slog.w(TAG_CONFIGURATION, "Unable to send config for IME proc " + mName+ ": no app thread");}return;}try {config.seq = mAtm.increaseConfigurationSeqLocked();//注釋1,獲取ClientLifecycleManager實例//調用其scheduleTransaction方法,并傳入ConfigurationChangeItem對象實例mAtm.getLifecycleManager().scheduleTransaction(mThread,ConfigurationChangeItem.obtain(config));setLastReportedConfiguration(config);} catch (Exception e) {Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);}}    
}    public class ActivityTaskManagerService extends IActivityTaskManager.Stub {private final ClientLifecycleManager mLifecycleManager;ClientLifecycleManager getLifecycleManager() {return mLifecycleManager;}   
}>frameworks/base/services/core/java/com/android/server/wm/ClientLifecycleManager.java
class ClientLifecycleManager {void scheduleTransaction(@NonNull IApplicationThread client,@NonNull ClientTransactionItem callback) throws RemoteException {final ClientTransaction clientTransaction = transactionWithCallback(client,null /* activityToken */, callback);//調用scheduleTransaction方法scheduleTransaction(clientTransaction);}private static ClientTransaction transactionWithCallback(@NonNull IApplicationThread client,IBinder activityToken, @NonNull ClientTransactionItem callback) {//獲取clientTransaction 對象實例final clientTransaction clientTransaction = ClientTransaction.obtain(client, activityToken);clientTransaction.addCallback(callback);return clientTransaction;}void scheduleTransaction(ClientTransaction transaction) throws RemoteException {final IApplicationThread client = transaction.getClient();//調用ClientTransaction的scheduleTransactiontransaction.schedule();if (!(client instanceof Binder)) {transaction.recycle();}}
}
>frameworks/base/core/java/android/app/servertransaction/ClientTransaction.java
public class ClientTransaction implements Parcelable, ObjectPoolItem {private IApplicationThread mClient;public void schedule() throws RemoteException {//注釋2,調用IApplicationThread的scheduleTransaction方法mClient.scheduleTransaction(this);}
}public final class ActivityThread extends ClientTransactionHandlerimplements ActivityThreadInternal {private class ApplicationThread extends IApplicationThread.Stub {@Overridepublic void scheduleTransaction(ClientTransaction transaction) throws RemoteException {//注釋3,調用scheduleTransaction方法,該方法位于父類ClientTransactionHandler中ActivityThread.this.scheduleTransaction(transaction);}}
}public abstract class ClientTransactionHandler {void scheduleTransaction(ClientTransaction transaction) {transaction.preExecute(this);//注釋3,調用sendMessagesendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);}
}public final class ActivityThread extends ClientTransactionHandler implements ActivityThreadInternal {private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this);final H mH = new H();void sendMessage(int what, Object obj) {sendMessage(what, obj, 0, 0, false);}private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {Message msg = Message.obtain();msg.what = what;msg.obj = obj;msg.arg1 = arg1;msg.arg2 = arg2;if (async) {msg.setAsynchronous(true);}mH.sendMessage(msg);}class H extends Handler {public static final int EXECUTE_TRANSACTION = 159;public void handleMessage(Message msg) {      case EXECUTE_TRANSACTION:final ClientTransaction transaction = (ClientTransaction) msg.obj;//注釋4,調用TransactionExecutor的execute方法mTransactionExecutor.execute(transaction);if (isSystem()) {transaction.recycle();}break;}}   
}public class TransactionExecutor {public void execute(ClientTransaction transaction) {...代碼省略...//繼續調用executeCallbacks方法executeCallbacks(transaction);executeLifecycleState(transaction);mPendingActions.clear();}public void executeCallbacks(ClientTransaction transaction) {...代碼省略...  final int size = callbacks.size();for (int i = 0; i < size; ++i) {final ClientTransactionItem item = callbacks.get(i);...代碼省略...//注釋4,調用每個ClientTransactionItem對象實例的execute方法item.execute(mTransactionHandler, token, mPendingActions);item.postExecute(mTransactionHandler, token, mPendingActions);...代碼省略...}}
}public class ConfigurationChangeItem extends ClientTransactionItem {//獲取ConfigurationChangeItem實例對象public static ConfigurationChangeItem obtain(Configuration config) {ConfigurationChangeItem instance = ObjectPool.obtain(ConfigurationChangeItem.class);if (instance == null) {instance = new ConfigurationChangeItem();}instance.mConfiguration = config;return instance;}@Overridepublic void execute(ClientTransactionHandler client, IBinder token,PendingTransactionActions pendingActions) {//注釋5,這里會觸發ActivityThreade的handleConfigurationChanged方法client.handleConfigurationChanged(mConfiguration);}
}public class ActivityRelaunchItem extends ActivityTransactionItem {@Overridepublic void execute(ClientTransactionHandler client, ActivityClientRecord r,PendingTransactionActions pendingActions) {if (mActivityClientRecord == null) {if (DEBUG_ORDER) Slog.d(TAG, "Activity relaunch cancelled");return;}Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");//注釋6,調用ActivityThread的handleRelaunchActivity方法client.handleRelaunchActivity(mActivityClientRecord, pendingActions);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);}
}public class ActivityConfigurationChangeItem extends ActivityTransactionItem {@Overridepublic void execute(ClientTransactionHandler client, ActivityClientRecord r,PendingTransactionActions pendingActions) {Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");//注釋7,調用ActivityThread的handleActivityConfigurationChanged方法client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY);Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);}
}

對以上代碼做個簡單概述。

  • WindowProcessController的onConfigurationChanged方法經過層層調用,最終會在注釋1處觸發一個關鍵調用,調用ATM的getLifecycleManager方法獲取ClientLifecycleManager實例,并調用該對象的scheduleTransaction方法,傳入ConfigurationChangeItem對象實例作為參數,這個我們后面還會提到。
  • ClientLifecycleManager的scheduleTransaction方法繼續往下走,會在注釋2處觸發IApplicationThread的scheduleTransaction方法,該方法位于父類ClientTransactionHandler中,在注釋3處調用sendMessage方法,發送ActivityThread.H.EXECUTE_TRANSACTION的消息類型,最終會走到注釋4處,調用TransactionExecutor的execute方法。
  • TransactionExecutor的execute方法繼續調用executeCallbacks方法,然后在注釋5處調用每個ClientTransactionItem對象實例的execute方法,我們在前面注釋1處提過到過ConfigurationChangeItem對象作為參數。
  • 默認情況下在UIMode發生切換的時候,這里的ClientTransactionItem對象實例其實是ActivityRelaunchItem,執行execute方法,也就是注釋6處會觸發ActivityThread的handleRelaunchActivity,最終會觸發Activity的onCreate方法。
  • 如果Activity頁面不希望UIMode發生切換的時候重新執行onCreate方法,這里的ClientTransactionItem對象實例其實是ActivityConfigurationChangeItem,執行execute方法,也就是注釋7處會觸發ActivityThread的handleActivityConfigurationChanged,最終會觸發Activity的onConfigurationChanged方法。

三、ActivityThread通知應用

3.1 回調Activity的onCreate方法

public final class ActivityThread extends ClientTransactionHandlerimplements ActivityThreadInternal {@Overridepublic void handleRelaunchActivity(ActivityClientRecord tmp,PendingTransactionActions pendingActions) {...代碼省略...//注釋1,調用handleRelaunchActivityInner方法handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");...代碼省略...}private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,PendingTransactionActions pendingActions, boolean startsNotResumed,Configuration overrideConfig, String reason) {// Preserve last used intent, it may be set from Activity#setIntent().final Intent customIntent = r.activity.mIntent;// Need to ensure state is saved.if (!r.paused) {performPauseActivity(r, false, reason, null /* pendingActions */);}if (!r.stopped) {callActivityOnStop(r, true /* saveState */, reason);}//注釋2,調用handleDestroyActivityhandleDestroyActivity(r, false, configChanges, true, reason);r.activity = null;r.window = null;r.hideForNow = false;r.nextIdle = null;// Merge any pending results and pending intents; don't just replace themif (pendingResults != null) {if (r.pendingResults == null) {r.pendingResults = pendingResults;} else {r.pendingResults.addAll(pendingResults);}}if (pendingIntents != null) {if (r.pendingIntents == null) {r.pendingIntents = pendingIntents;} else {r.pendingIntents.addAll(pendingIntents);}}r.startsNotResumed = startsNotResumed;r.overrideConfig = overrideConfig;//注釋3,調用handleLaunchActivity方法handleLaunchActivity(r, pendingActions, customIntent);}@Overridepublic void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,...代碼省略...WindowManager wm = r.activity.getWindowManager();View v = r.activity.mDecor;...代碼省略...//將當前activity對應的viwe從WMS中移除wm.removeViewImmediate(v);...代碼省略...}}
}

注釋1處繼續調用handleRelaunchActivityInner方法;
注釋2處調用handleDestroyActivity方法將當前Activity對應的視圖從WMS中移除。
注釋3處調用handleLaunchActivity方法將當前Activity對應的視圖重新添加到WMS中。

3.2 回調Activity的onConfigurationChanged方法

public final class ActivityThread extends ClientTransactionHandlerimplements ActivityThreadInternal {public void handleActivityConfigurationChanged(ActivityClientRecord r,@NonNull Configuration overrideConfig, int displayId) {...代碼省略...r.overrideConfig = overrideConfig;final ViewRootImpl viewRoot = r.activity.mDecor != null? r.activity.mDecor.getViewRootImpl() : null;//注釋1,調用performConfigurationChangedForActivity方法final Configuration reportedConfig = performConfigurationChangedForActivity(r,mConfigurationController.getCompatConfiguration(),movedToDifferentDisplay ? displayId : r.activity.getDisplayId());if (viewRoot != null) {if (movedToDifferentDisplay) {viewRoot.onMovedToDisplay(displayId, reportedConfig);}viewRoot.updateConfiguration(displayId);}mSomeActivitiesChanged = true;}private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,Configuration newBaseConfig, int displayId) {r.tmpConfig.setTo(newBaseConfig);if (r.overrideConfig != null) {r.tmpConfig.updateFrom(r.overrideConfig);}//注釋2,調用performActivityConfigurationChanged方法final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,r.tmpConfig, r.overrideConfig, displayId);freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));return reportedConfig;}private Configuration performActivityConfigurationChanged(Activity activity,Configuration newConfig, Configuration amOverrideConfig, int displayId) {...代碼省略...//調用activity的onConfigurationChanged方法activity.onConfigurationChanged(configToReport);...代碼省略...}
}

注釋1處繼續調用performConfigurationChangedForActivity方法;
注釋2處調用performActivityConfigurationChanged方法;
注釋3處調用Activity的onConfigurationChanged方法。

參考文章:Android 13 深色主題切換流程

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/62226.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/62226.shtml
英文地址,請注明出處:http://en.pswp.cn/web/62226.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

河南省的教育部科技查新工作站有哪些?

鄭州大學圖書館&#xff08;Z12&#xff09;&#xff1a;2007年1月被批準設立“教育部綜合類科技查新工作站”&#xff0c;同年12月被河南省科技廳認定為河南省省級科技查新機構。主要面向河南省的高校、科研機構、企業提供科技查新、查收查引等服務。 河南大學圖書館&#xf…

Leetcode經典題6--買賣股票的最佳時機

買賣股票的最佳時機 題目描述&#xff1a; 給定一個數組 prices &#xff0c;它的第 i 個元素 prices[i] 表示一支給定股票第 i 天的價格。 你只能選擇 某一天 買入這只股票&#xff0c;并選擇在 未來的某一個不同的日子 賣出該股票。設計一個算法來計算你所能獲取的最大利潤。…

MCPTT 與BTC

MCPTT&#xff08;Mission Critical Push-to-Talk&#xff09;和B-TrunC&#xff08;寬帶集群&#xff09;是兩種關鍵通信標準&#xff0c;它們分別由不同的組織制定和推廣。 MCPTT&#xff08;Mission Critical Push-to-Talk&#xff09;標準由3GPP&#xff08;第三代合作伙伴…

去除賬號密碼自動賦值時的輸入框背景色

問題描述&#xff1a; 前端使用賬號密碼登錄&#xff0c;若在網頁保存過當前頁面的密碼和賬號&#xff0c;那么當再次進入該頁面&#xff0c;網頁會自動的把賬號和密碼賦到輸入框中&#xff0c;而此時輸入框是帶有背景色的&#xff0c;與周邊的白色背景顯得很不協調&#xff1…

【Pytorch】torch.reshape與torch.Tensor.reshape區別

問題引入&#xff1a; 在Pytorch文檔中&#xff0c;有torch.reshape與torch.Tensor.reshape兩個reshape操作&#xff0c;他們的區別是什么呢&#xff1f; 我們先來看一下官方文檔的定義&#xff1a; torch.reshape&#xff1a; torch.Tensor.reshape: 解釋&#xff1a; 在p…

掃碼與短信驗證碼登錄JS逆向分析與Python純算法還原

文章目錄 1. 寫在前面2. 掃碼接口分析2. 短信接口分析3. 加密算法還原【??作者主頁】:吳秋霖 【??作者介紹】:擅長爬蟲與JS加密逆向分析!Python領域優質創作者、CSDN博客專家、阿里云博客專家、華為云享專家。一路走來長期堅守并致力于Python與爬蟲領域研究與開發工作!…

spring6:3容器:IoC

spring6&#xff1a;3容器&#xff1a;IoC 目錄 spring6&#xff1a;3容器&#xff1a;IoC3、容器&#xff1a;IoC3.1、IoC容器3.1.1、控制反轉&#xff08;IoC&#xff09;3.1.2、依賴注入3.1.3、IoC容器在Spring的實現 3.2、基于XML管理Bean3.2.1、搭建子模塊spring6-ioc-xml…

【認證法規】安全隔離變壓器

文章目錄 定義反激電源變壓器 定義 安全隔離變壓器&#xff08;safety isolating transformer&#xff09;&#xff0c;通過至少相當于雙重絕緣或加強絕緣的絕緣使輸入繞組與輸出繞組在電氣上分開的變壓器。這種變壓器是為以安全特低電壓向配電電路、電器或其它設備供電而設計…

車機端同步outlook日歷

最近在開發一個車機上的日歷助手&#xff0c;其中一個需求就是要實現手機端日歷和車機端日歷數據的同步。然而這種需求似乎沒辦法實現&#xff0c;畢竟手機日歷是手機廠商自己帶的系統應用&#xff0c;根本不能和車機端實現數據同步的。 那么只能去其他公共的平臺尋求一些機會&…

OpenCV-圖像閾值

簡單閾值法 此方法是直截了當的。如果像素值大于閾值&#xff0c;則會被賦為一個值&#xff08;可能為白色&#xff09;&#xff0c;否則會賦為另一個值&#xff08;可能為黑色&#xff09;。使用的函數是 cv.threshold。第一個參數是源圖像&#xff0c;它應該是灰度圖像。第二…

力扣300.最長遞增子序列

題目描述 題目鏈接300. 最長遞增子序列 給你一個整數數組 nums &#xff0c;找到其中最長嚴格遞增子序列的長度。 子序列 是由數組派生而來的序列&#xff0c;刪除&#xff08;或不刪除&#xff09;數組中的元素而不改變其余元素的順序。例如&#xff0c;[3,6,2,7] 是數組 […

Vue CLI的作用

Vue CLI&#xff08;Command Line Interface&#xff09;是一個基于Vue.js的官方腳手架工具&#xff0c;其主要作用是幫助開發者快速搭建Vue項目的基礎結構和開發環境。以下是Vue CLI的具體作用&#xff1a; 1、項目模板與快速生成 Vue CLI提供了一系列預設的項目模板&#x…

【藍橋杯每日一題】掃雷

掃雷 知識點 2024-12-3 藍橋杯每日一題 掃雷 dfs &#xff08;bfs也是可行的&#xff09; 題目大意 在一個二維平面上放置這N個炸雷&#xff0c;每個炸雷的信息有$(x_i,y_i,r_i) $&#xff0c;前兩個是坐標信息&#xff0c;第三個是爆炸半徑。然后會輸入M個排雷火箭&#xff0…

【大數據學習 | 面經】Spark 3.x 中的AQE(自適應查詢執行)

Spark 3.x 中的自適應查詢執行&#xff08;Adaptive Query Execution&#xff0c;簡稱 AQE&#xff09;通過多種方式提升性能&#xff0c;主要包括以下幾個方面&#xff1a; 動態合并 Shuffle 分區&#xff08;Coalescing Post Shuffle Partitions&#xff09;&#xff1a; 當 …

城電科技 | 光伏景觀長廊 打造美麗鄉村綠色低碳示范區 光伏景觀設計方案

光伏景觀長廊是一種結合了光伏發電技術和零碳景觀設計的新型公共公共設施&#xff0c;光伏景觀長廊頂上的光伏板不僅可以為周邊用電設備提供清潔電能&#xff0c;而且還能作為遮陽設施使用&#xff0c;為人們提供一個美麗又實用的休閑娛樂空間。 光伏景觀長廊建設對打造美麗鄉…

開發系統準備與開發環境配置總結

開發前系統配置及環境搭建 系統配置0 Github打不開、速度慢怎么辦1 WSL、Linux、Ubuntu、Docker都是什么鬼2 在Windows下安裝WSL和Ubuntu3 配置MySQL4 配置Redis并啟動服務5 Docker&#xff08;Windows和Ubuntu下&#xff09;6 Nginx 系統配置 你好&#xff01; 這是你第一次使…

uniapp 添加loading

在uniapp中添加loading可以使用uni的API uni.showLoading 方法。以下是一個簡單的示例代碼 // 顯示loading uni.showLoading({title: 加載中 });// 假設這里是異步操作&#xff0c;比如網絡請求 setTimeout(function () {// 隱藏loadinguni.hideLoading(); }, 2000);

C++(九)

前言&#xff1a; 本文主要講述運算符的優先順序。 一&#xff0c;運算符的優先級。 請看以下表達式&#xff1a; a32*5 運算結果為&#xff1a;13. 可以看到&#xff0c;在此代碼中&#xff0c;先運行了2*5的結果&#xff0c;在此基礎上在進行3操作&#xff0c;因此結果…

Android 拍照(有無存儲權限兩種方案,兼容Q及以上版本)

在某些行業&#xff0c;APP可能被禁止使用存儲權限&#xff0c;或公司在寫SDK功能&#xff0c;不方便獲取權限 所以需要有 無存儲權限拍照方案。這里兩種方案都列出里。 對于寫入權限&#xff0c;在高版本中&#xff0c;已經廢棄&#xff0c; 不可用文件寫入讀取權限&#xf…

【Altium Designer 】AD如何使用嘉立創元器件的3D封裝

1.下載3D封裝 以STM32F407VGT6為例&#xff0c;進入嘉立創商城網站&#xff0c;找到需要的元器件封裝 復制編號&#xff0c;打開嘉立創EDA&#xff0c;編譯器選擇專業版&#xff0c;新建工程&#xff0c;點擊PCB1 復制編號在搜索框中&#xff0c;點擊搜索&#xff0c;然后放置…