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

Android OTA 升级基础知识详解+源码分析

前言:

本文仅仅对OTA升级的几种方式的概念和运用进行总结,仅在使用层面对其解释。需要更详细的内容我推荐大神做的全网最详细的讲解:
https://blog.csdn.net/guyongqiangx/article/details/129019303?spm=1001.2014.3001.5502

三种升级方式

首先对Android的升级分个类,分别是
非AB升级(Non-A/B system updates)
A/B系统升级(A/B (seamless) system updates)
虚拟A/B升级(Virtual A/B overview)

非A/B升级(Non-A/B System Updates)

官网文档如下:
https://source.android.com/docs/core/ota/nonab?hl=zh-cn
非A/B升级是传统的Android系统更新方式,系统在运行过程中更新系统映像文件,并在更新完成后需要重新启动设备来应用更新。
工作流程:

  1. 下载更新包:用户接收到更新通知后,下载OTA(Over-The-Air)更新包。
  2. 准备更新:下载完成后,系统会将更新包解压并准备进行安装。
  3. 安装更新:设备进入恢复模式(recovery mode),系统停止正常运行,安装更新包。
  4. 重启设备:更新完成后,设备重新启动并应用更新。

优点:

  • 实现相对简单,因为不需要额外的分区或复杂的管理逻辑。

缺点:

  • 安装过程中设备无法使用,更新过程可能需要较长时间。
  • 如果更新过程中发生故障,可能导致设备无法正常启动,必须通过手动恢复模式进行修复。

A/B系统升级(A/B (Seamless) System Updates)

官方文档如下:
https://source.android.com/docs/core/ota/ab?hl=zh-cn
由于非A/B升级的局限性,因此Android从7.0开始引入新的OTA升级方式A/B升级(也称为无缝更新)。

概述:

  • A/B系统升级通过创建两个系统分区(A和B),实现无缝更新。
  • 当一个分区正在使用时,另一个分区可以用来进行更新,从而减少更新过程中的设备不可用时间。

工作流程:

  1. 下载更新包:用户接收到更新通知后,下载OTA更新包。
  2. 更新备用分区:系统在后台将更新包安装到未使用的分区(例如,当前使用分区为A,更新包安装到分区B)。
  3. 切换分区:更新完成后,系统会将引导加载程序配置为从更新的分区启动(例如,从A切换到B)。
  4. 重启设备:设备重新启动,从更新的分区启动。

优点:

  • 无缝更新:更新在后台进行,用户可以继续使用设备,减少了更新过程中的不便。
  • 更安全:如果更新过程中出现问题,可以回滚到之前的分区,确保设备的可用性。

缺点:

  • 需要更多存储空间,因为需要两个系统分区。

虚拟A/B升级(Virtual A/B Overview)

官方文档如下:
https://source.android.com/docs/core/ota/virtual_ab?hl=zh-cn
由于A/B升级也有缺点,因此又引入了虚拟A/B升级

概述:

  • 虚拟A/B升级结合了A/B和非A/B升级的优点,通过逻辑分区和动态分区减少存储空间需求。
  • 在Android 10及更高版本中引入,通过使用动态分区来实现无缝更新,而不需要实际的物理A/B分区。

工作流程:

  1. 下载更新包:用户接收到更新通知后,下载OTA更新包。
  2. 创建和更新动态分区:系统在后台创建或更新动态分区(使用逻辑分区),这些分区通过内核支持并在内存中映射。
  3. 切换分区映射:更新完成后,系统会更新内存中的分区映射,从而指向更新后的逻辑分区。
  4. 重启设备:设备重新启动,加载更新后的分区。

优点:

  • 无缝更新:用户可以在后台进行更新,无需停止设备操作。
  • 更高效的存储利用率:使用动态分区减少了传统A/B分区所需的存储空间。

缺点:

  • 实现复杂度更高,需要内核和系统的支持。

这三者的详细流程可以参考下文

Android OTA升级看这一篇就够了-CSDN博客 (2024_6_17 09_40_23).html

判断自己的Android系统是哪种升级方式

  1. 检查分区结构
    使用adb shell命令

adb shell ls /dev/block/by-name/

你会看到设备上的分区列表。A/B系统升级的设备通常会有一对分区(例如system_a和system_b),而非A/B升级的设备只有一个系统分区(例如system)。如:

console:/ # ls /dev/block/by-name/
boot_a       cri_data  logo          odm_ext_a  rsv       vbmeta_system_a
boot_b       dtbo_a    metadata      odm_ext_b  super     vbmeta_system_b
bootloader   dtbo_b    misc          oem_a      tee       vendor_boot_a
bootloader0  env       mmcblk0       oem_b      userdata  vendor_boot_b
bootloader1  factory   mmcblk0boot0  param      vbmeta_a
cache        frp       mmcblk0boot1  reserved   vbmeta_b

