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);}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/404865.html

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

相关文章

存储实验:华为异构存储在线接管与在线数据迁移(Smart Virtualization Smart Migration 特性)

目录 目的实验环境实验步骤参考文档1. 主机安装存储多路径2. v2存储创建Lun&#xff0c;映射给主机&#xff1b;主机分区格式化&#xff0c;写数据3. 将v2存储映射该成映射到v3存储上(v3存储和v2之间链路搭建&#xff0c;测通&#xff0c;远端设备&#xff09;&#xff08;Smar…

便利店(超市)管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图详细视频演示技术栈系统测试为什么选择我官方认证玩家&#xff0c;服务很多代码文档&#xff0c;百分百好评&#xff0c;战绩可查&#xff01;&#xff01;入职于互联网大厂&#xff0c;可以交流&#xff0c;共同进步。有保障的售后 代码参考数据库参…

中国:“虚拟资产”交易被列为公认的洗钱方式之一!最高法院承认加密货币交易!

2024年8月19日&#xff0c;最高人民法院和最高人民检察院表示&#xff0c;根据他们对反洗钱法的新解释&#xff0c;“虚拟资产”交易现已被列为公认的洗钱方式之一。这是中国首次针对此类资产类别采取此类举措&#xff0c;说明为应对加密货币和其他虚拟资产日益增长的使用&…

IO进程线程8.20

1.使用fgets获取文件的行号 #include <myhead.h> int main(int argc, const char *argv[]) {FILE *fp fopen("./1.txt","r");if(fpNULL){perror("fp");return -1;}char buf[30];int count 0;while(fgets(buf,sizeof(buf),fp)){count;}p…

为IntelliJ IDEA安装插件

安装插件 插件是开发工具的扩展程序&#xff0c;通常由第三方提供&#xff0c;当安装了插件后&#xff0c;原开发工作的菜单、按钮等开发环境可能会发生变化&#xff0c;例如出现了新的菜单项&#xff0c;或出现了新的按钮&#xff0c;甚至一些全新的编码方式&#xff0c;通常…

双向链表复习(C语言版)

目录 链表分类&#xff1a; 双向链表初始化&#xff1a; 双向链表的插入&#xff1a; 双向链表的打印&#xff1a; 双向链表的删除&#xff1a; 双向链表的指定结点位置查找&#xff1a; 双向链表的在指定位置之后插入数据&#xff1a; 注意&#xff1a;通过上文的指定…

地理科学专业| 中国大学排行榜(2024年)

地理科学专业| 中国大学排行榜&#xff08;2024年&#xff09;

客车制造5G智能工厂工业物联数字孪生平台,推进制造业数字化转型

制造业正经历着前所未有的变革&#xff0c;其中客车制造行业作为传统制造业的重要组成部分&#xff0c;正积极拥抱5G、工业物联网及数字孪生等先进技术&#xff0c;推动生产模式的全面升级与数字化转型。 客车制造5G智能工厂工业物联数字孪生平台的出现&#xff0c;不仅为行业…

【Linux】系列入门摘抄笔记-8-权限管理chmod/chown

Linux操作系统中文件的基本权限由9个字符组成&#xff0c;分别为属主、属组和其他用户&#xff0c;用于规定是否对文件有读、写和执行权限。 文件/目录的权限与归属 目录列表中&#xff0c;有9列 第一列&#xff1a;文件类型与权限&#xff08;共10个字符&#xff0c;分为四组…

电子木鱼+提肛+游戏地图,车机还能这么玩?

文/王俣祺 导语&#xff1a;电子木鱼、提肛训练、游戏级地图&#xff0c;你很难想象这些“直男关怀”是来自小鹏MONA M03的车机系统。最近&#xff0c;一批关于MONA M03车机功能的视频在网上疯传&#xff0c;一系列“没用但有趣”的功能广受年轻用户的好评&#xff0c;情绪价值…

linux上用anaconda创建一个新环境,并将nicegui的应用打包为一个可执行应用

