当前位置: 首页 > news >正文

Android 12系统源码_多屏幕(二)模拟辅助设备功能开关实现原理

前言

上一篇我们通过为Android系统开启模拟辅助设备功能开关,最终实现了将一个Activity显示到多个屏幕的效果。
模拟辅助设备功能开关
开启一块新的虚拟屏幕设备
本篇文章我们具体来分析一下当我们开启模拟辅助设备功能开关的时候,Android系统做了什么哪些操作。

一、模拟辅助设备功能开关应用位置

Android12系统中,车机系统有一个专门的开发者选项页面,其完整名称如下:

com.android.car.developeroptions/com.android.car.developeroptions.CarDevelopmentSettingsDashboardActivity

可以发现此Activity对应的包名为com.android.car.developeroptions,输入adb命令:

adb shell pm path com.android.car.developeroptions

返回的结果是:

/system_ext/priv-app/CarDeveloperOptions/CarDeveloperOptions.apk

可以发现是一个名为CarDeveloperOptions的车机系统应用,直接在aosp源码中进行搜索,搜索结果如下:
在这里插入图片描述
可以发现这个系统应用位于/packages/services/Car/packages/CarDeveloperOptions目录。

二、模拟辅助设备功能开关相关源码

2.1 系统开发者选项对应的页面声明

CarDeveloperOptions系统应用的AndroidManifest.xml文件中对开发者选项页面的声明如下。

services/Car/packages/CarDeveloperOptions/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"xmlns:tools="http://schemas.android.com/tools"coreApp="true"package="com.android.car.developeroptions"android:sharedUserId="android.uid.system">...代码省略...<activityandroid:name=".CarDevelopmentSettingsDashboardActivity"android:enabled="false"android:exported="true"android:icon="@drawable/ic_settings_development"android:label="@string/development_settings_title"android:taskAffinity=""android:theme="@style/Theme.CarDeveloperOptions"><intent-filter android:priority="1"><action android:name="android.settings.APPLICATION_DEVELOPMENT_SETTINGS"/><action android:name="com.android.settings.APPLICATION_DEVELOPMENT_SETTINGS"/><category android:name="android.intent.category.DEFAULT"/></intent-filter><meta-data android:name="com.android.settings.summary"android:resource="@string/summary_empty"/><meta-data android:name="com.android.settings.FRAGMENT_CLASS"android:value="com.android.car.developeroptions.CarDevelopmentSettingsDashboardFragment"/><meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"android:value="true"/></activity>...代码省略...
</manifest>

2.2 系统开发者选项对应的Activity

1、CarDevelopmentSettingsDashboardActivity的系统源码非常简洁。

services/Car/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentSettingsDashboardActivity.java