这就表示是A/B系统升级

  1. 检查系统属性
    使用adb shell命令
adb shell getprop ro.build.ab_update

如果输出为true,则表示设备使用A/B系统升级。
如果输出为空或false,则表示设备使用非A/B升级。

判断A/B升级现在是使用的哪个分区

使用adb shell命令

cat /proc/cmdline

这是一个用于显示Linux内核启动参数的命令。在Android设备上,它可以用来检查设备在启动时传递给内核的命令行参数。启动参数中可能包含有关设备特性和支持的功能的信息,例如是否启用了动态分区。
androidboot.slot_suffix=_a 表示当前是使用的a分区
androidboot.dynamic_partitions=true 表示当前设备支持动态分区,表明设备可能使用虚拟A/B升级。

OTA升级包

OTA升级包的制作

OTA升级包在我们构建Android系统的时候会自动构建,只要我们输入了make otapackage 命令。他其实是使用了build/make/tools/releasetools 中提供的 ota_from_target_files 工具进行的。我们也可以手动使用这个工具进行制作。制作时需要系统构建时的原zip文件(target-files.zip)
原zip文件的位置在:
out/target/product/[项目名]/obj/PACKAGING/target_files_intermediates/目录下

. build/envsetup.sh && lunch tardis-eng
mkdir dist_output
make dist DIST_DIR=dist_output我们一般使用
make  otapackage

OTA升级包的内容

解压OTA升级包,结果如下:

$ tree -l full-otafull-ota
├── care_map.pb
├── META-INF
│   └── com
│       └── android
│           ├── metadata
│           └── otacert
├── payload.bin
└── payload_properties.txt3 directories, 5 files
  1. payload.bin是系统要更新的数据文件,payload_properties.txt包含了升级内容的一些属性信息,如下:
$ cat full-ota/payload_properties.txt
FILE_HASH=XuFSKnR3J7i9e5g3rd8kc3WM4/hbjnRDmEQDloWtN34=
FILE_SIZE=358295010
METADATA_HASH=uRar88FujcLyXEOF/JLOx2EE2rwFE7kuM1GLYFuYfnA=
METADATA_SIZE=29432

升级时会使用到payload_properties.txt里面的信息。

  1. META-INF/com/android/metadata用于描述软件包的元数据文件
ota-property-files=payload_metadata.bin:1490:36805,payload.bin:1490:67848,payload_properties.txt:69396:150,care_map.pb:809:634,metadata:69:693   
ota-required-cache=0
ota-streaming-property-files=payload.bin:1490:67848,payload_properties.txt:69396:150,care_map.pb:809:634,metadata:69:693
ota-type=AB
post-build=RB56/ohm/ohm:11/RD2A.211001.002/eng.xxx.20240612.134531:userdebug/release-keys
post-build-incremental=eng.xxxxxx.20240612.134531
post-sdk-level=30
post-security-patch-level=2021-10-01
post-timestamp=1718171125
pre-build=xxxxx/ohm/ohm:11/RD2A.211001.002/eng.xxxx.20240612.134531:userdebug/release-keys
pre-build-incremental=eng.xxxxx.20240612.134531
pre-device=ohm

pre-devicepre-build-incrementalpre-build 值定义了设备必须具备哪种状态后才能安装 OTA 软件包。
post-build-incrementalpost-build 值定义了设备在安装 OTA 软件包后的预期状态。
pre- post- 字段的值衍生自下列对应的构建属性。

  • pre-device 值衍生自 ro.product.device build 属性。
  • pre-build-incrementalpost-build-incremental 值衍生自 ro.build.version.incremental build 属性。
  • pre-build post-build 值衍生自ro.build.fingerprint构建属性。
  1. MANIFEST.MF CERT.SF CERT.RSA 表示该OTA升级包已经签名,网上的博客上有这几个文件,但是在我编译出来的OTA包中没有这几个文件,但是OTA包确实是签名了的。

使用update_engine_client升级

A/B系统在debug模式下会包含升级应用update_engine_client
payload.bin文件放到可以通过http访问的地方,然后在命令行调用update_engine_client进行升级

 bcm7252ssffdr4:/ # update_engine_client \--payload=http://stbszx-bld-5/public/android/full-ota/payload.bin \--update \--headers="FILE_HASH=ozGgyQEcnkI5ZaX+Wbjo5I/PCR7PEZka9fGd0nWa+oY= FILE_SIZE=282164983METADATA_HASH=GLIKfE6KRwylWMHsNadG/Q8iy5f7ENWTatvMdBlpoPg= METADATA_SIZE=21023 "