先下载好anaconda linux版本 Download Anaconda Distribution | Anacondahttps://www.anaconda.com/download/之后运行 conda create --name py311 python3.11 --name py311 是环境名 python3.11 是python版本 安装完成后&#xff0c;运行 conda env list 得到 这时我们…

手机使用技巧:如何恢复Android手机不见的短信

在您的 Android 手机上丢失短信可能是一种令人沮丧的经历&#xff0c;尤其是在文本包含重要信息的情况下。幸运的是&#xff0c;有一些方法可以在Android上恢复已删除的短信。在这篇博文中&#xff0c;我们将讨论几种在Android手机上恢复已删除短信的方法。 为什么需要恢复Andr…

【python】逐步回归(多元线性回归模型中的应用)

文章目录 前言一、逐步回归1. 前进法&#xff08;Forward Selection&#xff09;2. 后退法&#xff08;Backward Elimination&#xff09;3. 逐步回归法&#xff08;Stepwise Regression&#xff09; 二、示例三、代码实现----python 前言 Matlab中逐步回归的实现可以使用 Mat…

软体水枪在灭火工作中发挥什么作用_鼎跃安全

火灾&#xff0c;这一频繁侵袭我们日常生活的灾难性事件&#xff0c;以其迅猛之势对人类的生存环境与日常生活构成了极其严重的破坏与威胁。它不仅能够在瞬间吞噬财产&#xff0c;更可怕的是&#xff0c;它无情地剥夺了生命&#xff0c;破坏了家庭&#xff0c;给社会留下了难以…

关于Ubuntu中使用命令行安装Qt的一些分享

以Ubuntu 22.04为例。 1、安装默认的Qt库 sudo apt-get install qtbase5-dev qtbase5-dev-tools qtchooser 这条指令执行完会出现 usr/lib/x86_64-linux-gnu/qt5 文件&#xff0c;并伴随5个子文件夹&#xff0c;结构如下&#xff1a; 并且会出现 usr/lib/qt5, usr/lib/x86_6…

第5节:Elasticsearch核心概念

我的后端学习笔记大纲 我的ElasticSearch学习大纲 1.Lucene和Elasticsearch的关系: 1.Lucene&#xff1a;最先进、功能最强大的搜索库&#xff0c;直接基于lucene开发&#xff0c;非常复杂&#xff0c;api复杂2.Elasticsearch&#xff1a;基于lucene&#xff0c;封装了许多luc…

SpringBoot的自动配置原理探究

目录 什么是SpringBoot的自动配置&#xff08;Auto-Configuration&#xff09; 举例&#xff1a;SpringBoot自动配置&#xff08;Redis的自动配置&#xff09;的实例&#xff1a; 步骤1.&#xff1a;引入Redis启动器pom依赖 步骤2.在application.yml或者&#xff08;proper…

XXL-JOB漏洞分析与利用

一、前言 在当今的数字化时代&#xff0c;任务调度平台对于企业级应用来说至关重要。它们负责自动化和协调各种时间敏感或周期性的任务&#xff0c;确保业务流程的顺畅运行。XXL-JOB作为一款流行的分布式任务调度平台&#xff0c;因其强大的功能和易用性&#xff0c;被广泛部署…

vue3父子组件双向数据绑定v-model;父组件调用子组件事件

效果&#xff1a; 父far.vue <template><div><div>父组件内容<pre>value1:{{ value1 }}</pre><el-button type"primary">flag1:{{ flag1 }}</el-button><pre>obj1:{{ obj1 }}</pre><el-input v-model&q…

进阶SpringBoot之 JDBC 篇

对于数据访问层&#xff0c;无论是SQL&#xff08;关系型数据库&#xff09;还是NOSQL&#xff08;非关系型数据库&#xff09;&#xff0c; Spring Boot 底层都是采用 Spring Data 的方式进行统一处理 创建一个新项目&#xff0c;依赖勾选 JDBC API、MySQL Driver 项目创建好…