public class CarDevelopmentSettingsDashboardActivity extends SettingsActivity {private static final String CAR_DEVELOPMENT_SETTINGS_FRAGMENT ="com.android.car.developeroptions.CarDevelopmentSettingsDashboardFragment";@Overrideprotected boolean isValidFragment(String fragmentName) {return CAR_DEVELOPMENT_SETTINGS_FRAGMENT.equals(fragmentName);}@Overrideprotected boolean isToolbarEnabled() {// Disable the default Settings toolbar in favor of a chassis toolbar.return false;}
}

此类中的源码非常简单,最关键的就是CAR_DEVELOPMENT_SETTINGS_FRAGMENT 这个字段,该字段指向了一个Fragment,该Fragment才是开发者选项页面的真正载体,

2、想要明白CarDevelopmentSettingsDashboardActivity页面的具体加载流程,我们有必要看下其父类SettingsActivity 。

packages/apps/Settings/src/com/android/settings/SettingsActivity.java

public class SettingsActivity extends SettingsBaseActivityimplements PreferenceManager.OnPreferenceTreeClickListener,PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,ButtonBarHandler, FragmentManager.OnBackStackChangedListener {@Overrideprotected void onCreate(Bundle savedState) {...代码省略...//加载布局文件setContentView(R.layout.settings_main_prefs);...代码省略...//获取页面参数final String initialFragmentName = getInitialFragmentName(intent);...代码省略...//加载设置模块具体页面对应的fragmentlaunchSettingFragment(initialFragmentName, intent);...代码省略...}/*** 将initialFragmentName指向的fragment加载到当前Activity中*/void launchSettingFragment(String initialFragmentName, Intent intent) {if (initialFragmentName != null) {setTitleFromIntent(intent);Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);switchToFragment(initialFragmentName, initialArguments, true,mInitialTitleResId, mInitialTitle);} else {// Show search icon as up affordance if we are displaying the main DashboardmInitialTitleResId = R.string.dashboard_title;switchToFragment(TopLevelSettings.class.getName(), null /* args */, false,mInitialTitleResId, mInitialTitle);}}/*** 将fragmentName指向的fragment加载到当前Activity中*/private void switchToFragment(String fragmentName, Bundle args, boolean validate,int titleResId, CharSequence title) {Log.d(LOG_TAG, "Switching to fragment " + fragmentName);if (validate && !isValidFragment(fragmentName)) {throw new IllegalArgumentException("Invalid fragment for this activity: "+ fragmentName);}Fragment f = Utils.getTargetFragment(this, fragmentName, args);if (f == null) {return;}FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();transaction.replace(R.id.main_content, f);if (titleResId > 0) {transaction.setBreadCrumbTitle(titleResId);} else if (title != null) {transaction.setBreadCrumbTitle(title);}transaction.commitAllowingStateLoss();getSupportFragmentManager().executePendingTransactions();Log.d(LOG_TAG, "Executed frag manager pendingTransactions");}
}

packages/apps/Settings/res/layout/settings_main_prefs.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_height="match_parent"android:layout_width="match_parent"><com.android.settings.widget.SettingsMainSwitchBarandroid:id="@+id/switch_bar"android:visibility="gone"android:layout_width="match_parent"android:layout_height="wrap_content"/><FrameLayoutandroid:id="@+id/main_content"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"/><RelativeLayout android:id="@+id/button_bar"android:layout_height="wrap_content"android:layout_width="match_parent"android:layout_weight="0"android:visibility="gone"><Button android:id="@+id/back_button"android:layout_width="150dip"android:layout_height="wrap_content"android:layout_margin="5dip"android:layout_alignParentStart="true"android:text="@*android:string/back_button_label"/><LinearLayoutandroid:orientation="horizontal"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentEnd="true"><Button android:id="@+id/skip_button"android:layout_width="150dip"android:layout_height="wrap_content"android:layout_margin="5dip"android:text="@*android:string/skip_button_label"android:visibility="gone"/><Button android:id="@+id/next_button"android:layout_width="150dip"android:layout_height="wrap_content"android:layout_margin="5dip"android:text="@*android:string/next_button_label"/></LinearLayout></RelativeLayout></LinearLayout>

上面我们列出了SettingsActivity和UI加载相关的源码,可以发现SettingsActivity先是加载了一个名为settings_main_prefs的布局文件,然后将initialFragmentName指向的fragment添加到了当前页面上。结合CarDevelopmentSettingsDashboardActivity的源码我们可以知道,开发者选项页面的真正载体是CarDevelopmentSettingsDashboardFragment。

packages/services/Car/packages/CarDeveloperOptions/src/com/android/car/developeroptions/CarDevelopmentSettingsDashboardFragment.java

public class CarDevelopmentSettingsDashboardFragment extends DevelopmentSettingsDashboardFragment {}

packages/apps/Settings/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java

public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragmentimplements OnMainSwitchChangeListener, OemUnlockDialogHost, AdbDialogHost,AdbClearKeysDialogHost, LogPersistDialogHost,BluetoothA2dpHwOffloadRebootDialog.OnA2dpHwDialogConfirmedListener,AbstractBluetoothPreferenceController.Callback {@Overrideprotected int getPreferenceScreenResId() {return Utils.isMonkeyRunning() ? R.xml.placeholder_prefs : R.xml.development_settings;//页面对应的布局文件}private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,Activity activity, Lifecycle lifecycle, DevelopmentSettingsDashboardFragment fragment,BluetoothA2dpConfigStore bluetoothA2dpConfigStore) {final List<AbstractPreferenceController> controllers = new ArrayList<>();...代码省略...controllers.add(new SecondaryDisplayPreferenceController(context));//模拟辅助设备功能组件控制器controllers.add(new GpuViewUpdatesPreferenceController(context));controllers.add(new HardwareLayersUpdatesPreferenceController(context));controllers.add(new DebugGpuOverdrawPreferenceController(context));controllers.add(new DebugNonRectClipOperationsPreferenceController(context));controllers.add(new ForceDarkPreferenceController(context));controllers.add(new EnableBlursPreferenceController(context));controllers.add(new ForceMSAAPreferenceController(context));controllers.add(new HardwareOverlaysPreferenceController(context));controllers.add(new SimulateColorSpacePreferenceController(context));controllers.add(new UsbAudioRoutingPreferenceController(context));controllers.add(new StrictModePreferenceController(context));controllers.add(new ProfileGpuRenderingPreferenceController(context));controllers.add(new KeepActivitiesPreferenceController(context));controllers.add(new BackgroundProcessLimitPreferenceController(context));controllers.add(new CachedAppsFreezerPreferenceController(context));controllers.add(new ShowFirstCrashDialogPreferenceController(context));controllers.add(new AppsNotRespondingPreferenceController(context));controllers.add(new NotificationChannelWarningsPreferenceController(context));controllers.add(new AllowAppsOnExternalPreferenceController(context));controllers.add(new ResizableActivityPreferenceController(context));controllers.add(new FreeformWindowsPreferenceController(context));controllers.add(new DesktopModePreferenceController(context));controllers.add(new NonResizableMultiWindowPreferenceController(context));controllers.add(new ShortcutManagerThrottlingPreferenceController(context));controllers.add(new EnableGnssRawMeasFullTrackingPreferenceController(context));controllers.add(new DefaultLaunchPreferenceController(context, "running_apps"));controllers.add(new DefaultLaunchPreferenceController(context, "demo_mode"));controllers.add(new DefaultLaunchPreferenceController(context, "quick_settings_tiles"));controllers.add(new DefaultLaunchPreferenceController(context, "feature_flags_dashboard"));controllers.add(new DefaultUsbConfigurationPreferenceController(context));controllers.add(new DefaultLaunchPreferenceController(context, "density"));controllers.add(new DefaultLaunchPreferenceController(context, "background_check"));controllers.add(new DefaultLaunchPreferenceController(context, "inactive_apps"));controllers.add(new AutofillLoggingLevelPreferenceController(context, lifecycle));controllers.add(new AutofillResetOptionsPreferenceController(context));controllers.add(new BluetoothCodecDialogPreferenceController(context, lifecycle,bluetoothA2dpConfigStore, fragment));controllers.add(new BluetoothSampleRateDialogPreferenceController(context, lifecycle,bluetoothA2dpConfigStore));controllers.add(new BluetoothBitPerSampleDialogPreferenceController(context, lifecycle,bluetoothA2dpConfigStore));controllers.add(new BluetoothQualityDialogPreferenceController(context, lifecycle,bluetoothA2dpConfigStore));controllers.add(new BluetoothChannelModeDialogPreferenceController(context, lifecycle,bluetoothA2dpConfigStore));controllers.add(new BluetoothHDAudioPreferenceController(context, lifecycle,bluetoothA2dpConfigStore, fragment));controllers.add(new SharedDataPreferenceController(context));controllers.add(new OverlaySettingsPreferenceController(context));return controllers;}
}

2.3 模拟辅助显示设备功能开关

1、由于CarDevelopmentSettingsDashboardFragment构建页面也和其他Settings模块的页面一样,大量使用了Preference这套组件来构建页面,如果对于Preference完全不了解,可以参考一下Android 12系统源码_Settings(一)认识Preference这篇文章。
由于Preference构建视图和常见的Android构建视图的方案有很大差异,要想使用Android那套UI架构来分析Settings模块的源码基本不可行,这里我们直接在aosp中搜索“模拟辅助显示设备”这几个字,搜索结果如下所示。
在这里插入图片描述
可以发现“模拟辅助显示设备”这个字符串对应的资源名称为overlay_display_devices_title。

2、继续在aosp中进行类型为.xml,名称为overlay_display_devices_title的资源的搜索,会发现development_settings.xml这个文件有引用。
aosp搜索结果

packages/apps/Settings/res/xml/development_settings.xml

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"xmlns:settings="http://schemas.android.com/apk/res-auto"android:key="development_prefs_screen"android:title="@string/development_settings_title">...代码省略...<PreferenceCategoryandroid:key="debug_drawing_category"android:title="@string/debug_drawing_category"android:order="600">...代码省略...<!--模拟辅助显示设备功能开关--><ListPreferenceandroid:key="overlay_display_devices"android:title="@string/overlay_display_devices_title"android:entries="@array/overlay_display_devices_entries"android:entryValues="@array/overlay_display_devices_values" />...代码省略...</PreferenceCategory>...代码省略...
</PreferenceScreen>

base/packages/SettingsLib/res/values-zh-rCN/arrays.xml

    <!-- 模拟辅助设备的条目标题 --><string-array name="overlay_display_devices_entries"><item msgid="4497393944195787240">"无"</item><item msgid="8461943978957133391">"480p"</item><item msgid="6923083594932909205">"480p(安全)"</item><item msgid="1226941831391497335">"720p"</item><item msgid="7051983425968643928">"720p(安全)"</item><item msgid="7765795608738980305">"1080p"</item><item msgid="8084293856795803592">"1080p(安全)"</item><item msgid="938784192903353277">"4K"</item><item msgid="8612549335720461635">"4K(安全)"</item><item msgid="7322156123728520872">"4K(画质提升)"</item><item msgid="7735692090314849188">"4K(画质提升、安全)"</item><item msgid="7346816300608639624">"720p,1080p(双屏)"</item></string-array>

base/packages/SettingsLib/res/values/arrays.xml

    <!-- 模拟辅助设备的条目属性值 --><string-array name="overlay_display_devices_values" translatable="false" ><item></item><item>720x480/142</item><item>720x480/142,secure</item><item>1280x720/213</item><item>1280x720/213,secure</item><item>1920x1080/320</item><item>1920x1080/320,secure</item><item>3840x2160/320</item><item>3840x2160/320,secure</item><item>1920x1080/320|3840x2160/640</item><item>1920x1080/320|3840x2160/640,secure</item><item>1280x720/213;1920x1080/320</item></string-array>

结合布局文件可知,key值为overlay_display_devices的ListPreference组件就是我们要找的模拟辅助显示设备功能开关组件,其功能开关子条目标题和属性值刚好对应了

3、进一步在aosp中进行类型为.java,名称为overlay_display_devices的资源的搜索,会发现SecondaryDisplayPreferenceController.java这个类有引用,前面承载开发者设置页面内容的DevelopmentSettingsDashboardFragment里面就有引用到这个类。
在这里插入图片描述

/packages/apps/Settings/src/com/android/settings/development/SecondaryDisplayPreferenceController.java

public class SecondaryDisplayPreferenceController extends DeveloperOptionsPreferenceControllerimplements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {private static final String OVERLAY_DISPLAY_DEVICES_KEY = "overlay_display_devices";private final String[] mListValues;private final String[] mListSummaries;public SecondaryDisplayPreferenceController(Context context) {super(context);mListValues = context.getResources().getStringArray(R.array.overlay_display_devices_values);mListSummaries = context.getResources().getStringArray(R.array.overlay_display_devices_entries);}@Overridepublic String getPreferenceKey() {return OVERLAY_DISPLAY_DEVICES_KEY;//preference组件的唯一key值}@Overridepublic boolean onPreferenceChange(Preference preference, Object newValue) {//用户选择了条目内容,对开关属性进行更新和数据保存writeSecondaryDisplayDevicesOption(newValue.toString());return true;}@Overridepublic void updateState(Preference preference) {//初始化模拟辅助设备功能更开关的属性值updateSecondaryDisplayDevicesOptions();}@Overrideprotected void onDeveloperOptionsSwitchDisabled() {super.onDeveloperOptionsSwitchDisabled();writeSecondaryDisplayDevicesOption(null);}private void updateSecondaryDisplayDevicesOptions() {//从global中获取当前模拟辅助设备功能开关的属性值final String value = Settings.Global.getString(mContext.getContentResolver(),Settings.Global.OVERLAY_DISPLAY_DEVICES);//获取当前选中的条目序列号int index = 0; // defaultfor (int i = 0; i < mListValues.length; i++) {if (TextUtils.equals(value, mListValues[i])) {index = i;break;}}final ListPreference listPreference = (ListPreference) mPreference;//设置模拟辅助设备功能开关菜单条目列表中当前选中的条目listPreference.setValue(mListValues[index]);listPreference.setSummary(mListSummaries[index]);}private void writeSecondaryDisplayDevicesOption(String newValue) {//更新模拟辅助设备功能开关的属性值到global里面Settings.Global.putString(mContext.getContentResolver(),Settings.Global.OVERLAY_DISPLAY_DEVICES, newValue);updateSecondaryDisplayDevicesOptions();}
}
public final class Settings {@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)@TestApi@Readablepublic static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";//模拟辅助设备功能开关属性对应的字段
}

用户对模拟辅助设备功能开关的操作最终会触发SecondaryDisplayPreferenceController的onPreferenceChange方法回调,该方法调用writeSecondaryDisplayDevicesOption将当前用户的选择是开关属性值以key值为overlay_display_devices保存到了global里面,这就意味着我们通过模拟辅助设备功能开关,最终就只是将一串字符串存储到了key为overlay_display_devices的值的global内容。

三、模拟辅助设备功能开关监听者

1、OverlayDisplayAdapter类中有对global的overlay_display_devices字段的变化做监听,这样该字段发生变化的时候可以收到回调。

frameworks/base/services/core/java/com/android/server/display/OverlayDisplayAdapter.java

final class OverlayDisplayAdapter extends DisplayAdapter {private final Handler mUiHandler;// Called with SyncRoot lock held.public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener, Handler uiHandler) {super(syncRoot, context, handler, listener, TAG);mUiHandler = uiHandler;}@Overridepublic void registerLocked() {super.registerLocked();getHandler().post(new Runnable() {@Overridepublic void run() {//注册监听overlay_display_devices字段的内容变化getContext().getContentResolver().registerContentObserver(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),true, new ContentObserver(getHandler()) {@Overridepublic void onChange(boolean selfChange) {	//触发回调updateOverlayDisplayDevices();}});updateOverlayDisplayDevices();}});}private void updateOverlayDisplayDevices() {synchronized (getSyncRoot()) {//继续调用updateOverlayDisplayDevicesLocked方法updateOverlayDisplayDevicesLocked();}}}

2、global的overlay_display_devices字段内容发生变化的时候,会回调OverlayDisplayAdapter的updateOverlayDisplayDevices方法。
该方法上锁之后继续调用updateOverlayDisplayDevicesLocked方法。

final class OverlayDisplayAdapter extends DisplayAdapter {private final ArrayList<OverlayDisplayHandle> mOverlays =new ArrayList<OverlayDisplayHandle>();private String mCurrentOverlaySetting = "";//当前的模拟辅助设备属性值private void updateOverlayDisplayDevicesLocked() {String value = Settings.Global.getString(getContext().getContentResolver(),Settings.Global.OVERLAY_DISPLAY_DEVICES);if (value == null) {value = "";}if (value.equals(mCurrentOverlaySetting)) {return;}mCurrentOverlaySetting = value;if (!mOverlays.isEmpty()) {Slog.i(TAG, "Dismissing all overlay display devices.");for (OverlayDisplayHandle overlay : mOverlays) {overlay.dismissLocked();}mOverlays.clear();}int count = 0;for (String part : value.split(DISPLAY_SPLITTER)) {Matcher displayMatcher = DISPLAY_PATTERN.matcher(part);if (displayMatcher.matches()) {if (count >= 4) {Slog.w(TAG, "Too many overlay display devices specified: " + value);break;}String modeString = displayMatcher.group(1);String flagString = displayMatcher.group(2);ArrayList<OverlayMode> modes = new ArrayList<>();for (String mode : modeString.split(MODE_SPLITTER)) {Matcher modeMatcher = MODE_PATTERN.matcher(mode);if (modeMatcher.matches()) {try {int width = Integer.parseInt(modeMatcher.group(1), 10);int height = Integer.parseInt(modeMatcher.group(2), 10);int densityDpi = Integer.parseInt(modeMatcher.group(3), 10);if (width >= MIN_WIDTH && width <= MAX_WIDTH&& height >= MIN_HEIGHT && height <= MAX_HEIGHT&& densityDpi >= DisplayMetrics.DENSITY_LOW&& densityDpi <= DisplayMetrics.DENSITY_XXXHIGH) {modes.add(new OverlayMode(width, height, densityDpi));continue;} else {Slog.w(TAG, "Ignoring out-of-range overlay display mode: " + mode);}} catch (NumberFormatException ex) {}} else if (mode.isEmpty()) {continue;}}if (!modes.isEmpty()) {int number = ++count;String name = getContext().getResources().getString(com.android.internal.R.string.display_manager_overlay_display_name,number);int gravity = chooseOverlayGravity(number);OverlayFlags flags = OverlayFlags.parseFlags(flagString);Slog.i(TAG, "Showing overlay display device #" + number+ ": name=" + name + ", modes=" + Arrays.toString(modes.toArray())+ ", flags=" + flags);mOverlays.add(new OverlayDisplayHandle(name, modes, gravity, flags, number));continue;}}Slog.w(TAG, "Malformed overlay display devices setting: " + value);}}}

相关文章:

Android 12系统源码_多屏幕(二)模拟辅助设备功能开关实现原理

前言 上一篇我们通过为Android系统开启模拟辅助设备功能开关&#xff0c;最终实现了将一个Activity显示到多个屏幕的效果。 本篇文章我们具体来分析一下当我们开启模拟辅助设备功能开关的时候&#xff0c;Android系统做了什么哪些操作。 一、模拟辅助设备功能开关应用位置 …...

【Go语言初探】(二)、项目文件结构和GOPATH设置

一、go语言项目文件结构 由go/bin、go/src和go/pkg三个子文件夹组成&#xff0c;见下图&#xff1a; 实际项目&#xff1a; 二、gopath路径变量设置 在项目中创建main.go文件后&#xff0c;IDE会提示设置GOPATH路径&#xff1a; 点击“configure GOPATH”&#xff0c;设置GOP…...

三种简单排序:插入排序、冒泡排序与选择排序 【算法 05】

三种简单排序&#xff1a;插入排序、冒泡排序与选择排序 在编程中&#xff0c;排序算法是基础且重要的知识点。虽然在实际开发中&#xff0c;我们可能会直接使用标准库中的排序函数&#xff08;如C的std::sort&#xff09;&#xff0c;但了解并实现这些基础排序算法对于理解算法…...

Python -- GUI图形界面编程—GUI编程实例 博主也在持续学习中[ 持续更新中!!! 欢迎白嫖 也求粉啊啊啊~ ]

本文介绍了GUI的图形界面编程&#xff08;相关视频是哔站上的应该搜这个题目就能找到&#xff09;&#xff0c;文章还是很基础的&#xff0c;反正我是小白从0开始&#xff0c;主要的结构tinkter库、重要组件简介&#xff08;这个不用死记硬背 用的时候再说&#xff09;、Label&…...

Vue2和Vue3中的diff算法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、diff算法是什么&#xff1f;二、vue2中的diff算法三、vue3中的diff算法总结 前言 一、diff算法是什么&#xff1f; diff算法很早就存在了&#xff0c;一开…...

springboot使用aop或Jackson进行数据脱敏

1.aop 启动类加EnableAspectJAutoProxy 自定义注解&#xff0c;在实体类中使用表示被脱敏字段 建立aop切面类 可能这里gpt会建议你用Pointcut("execution(public * com.xx.aop..*.get*(..))")这种方式拦截&#xff0c;这种我试了&#xff0c;拦截不住。猜测在mvc返…...

【Solidity】基础介绍

数据类型 值类型 值类型的变量在赋值或作为函数参数传递时会被复制。 布尔类型&#xff1a;bool整数类型&#xff1a; 无符号&#xff1a;uint8、uint16、…、uint256 (uint256 可简写为 uint)有符号&#xff1a;int8、int16、…、int256 (int256可简写为 int) 地址类型&…...

【SpringBoot3】双向实时通讯 websocket

文章目录 一、Websocket使用步骤二、示例1&#xff1a;继承抽象类 AbstractWebSocketHandler后端代码前端代码 三、示例2&#xff1a;使用注解ServerEndpoint后端代码前端代码 四、前端代码封装 一、Websocket使用步骤 在Spring Boot中使用WebSocket是一个常见的需求&#xff…...

搭建内网开发环境(一)|基于docker快速部署开发环境

引言 最近因需要搭建一套简易版的纯内网的开发环境&#xff0c;服务器采用 centos8.0&#xff0c;容器化技术采用 docker 使用 docker-compose 进行容器编排。 该系列教程分为两大类&#xff1a; 软件安装和使用&#xff0c;这类是开发环境常用的软件部署和使用&#xff0c;涉…...

MATLAB R2023b配置Fortran编译器

MATLAB R2023b配置Fortran编译器 引言1. 安装Visual Studio 20192. 安装Intel API20243. 配置xml文件文件4. 设置环境变量5. MATLAB编译Fortran 引言 当我们需要用到MATLAB编译Fortran代码后进行调用计算时&#xff0c;整个配置流程较繁琐。下面以MATLAB R2023b为例&#xff0…...

2024新型数字政府综合解决方案(七)

新型数字政府综合解决方案通过集成人工智能、大数据、区块链和云计算技术&#xff0c;创建了一个高度智能化和互联互通的政府服务平台&#xff0c;旨在全面提升行政效率、服务质量和透明度。该平台实现了跨部门的数据整合与实时共享&#xff0c;利用人工智能进行智能决策支持和…...

搭建高可用k8s集群

高可用 Kubernetes V1.28.10 安装 文章目录 1. 环境介绍2. 准备工作2.1 修改主机名称2.2 修改hosts文件2.3 关闭防火墙和SLinux2.4 配置SSH免密访问2.4.1 主机名称: k8s-master-01 操作 2.5 配置yum源2.6 禁用Swarp分区2.7 同步时间2.8 配置内核转发及网桥过滤2.9 安装 IPVS 3…...

完美解决html2canvas + jsPDF导出pdf分页内容截断问题

代码地址&#xff1a;https://github.com/HFQ12333/export-pdf.git html2canvas jspdf方案是前端实现页面打印的一种常用方案&#xff0c;但是在实践过程中&#xff0c;遇到的最大问题就是分页截断的问题&#xff1a;当页面元素超过一页A4纸的时候&#xff0c;连续的页面就会…...

14 地址映射

14 地址映射 1、地址划分2、相关函数2.1 ioremap/iounmap2.2 mmap地址映射 3、总结 1、地址划分 明确&#xff1a;在linux系统中,不管是应用程序还是驱动程序&#xff0c;都不允许直接访问外设的物理地址,要想访问必须将物理地址映射到用户虚拟地址或者内核虚拟地址&#xff0…...

Java Resilience4j-RateLimiter学习

一. 介绍 Resilience4j-RateLimiter 是 Resilience4j 中的一个限流模块&#xff0c;我们对 Resilience4j 的 CircuitBreaker、Retry 已经有了一定的了解&#xff0c;现在来学习 RateLimiter 限流器&#xff1b; 引入依赖&#xff1b; <dependency><groupId>io.g…...

Nginx--地址重写Rewrite

一、什么是Rewrite Rewrite对称URL Rewrite&#xff0c;即URL重写&#xff0c;就是把传入Web的请求重定向到其他URL的过程 URL Rewrite最常见的应用是URL伪静态化&#xff0c;是将动态页面显示为静态页面方式的一种技术。比如http://www.123.com/news/index.php?id123 使用U…...

webflux源码解析(1)-主流程

目录 1.关键实例的创建1.1 实例创建1.2 初始化 2.处理请求的关键流程2.1 从ReactorHttpHandlerAdapter开始2.1 DispatcherHandler的初始化2.2查找mapping handler2.3 处理请求(执行handler)2.4 返回结果处理 3.webflux的配置装配参考&#xff1a; WebFlux是Spring 5.0框架推出的…...

ipad作为扩展屏的最简单方式

将iPad用作扩展屏幕有几种简单而有效的方法。以下是几种常见的方式&#xff1a; 1. Sidecar&#xff08;苹果官方功能&#xff09; 适用设备&#xff1a;iPad和Mac&#xff08;macOS Catalina及以上版本&#xff09;。功能&#xff1a;Sidecar 是苹果官方的功能&#xff0c;可…...

【卡码网Python基础课 17.判断集合成员】

目录 题目描述与分析一、集合二、集合的常用方法三、代码编写 题目描述与分析 题目描述&#xff1a; 请你编写一个程序&#xff0c;判断给定的整数 n 是否存在于给定的集合中。 输入描述&#xff1a; 有多组测试数据&#xff0c;第一行有一个整数 k&#xff0c;代表有 k 组测…...

生物研究新范式!AI语言模型在生物研究中的应用

–https://doi.org/10.1038/s41592-024-02354-y 留意更多内容&#xff0c;欢迎关注微信公众号&#xff1a;组学之心 Language models for biological research: a primer 研究团队及研究单位 James Zou–Department of Biomedical Data Science, Stanford University, Stan…...

python语言day08 属性装饰器和property函数 异常关键字 约束

属性装饰器&#xff1a; 三个装饰器实现对私有化属性_creat_time的get&#xff0c;set&#xff0c;del方法&#xff1b; 三个装饰器下的方法名都一样&#xff0c;通过message.creat_time的不同操作实现调用get&#xff0c;set&#xff0c;del方法。 __inti__&#xff1a; 创建并…...

day01JS-数据类型-01

1. 浏览器内核 通常所谓的浏览器内核也就是浏览器所采用的渲染引擎&#xff0c;渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。不同的浏览器内核对网页编写语法的解释也有不同&#xff0c;因此同一网页在不同的内核的浏览器里的渲染&#xff08;显示&#xff09;…...

MATLAB 手动实现一种高度覆盖值提取建筑物点云的方法(74)

专栏往期文章,包含本章 MATLAB 手动实现一种高度覆盖值提取建筑物点云的方法(74) 一、算法介绍二、算法实现1.代码2.效果总结一、算法介绍 手动实现一种基于高度覆盖值的建筑物点云提取方法,适用于高大的城市建筑物,比只利用高度提取建筑物的方法更加稳定和具有价值,主要…...

git的下载与安装(Windows)

Git是一个开源的分布式版本控制系统&#xff08;Distributed Version Control System&#xff0c;简称DVCS&#xff09;&#xff0c;它以其高效、灵活和强大的功能&#xff0c;在现代软件开发中扮演着至关重要的角色。 git官网&#xff1a;Git (git-scm.com) 1.进入git官网 2…...

腾讯云AI代码助手 —— 编程新体验,智能编码新纪元

阅读导航 引言一、开发环境介绍1. 支持的编程语言2. 支持的集成开发环境&#xff08;IDE&#xff09; 二、腾讯云AI代码助手使用实例1. 开发环境配置2. 代码补全功能使用&#x1f4bb;自动生成单句代码&#x1f4bb;自动生成整个代码块 3. 技术对话3. 规范/修复错误代码4. 智能…...

使用 ESP32 和 TFT 屏幕显示实时天气信息 —— 基于 OpenWeatherMap API

实时监测环境数据是一个非常常见的应用场景&#xff0c;例如气象站、智能家居等。这篇博客将带你使用 ESP32 微控制器和一个 TFT 屏幕&#xff0c;实时显示当前城市的天气信息。通过 OpenWeatherMap API&#xff0c;我们能够获取诸如温度、天气情况以及经纬度等详细的天气数据&…...

高阶数据结构——B树

1. 常见的搜索结构 以上结构适合用于数据量相对不是很大&#xff0c;能够一次性存放在内存中&#xff0c;进行数据查找的场景。如果数据量很大&#xff0c;比如有100G数据&#xff0c;无法一次放进内存中&#xff0c;那就只能放在磁盘上了&#xff0c;如果放在磁盘上&#xff0…...

Vue2中watch与Vue3中watch对比和踩坑

上一节说到了 computed计算属性对比 &#xff0c;虽然计算属性在大多数情况下更合适&#xff0c;但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法&#xff0c;来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时&#…...

在Java程序中执行Linux命令

在Java中执行Linux命令通常涉及到使用Java的运行时类 (java.lang.Runtime) 或者 ProcessBuilder 类来启动一个外部进程 1. 使用 Runtime.exec() Runtime.exec() 方法可以用来执行一个外部程序。它返回一个 Process 对象&#xff0c;可以通过这个对象与外部程序交互&#xff0…...

微信小程序在不同移动设备上的差异导致原因

在写小程序的时候用了rpx自适应单位&#xff0c;但是还是出现了在不同机型上布局不统一的问题&#xff0c;在此记录一下在首页做一个输入框&#xff0c;在测试的时候&#xff0c;这个输入框在不同的机型上到处跑&#xff0c;后来排查了很久都不知道为什么会这样 解决办法是后 …...

快速体验fastllm安装部署并支持AMD ROCm推理加速

序言 fastllm是纯c实现&#xff0c;无第三方依赖的高性能大模型推理库。 本文以国产海光DCU为例&#xff0c;在AMD ROCm平台下编译部署fastllm以实现LLMs模型推理加速。 测试平台&#xff1a;曙光超算互联网平台SCNet GPU/DCU&#xff1a;异构加速卡AI 显存64GB PCIE&#…...

报错:java: javacTask: 源发行版 8 需要目标发行版 1.8

程序报错&#xff1a; Executing pre-compile tasks... Loading Ant configuration... Running Ant tasks... Running before tasks Checking sources Copying resources... [gulimail-coupon] Copying resources... [gulimail-common] Parsing java… [gulimail-common] java…...

【数据结构篇】~单链表(附源码)

【数据结构篇】~链表 链表前言链表的实现1.头文件2.源文件 链表前言 链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 1、链式机构在逻辑上是连续的&#xff0c;在物理结构上不一定连续​ 2、结点一般是从…...

旋转图像(LeetCode)

题目 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 解题 def rotate(matrix):n len(matrix)# 矩阵转置for i in range(n):for…...

入门 - vue中v-model的实现原理和完整用法详解

v-model介绍 v-model是vue的双向绑定的指令&#xff0c;能将页面上控件输入的值同步更新到相关绑定的data属性&#xff0c;也会在更新data绑定属性时候&#xff0c;更新页面上输入控件的值。在view层&#xff0c;model层相互需要数据交互&#xff0c;即可使用v-model。 双向绑…...

【区块链+金融服务】港融区域股权服务平台 | FISCO BCOS应用案例

中国证监会在 2020 年启动了区块链建设试点工作&#xff0c;提出建设基于区块链的场外市场登记系统和交易报告库&#xff0c;利 用区块链去中心化、不易篡改、安全稳定等技术特点&#xff0c;构建区域性股权市场数字化信任机制&#xff0c;为区域性股权市场 提供基础支撑设施。…...

Nginx反向代理和前后端分离项目打包部署

Nginx反向代理 Nginx的定位&#xff1a;主要用于做反向代理&#xff0c;一般都是用它来做前端页面的服务器&#xff0c;动态资源代理到后端服务器。这样做的好处是可以避免跨域请求带来的不便。 使用Nginx主要是对Nginx中的nginx.conf文件进行配置&#xff1a; 虚拟主机配置…...

Spring 中ApplicationContext

ApplicationContext 是 Spring 框架中最重要的接口之一&#xff0c;用于提供 Spring IoC 容器的功能。它是一个比 BeanFactory 更高级的容器&#xff0c;负责管理 Spring bean 的生命周期&#xff0c;同时提供对各种企业服务的集成&#xff0c;例如事件传播、国际化、弱引用等。…...

python之时间 datetime、date、time、timedelta、dateutil

在 Python 中&#xff0c;处理日期和时间的常用库是 datetime。此外&#xff0c;还有一些第三方库如 pytz 和 dateutil 可以帮助处理时区和日期解析。 1. 使用 datetime 模块 导入模块 from datetime import datetime, date, time, timedelta获取当前日期和时间 now datet…...

【机器学习第11章——特征选择与稀疏学习】

机器学习第11章——特征选择与稀疏学习 11.特征选择与稀疏学习11.1子集搜索与评价子集搜索子集评价 11.2 过滤式选择11.3 包裹式选择11.4 嵌入式选择11.5 稀疏表示与字典学习稀疏表示字典学习 11.6 压缩感知 11.特征选择与稀疏学习 11.1子集搜索与评价 特征&#xff1a;描述物…...

LeetCode-day43-3137. K 周期字符串需要的最少操作次数

LeetCode-day43-3137. K 周期字符串需要的最少操作次数 题目描述示例示例1&#xff1a;示例2&#xff1a; 思路代码 题目描述 给你一个长度为 n 的字符串 word 和一个整数 k &#xff0c;其中 k 是 n 的因数。 在一次操作中&#xff0c;你可以选择任意两个下标 i 和 j&#x…...

基于springboot的智能家居系统

TOC springboot198基于springboot的智能家居系统 研究背景与现状 时代的进步使人们的生活实现了部分自动化&#xff0c;由最初的全手动办公已转向手动自动相结合的方式。比如各种办公系统、智能电子电器的出现&#xff0c;都为人们生活的享受提供帮助。采用新型的自动化方式…...

【从问题中去学习k8s】k8s中的常见面试题(夯实理论基础)(七)

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…...

C:每日一练:单身狗(2.0版本)

前言&#xff1a; 今天在刷题的时候突然看到一道题&#xff0c;疑似一位故题。仔细一看&#xff0c;欸&#xff01;这不是就是单身狗的升级版吗&#xff1f;我想那必须再安排一篇&#xff0c;不过由于本篇文章与上一篇单身狗文章所涉及的知识点基本相同&#xff0c;所以还请大…...

打破接口壁垒:适配器模式让系统无缝对接

适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许不兼容的接口之间协同工作。主要用途是将一个类的接口转换成客户期望的另一个接口&#xff0c;使得原本接口不兼容的对象可以一起工作。 一、适配器模式的组成 目标接口&#xff08…...

U-Boot 命令使用

U-Boot 是一种常用的引导加载程序&#xff0c;用于引导嵌入式系统。它提供了一系列命令以进行系统配置、引导操作和调试。 以下是一些常见的 U-Boot 命令及其用法&#xff1a; bootm&#xff1a;从指定的内存地址启动操作系统映像。 用法&#xff1a;bootm [addr] bootz&…...

谷歌的高级指令有哪些

今天会分享一些组合用法&#xff0c;这样就能节省许多时间可以放在跟进客户上面&#xff08;本文只介绍谷歌的搜索指令&#xff0c;并无推广&#xff09; part one 谷歌常用的搜索引擎指令&#xff1a; 1、Inurl&#xff0c;在网址中 2、Intext&#xff0c;在网页内容中 3、…...

Redis操作--RedisTemplate(一)介绍

一、介绍 1、简介 RedisTemplate 是 Spring Data Redis 提供的一个高级抽象&#xff0c;由 Spring 官方提供的方便操作 Redis 数据库的一个工具类&#xff0c;支持模板设计模式&#xff0c;使得操作 Redis 更加符合 Spring 的编程模型。还支持序列化机制&#xff0c;可以处理…...

GitLab环境搭建

GitLab环境搭建 一、环境搭建 1、更新系统软件包: sudo yum update2、安装docker sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install do…...

Socket编程TCP 基础

一.什么是Socket(套接字&#xff09; 定义&#xff1a;就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲&#xff0c;套接字上联应用进程&#x…...