其中headers选项需要填写payload_properties.txt文件的内容。
增量包升级与完整包升级除了升级包的制作不一样之外,生成的升级包文件内容一样,使用update_engine_client进行升级的操作也完全一样。

增量包

增量升级包就是比较现存基础包与原来的基础包的差异而生成的,即该OTA包有特定的应用背景(用于两个增量包之间),用增量包升级不会格式化system分区,只是对其中部分存储段的内容进行重写。升级过程中,升级脚本(打开该升级包)会检测fingerprint,确保该升级包被正确应用。fingerprint这个属性存在于build.prop,可通过adb shell进入根路径,通过cat build.prop查看这个属性(或getprop)。内容在增量包的脚本META-INF/com/google/android/updater-script中。
增量包的制作
在Android源码根目录下:

ota_from_target_files i old.zip new.zip update.zip

或者

./build/tools/releasetools/ota_from_target_files -i old.zip new.zip update.zip

注意:

  1. 原OTA升级包的位置在out/target/product/[项目名]/obj/PACKAGING/target_files_intermediates/目录下
    它是在用命令make otapackage之后的中间生产物,是最原始的升级包。
  2. 第一种是官方推荐的做法,第二种是在博客中广泛出现的,但是我在实际操作中发现第二种会报错:
Error: Unable to access jarfile build/make/tools/framework/signapk.jar

我查看了一下没有这个目录,应该是文件里面对某些路径的定义有问题,推荐使用第二种
这里我找到了网上的解决方式:
https://blog.csdn.net/Tiger99111/article/details/126522501
make otapackage 前面增加make signapk 命令。它的作用是将signapk 编译到增量环境里面去了。然后再编译就会成功。步骤跟上面一样。

增量包的使用

增量包升级与完整包升级除了升级包的制作不一样之外,生成的升级包文件内容一样,使用update_engine_client进行升级的操作也完全一样。因此这里不再介绍使用方式。

增量包的内容

解压update增量包,结果如下:

 tree -l update-ota/
update-ota/
├── care_map.pb
├── META-INF
│   └── com
│       └── android
│           ├── metadata
│           └── otacert
├── payload.bin
└── payload_properties.txt3 directories, 5 files

注意点

  1. 使用增量包升级可能不会更新系统构建时间(ro.build.date)
    原因是在下面这个提交中说明了。
    https://android.googlesource.com/platform/external/libchrome/+/8b7977eccc94f6b3a3896cd13b4aeacbfa1e0f84

Instead, pull the build date of the system from the ro.build.date
system property. Then this library will be identical as long as the
sources and dependencies don’t change, and we won’t have to update it
on every OTA.

只要源和依赖项不变,这个库就会是相同的,因此不更新时间。

分区

在Android设备上,系统存储通常划分为多个分区,每个分区存储不同类型的数据和代码。这些分区可以在不同的设备上有所不同,但通常包括以下主要分区:
Boot分区(boot):

  • 包含内核和初始启动环境。
  • 用于引导设备。

系统分区(system):

  • 包含操作系统的核心部分(如系统应用和库)。
  • 这是Android系统的主要组成部分。

供应商分区(vendor):

  • 包含设备特定的二进制文件和HAL(硬件抽象层)。
  • 使系统与硬件相互配合。

用户数据分区(userdata):

  • 存储用户数据和应用程序数据。
  • 在恢复出厂设置时,此分区会被清空。

缓存分区(cache):

  • 存储临时文件和OTA更新包。
  • 通常用于系统更新过程中的缓存。

恢复分区(recovery):

  • 包含恢复模式的图像,用于执行恢复操作,如恢复出厂设置和OTA更新。

A/B 分区与槽

为了实现无缝更新,Android引入了A/B系统更新机制。这种机制在设备上保留两套系统分区:一套为当前正在使用的系统,另一套用于接收更新。数据分区现在用于存储下载的 OTA 更新包,而恢复映像代码位于启动分区。所有用于 A/B 更新的分区都应按如下方式命名(槽的名称始终为 a、b 等):boot_aboot_bsystem_asystem_bvendor_avendor_b。A/B系统更新的主要特点如下:

  1. 分区槽:
  • 每个分区有两个槽,通常称为slot A和slot B。
  • 例如,系统分区有system_a和system_b,引导分区有boot_a和boot_b。
  1. 无缝更新:
  • OTA更新会被安装到备用槽中,而不是当前使用的槽。
  • 更新完成后,设备会重新启动并切换到更新后的槽。
  • 如果更新失败,设备会回滚到原来的槽,确保系统可用性。
  1. 更新过程:
  • 下载OTA更新包。
  • 将更新包内容写入备用槽的分区。
  • 更新完成后,切换到备用槽并重新启动设备。

虚拟A/B系统更新

虚拟A/B系统更新是一种结合了A/B更新机制和传统更新机制的技术,旨在减少磁盘空间的使用,同时仍然提供无缝更新体验。虚拟A/B系统更新的主要特点包括:

  1. 动态分区:
  • 使用动态分区技术,允许分区在更新过程中调整大小。
  • 通过逻辑卷管理器(LVM)实现分区动态管理。
  1. 减少空间使用:
  • 与传统的A/B系统更新相比,虚拟A/B系统更新减少了重复存储分区所需的空间。
  • 动态分区仅在需要时创建或调整大小,优化了存储利用率。
  1. 更新过程:
  • 类似于A/B系统更新,但使用动态分区来减少空间需求。
  • OTA更新包应用于动态分区,完成后切换到新分区。

源码演示:SystemUpdater

系统 A/B 升级的源代码与UpdateEngine密切相关,A/B升级官方的演示代码可以参考packages/apps/Car/SystemUpdater/
这是android9上Google提供的一个apk,可以理解就是本地U盘测试update_engine升级的一个app。

这个app的主要作用就是:
1、读取U盘中的升级文件,用户点击目标升级文件。
2、调用updateEngine传递主要参数,收updateEngine的callback向用户显示升级升级进度
3、在升级结束之后通知powermanager重启机器。

代码目录如下(注意,以下是Android11的代码):

├── src
│   └── com
│       └── android
│           └── car
│               └── systemupdater
│                   ├── DeviceListFragment.java
│                   ├── SystemUpdaterActivity.java
│                   ├── UpdateLayoutFragment.java
│                   ├── UpdateParser.java
│                   └── UpFragment.java
└── SystemUpdater.iml
  1. 用户选择OTA升级包
    DeviceListFragment.java
/** Set the list of files shown on the screen. */
private void setFileList(List<File> files) {List<CarUiListItem> fileList = new ArrayList<>();for (File file : files) {CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);item.setTitle(file.getName());item.setOnItemClickedListener(i -> onFileSelected(file));//选中了我们的升级文件fileList.add(item);}CarUiListItemAdapter adapter = new CarUiListItemAdapter(fileList);adapter.setMaxItems(CarUiRecyclerView.ItemCap.UNLIMITED);mFolderListView.setAdapter(adapter);
}

这里设置了点击事件,会调用下面这个onFileSelected 函数

  1. 判断是否是OTA升级包
    DeviceListFragment.java
/** Handle user selection of a file. */
private void onFileSelected(File file) {if (isUpdateFile(file)) {mFileStack.clear();mSystemUpdater.applyUpdate(file);//传文件地址过去} else if (file.isDirectory()) {showFolderContent(file);mFileStack.push(file);} else {Toast.makeText(getContext(), R.string.invalid_file_type, Toast.LENGTH_LONG).show();}
}

如果我们选中的是升级文件那么就会调用 mSystemUpdater.applyUpdate(file)方法,传入升级文件的path

  1. 根据传入的文件对象创建一个新的 UpdateLayoutFragment 实例
    SystemUpdaterActivity.java
@Override
public void applyUpdate(File file) {UpdateLayoutFragment fragment = UpdateLayoutFragment.getInstance(file);//继续传getSupportFragmentManager().beginTransaction().replace(R.id.device_container, fragment, FRAGMENT_TAG).addToBackStack(null).commit();
}
4. UpdateLayoutFragment中调用 mPackageVerifier.execute(mUpdateFile) 方法
UpdateLayoutFragment.java
if (getArguments().getBoolean(EXTRA_RESUME_UPDATE)) {// Rejoin the update already in progress.showInstallationInProgress();
} else {// Extract the necessary information and begin the update.mPackageVerifier.execute(mUpdateFile);//这里传过去验证
}

如果是已经在更新了就展示进度信息,如果还没开始就调用 mPackageVerifier.execute(mUpdateFile) 方法

  1. 异步执行更新包的验证和信息提取
    UpdateLayoutFragment.java
/** Attempt to verify the update and extract information needed for installation. */
private class UpdateVerifier extends AsyncTask<File, Void, UpdateParser.ParsedUpdate> {@Overrideprotected UpdateParser.ParsedUpdate doInBackground(File... files) {Preconditions.checkArgument(files.length > 0, "No file specified");File file = files[0];try {return UpdateParser.parse(file);//在UpdateParser的parse解析升级文件} catch (IOException e) {Log.e(TAG, String.format("For file %s", file), e);return null;}}@Overrideprotected void onPostExecute(UpdateParser.ParsedUpdate result) {mProgressBar.setVisible(false);if (result == null) {showStatus(R.string.verify_failure);return;}if (!result.isValid()) {showStatus(R.string.verify_failure);Log.e(TAG, String.format("Failed verification %s", result));return;}if (Log.isLoggable(TAG, Log.INFO)) {Log.i(TAG, result.toString());}showInstallNow(result);//解析升级之后在展示install now button}
}
  1. 在前面的第5步中有一个UpdateParser.parse(file) 我们来看看
    UpdateParser.java
/*** Parse a zip file containing a system update and return a non null ParsedUpdate.*/
@Nullable
static ParsedUpdate parse(@NonNull File file) throws IOException {Preconditions.checkNotNull(file);long payloadOffset = 0;long payloadSize = 0;boolean payloadFound = false;String[] props = null;//打开zip文件并开始解析,初始化 payloadOffset、payloadSize、payloadFound 和 props 变量。try (ZipFile zipFile = new ZipFile(file)) {Enumeration<? extends ZipEntry> entries = zipFile.entries();while (entries.hasMoreElements()) {ZipEntry entry = entries.nextElement();long fileSize = entry.getCompressedSize();if (!payloadFound) {payloadOffset += ZIP_FILE_HEADER + entry.getName().length();if (entry.getExtra() != null) {payloadOffset += entry.getExtra().length;}}if (entry.isDirectory()) {continue;} else if (entry.getName().equals(PAYLOAD_BIN_FILE)) {//处理 PAYLOAD_BIN_FILE 条目payloadSize = fileSize;payloadFound = true;} else if (entry.getName().equals(PAYLOAD_PROPERTIES)) {//处理 PAYLOAD_PROPERTIES 条目try (BufferedReader buffer = new BufferedReader(new InputStreamReader(zipFile.getInputStream(entry)))) {props = buffer.lines().toArray(String[]::new);}}if (!payloadFound) {payloadOffset += fileSize;//更新 payloadOffset}if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, String.format("Entry %s", entry.getName()));}}}return new ParsedUpdate(file, payloadOffset, payloadSize, props);
}
  1. 在前面的第5步中有一个showInstallNow(result); 也就是显示“立即安装”按钮,并更新安装准备界面的内容。
    UpdateLayoutFragment.java
/** Show the install now button. */
private void showInstallNow(UpdateParser.ParsedUpdate update) {mContentTitle.setText(R.string.install_ready);mContentInfo.append(getString(R.string.update_file_name, mUpdateFile.getName()));mContentInfo.append(System.getProperty("line.separator"));mContentInfo.append(getString(R.string.update_file_size));mContentInfo.append(Formatter.formatFileSize(getContext(), mUpdateFile.length()));mContentDetails.setText(null);MenuItem installButton = MenuItem.builder(getActivity())//创建“立即安装”按钮.setTitle(R.string.install_now).setOnClickListener(i -> installUpdate(update)).build();mToolbar.setMenuItems(Collections.singletonList(installButton));
}/** Attempt to install the update that is copied to the device. */
private void installUpdate(UpdateParser.ParsedUpdate parsedUpdate) {showInstallationInProgress();mUpdateEngine.applyPayload(parsedUpdate.mUrl, parsedUpdate.mOffset, parsedUpdate.mSize, parsedUpdate.mProps);
}

这里使用 mUpdateEngine 的 applyPayload 方法开始应用更新包。

applyPayload 方法的参数如下:

  • parsedUpdate.mUrl:更新包的URL地址。
  • parsedUpdate.mOffset:更新包的偏移量。
  • parsedUpdate.mSize:更新包的大小。
  • parsedUpdate.mProps:更新包的属性,通常包括签名、校验和等信息。
  1. 我们继续查看UpdateEngine.java 的代码
frameworks/base/core/java/android/os/UpdateEngine.javapublic UpdateEngine() {mUpdateEngine = IUpdateEngine.Stub.asInterface(ServiceManager.getService(UPDATE_ENGINE_SERVICE));// 是c++层的updateEngine注册到serviceManager中的 }}

UpdateEngine 类的构造方法通过 ServiceManager 获取 UPDATE_ENGINE_SERVICE,这是在C++层注册到 ServiceManager 中的更新引擎服务。

private IUpdateEngine mUpdateEngine;
.............................................
public void applyPayload(String url, long offset, long size, String[] headerKeyValuePairs) {try {mUpdateEngine.applyPayload(url, offset, size, headerKeyValuePairs);// 是从serviceManager中拿到的} catch (RemoteException e) {throw e.rethrowFromSystemServer();}
}

applyPayload 方法实际调用了 IUpdateEngine 接口的方法,这个接口由C++层的更新引擎服务实现。

以上流程的时序图如下:

在这里插入图片描述

相关文章:

Android OTA 升级基础知识详解+源码分析

前言&#xff1a; 本文仅仅对OTA升级的几种方式的概念和运用进行总结&#xff0c;仅在使用层面对其解释。需要更详细的内容我推荐大神做的全网最详细的讲解&#xff1a; https://blog.csdn.net/guyongqiangx/article/details/129019303?spm1001.2014.3001.5502 三种升级方式…...

【吊打面试官系列-Mysql面试题】SQL 语言包括哪几部分?每部分都有哪些操作关键字?

大家好&#xff0c;我是锋哥。今天分享关于 【SQL 语言包括哪几部分&#xff1f;每部分都有哪些操作关键字&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; SQL 语言包括哪几部分&#xff1f;每部分都有哪些操作关键字&#xff1f; SQL 语言包括数据定义(DDL)、…...

Redis的缓存击穿与解决

缓存击穿问题也叫热点Key问题&#xff0c;就是一个被高并发访问并且缓存重建业务较复杂的Key突然失效了&#xff0c;无数的请求访问会在瞬间给数据库带来巨大的冲击。 Redis实战篇 | Kyles Blog (cyborg2077.github.io) 目录 解决方案 互斥锁 实现 逻辑过期 实现 解决方案…...

网络层 IP协议【计算机网络】【协议格式 || 分片 || 网段划分 || 子网掩码】

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;Linux_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们一起努力&#xff0c;一起成长&#xff01; 目录 一&#xff0c;前提 二&…...

Python学习笔记14:进阶篇(三)。类的终结篇,类的导入和模块的导入。

前言 这篇文章属于类知识的最后一篇&#xff0c;带一点点其他知识&#xff0c;学习内容来自于Python crash course。 关注我私信发送Python crash course&#xff0c;分享一份中文版PDF。 类的导入 在学习的时候&#xff0c;包括之前&#xff0c;我都是在一个文件中把所有代…...

C++ lambda表达式举例

C lambda表达式 Lambda表达式是一种简洁的方式来创建匿名函数&#xff0c;可以直接在函数调用的地方定义&#xff0c;主要用于简化代码。 Lambda表达式的基本语法如下&#xff1a; [capture](parameters) -> return_type {// function body };示例1&#xff1a;基本用法 …...

持续总结中!2024年面试必问 20 道设计模式面试题(五)

上一篇地址&#xff1a;持续总结中&#xff01;2024年面试必问 20 道设计模式面试题&#xff08;四&#xff09;-CSDN博客 九、请解释代理模式&#xff08;Proxy Pattern&#xff09;及其类型。 代理模式&#xff08;Proxy Pattern&#xff09;是一种结构设计模式&#xff0c…...

嵌入式面经111题答案汇总(含技术答疑)_嵌入式项目源码分享

111道嵌入式面试题答案汇总专栏链接&#xff08;承诺免费技术答疑&#xff09; --> 《嵌入式/C面试题解析大全》 1、简介 本人是2020年毕业于广东工业大学研究生&#xff1a;许乔丹&#xff0c;有国内大厂CVTE和世界500强企业工作经验&#xff0c;整理超全面111道嵌入式面试…...

鸿蒙开发通信与连接:【@ohos.connectedTag (有源标签)】

有源标签 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import connectedTag from ohos.connectedTag;connectedTag.init init(): boolean 初始化有源标签芯片。 需要权限&#…...

在线编程工具

1.C语言在线编程 https://www.jyshare.com/compile/11/ 2.java在线编程 https://www.w3cschool.cn/tryrun/runcode1?langjava 3.Python在线编程 https://www.python123.io/index/playground/python?ivk_sa1024320u...

NSSCTF中的[WUSTCTF 2020]朴实无华、[FSCTF 2023]源码!启动! 、[LitCTF 2023]Flag点击就送! 以及相关知识点

目录 [WUSTCTF 2020]朴实无华 [FSCTF 2023]源码&#xff01;启动! [LitCTF 2023]Flag点击就送&#xff01; 相关知识点 1.intval 绕过 绕过的方式&#xff1a; 2.session伪造攻击 [WUSTCTF 2020]朴实无华 1.进入页面几乎没什么可用的信息&#xff0c;所以想到使用dis…...

Vue49-props属性

一、当同一个组件标签被使用多次 因为data属性写的是函数形式&#xff01; 二、需求&#xff1a;老王也想用<Student>组件&#xff0c;但是需要动态把老王想要的值传进来。 2-1、使用props属性接收参数 使用props属性&#xff0c;接收的这三个参数&#xff0c;是被保存在…...

CVE-2020-1957 漏洞复现

先声明一下&#xff0c;免杀还是会更的&#xff0c;不过中间可能会穿插一下渗透的内容&#xff01;&#xff01;&#xff01; 踩坑点&#xff1a; 在一开始翻阅了CSDN之后&#xff0c;发现不同文章之间存在出入&#xff0c;于是最后去了CVE的官方文档&#xff0c;和参考一些国…...

网工内推 | 中国电信、香港宽频系统工程师,CCIE认证优先,最高年薪25w

01 中国电信股份有限公司浙江分公司 &#x1f537;招聘岗位&#xff1a;系统架构师 &#x1f537;岗位职责&#xff1a; 1、做好客户网络和信息安全产品的解决方案支撑、交付及后续运营维护&#xff0c;做好相关产数项目的支撑。 2、根据信息安全管理要求&#xff0c;负责客户…...

LLVM后端 td文件 tablegen 模式匹配 寄存器 指令集 calling convention

目录 一、寄存器 1.1 寄存器定义 1.2 寄存器分类 二、指令集 2.1 指令集定义 2.2 模式匹配 2.2.1 PatFrags与PatFrag 2.2.2 OutPatFrag 2.2.3 PatLeaf 2.2.4 ImmLeaf 2.2.5 IntImmLeaf和FPImmLeaf 2.2.6 Pat 2.2.7 ComplexPattern 2.3 指令合法化 2.3.1 Promote…...

嵌入式交叉编译:frp

参考 LINUX FRP下载编译_linux编译frpc-CSDN博客 编译 make -f Makefile.cross-compiles 检查 $ make -f Makefile.cross-compiles Build darwin-amd64... Build darwin-amd64 done Build darwin-arm64... Build darwin-arm64 done Build freebsd-amd64... Build freebsd-…...

SpringBoot实现的大文件上传

前言 大文件分片上传和断点续传是为了解决在网络传输过程中可能遇到的问题&#xff0c;以提高文件传输的效率和稳定性。 首先&#xff0c;大文件分片上传是将大文件分割成较小的片段进行上传。这样做的好处是可以减少单个文件的传输时间&#xff0c;因为较小的文件片段更容易快…...

【Python高级编程】用 Matplotlib 绘制迷人的图表

用 Matplotlib 绘制迷人的图表 引言 Matplotlib 是 Python 中广泛使用的绘图库&#xff0c;用于创建各种图表和可视化。本文将逐步指导您使用 Matplotlib 绘制基本图表&#xff0c;包括折线图、条形图和散点图。 安装 Matplotlib 使用 pip 安装 Matplotlib&#xff1a; pi…...

【UML用户指南】-19-对基本行为建模-用例图

目录 1、组成结构 2、表示法 3、一般用法 3.1、对主题的语境建模 3.2、对主题的需求建模 4、常用建模技术 4.1、对系统的语境建模 4.1.1、设计过程 4.2、对系统的需求建模 4.2.1、设计过程&#xff1a; 5、正向工程 UML 中的用例图是对系统的动态方面建模的 5 种图之…...

mysql密码过期的修改(Your password has expired. ..)

参考文章&#xff1a;mysql密码过期的修改方法&#xff08;your password has expired&#xff09;_我是知青-RuoYi 若依 (csdn.net) 问题&#xff1a;Your password has expired. To log inyou must change it using a clientthat supports expired passwords. 解决方式&…...

vivado SLR

描述 超级逻辑区&#xff08;SLR&#xff09;是包含在堆叠硅中的单个FPGA芯片 互连&#xff08;SSI&#xff09;设备。堆叠式硅互连&#xff08;SSI&#xff09;技术使用无源硅 具有微凸块和硅通孔&#xff08;TSV&#xff09;的内插器&#xff0c;用于组合多个FPGA管芯 切片&a…...

【CSS】深入了解圆角属性border-radius

border-radius 是 CSS 中的一个非常有用的属性&#xff0c;它允许你创建具有圆角边框的元素。这个属性可以应用于一个元素的四个角&#xff0c;或者分别应用于每个角。下面我们将深入了解 border-radius 的使用方法和一些高级技巧。 基本用法 你可以通过为 border-radius 指定…...

LabVIEW与C#的区别及重新开发自动测试程序的可行性分析

LabVIEW和C#是两种广泛使用的编程语言&#xff0c;各自有不同的应用领域和特点。本文将详细比较LabVIEW与C#在自动测试程序开发中的区别&#xff0c;并分析将已完成的LabVIEW自动测试程序重新用C#开发的合理性。本文帮助评估这种转换的必要性和潜在影响。 LabVIEW与C#的区别 开…...

人工智能—美国加利福尼亚州房价预测实战

引言 在当今快速发展的房地产市场中&#xff0c;房价预测已成为一个至关重要的领域。它不仅关系到投资者的决策&#xff0c;也直接影响到普通购房者的生活质量。特别是在美国加利福尼亚州&#xff0c;这个以其高房价和房地产市场的波动性而闻名的地方&#xff0c;准确的房价预…...

python pandas处理股票量化数据:笔记2

有一个同学用我的推荐链接注册了tushare社区帐号https://tushare.pro/register?reg671815&#xff0c;现在有了170分积分。目前使用数据的频率受限制。不过可以在调试期间通过python控制台获取数据&#xff0c;将数据保存在本地以后使用不用高频率访问tushare数据接口&#xf…...

enum库

Python enum 模块教程 enum 是 Python 3.4 引入的一个模块&#xff0c;用于定义枚举类型。枚举类型是一种特殊的数据类型&#xff0c;由一组命名的值组成&#xff0c;这些值称为枚举成员。使用 enum 可以提高代码的可读性和可维护性&#xff0c;特别是在处理一组相关的常量值时…...

【CT】LeetCode手撕—141. 环形链表

目录 题目1- 思路2- 实现⭐141. 环形链表——题解思路 3- ACM实现 题目 原题连接&#xff1a;141. 环形链表 1- 思路 模式识别 模式1&#xff1a;判断链表的环 ——> 快慢指针 思路 快指针 ——> 走两步慢指针 ——> 走一步判断环&#xff1a;若快慢相遇则有环&a…...

python,自定义token生成

1、使用的包PyJWT来实现token生成 安装&#xff1a;pip install PyJWT2.8.0 2、使用例子&#xff1a; import jwt import time pip install pyJWT2.8.0 SECRET_KEY %^ES*E&Ryurehuie9*7^%$#$EDFGHUYTRE#$%^&%$##$RTYGHIK DEFAULT_EXP 7 * 24 * 60def create_token(…...

小米SU7遇冷,下一代全新车型被官方意外曝光

不知道大伙儿有没有发现&#xff0c;最近小米 SU7 热度好像突然之间就淡了不少&#xff1f; 作为小米首款车型&#xff0c;SU7 自上市以来一直承载着新能源轿车领域流量标杆这样一个存在。 发售 24 小时订单量破 8 万&#xff0c;2 个月后累计交付破 2 万台。 看得出来限制它…...

JavaScript 函数与事件

1. JavaScript自定义函数 语法&#xff1a; function 函数名&#xff08;参数列表&#xff09;{ 方法体; } 在函数被调用时&#xff0c;一个 arguments 对象就会被创建&#xff0c;它只能使用在函数体中&#xff0c;以数组的形式来管理函数的实际…...

网站开发合同技术目标/成都网站建设seo

对于窗体的透明&#xff0c;一般来说&#xff0c;有三种方式&#xff1a; 1.通过Ps等工具制作一定透明度的图片&#xff0c;放在div或其他想要放置的trigger里面。以此实现透明的效果。 1 <html> 2   <body> 3     <div id"content">   4 …...

郑州做公司网站/seo核心技术排名

qq_Kero_1首先你要明白一个类的初始化顺序&#xff0c;先初始化类中的属性(如果是基础数据类型初始化为0&#xff0c;如果是引用类型&#xff0c;初始化为null。)&#xff0c;再执行该类的构造函数&#xff0c;如果构造中有初始化属性的代码&#xff0c;会对属性进行第二次初始…...

网站后台制作这么做/如何在百度发布信息推广

第三方登陆&#xff0c;就是使用大家比较熟悉的比如QQ、微信、微博等第三方软件登陆自己的网址&#xff0c;这可以免去注册账号、快速留住用户的目的&#xff0c;免去了相对复杂的注册流程。下边就给大家讲一下如何使用PHP开发QQ登陆的功能。1、进入QQ互联官方网站进行登陆(可以…...

合肥企业网站排名优化/沈阳seo优化新势力

Technorati 标签: BGP,CCIE,CCNP,原理,基础BGP博大精深&#xff0c;在学习的时候我们会发现和传统的IGP有很大的区别&#xff0c;以至于我们在学习的时候很多东西都是在颠覆我们传统的概念&#xff0c;而让学习变得更加困难。 学习是一个渐进的过程。所以对于学习BGP这个路由协…...

企业网站开发用什么好/电商网站有哪些

mounted() {//http://localhost:8080/#/order/Payresult?orderCode=20200721093517378188743943022 var url = window.location.href ; //获取url中"?"符后的字串 var cs = url.split(?)[1]; //获取?之后的参数字符串 //alert(url+地址);//orderC…...

wordpress插件汉化/成都网络营销推广公司

一&#xff1a;实现目标&#xff1a; 1&#xff09;三级分类&#xff1a; 2&#xff09;表结构&#xff1a; 3&#xff09; 一.编写出 查出所有分类&#xff0c;以及子分类。以 树形结构 组装起来 的接口&#xff1a; 1&#xff09;创建用于 数据…...