一、A/B升级之系统image的生成
一、A/B升级之系统image的生成
本篇将对AB升级打开宏开关后make 和 makeotapackage的流程做分析,下面这张图是之前文档中所提到的按照对应文件打开宏开关,即可开启AB升级,但是代码里面针对该宏控也有对应代码处理,本次先分析device 和 build下的修改
一、device主要修改
最初加入了MTK_AB_OTA_UPDATER = yes
去除cache分区编译的控制开关
ifneq ($(strip $(MTK_AB_OTA_UPDATER)), yes)
BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE := ext4
endif
diff --git a/mediateksample/k39tv1_64_bsp/BoardConfig.mk b/mediateksample/k39tv1_64_bsp/BoardConfig.mk index 13eb004..fd32381 100644 --- a/mediateksample/k39tv1_64_bsp/BoardConfig.mk +++ b/mediateksample/k39tv1_64_bsp/BoardConfig.mk @@ -6,10 +6,11 @@ include device/mediatek/mt6739/BoardConfig.mk#Config partition size-include $(MTK_PTGEN_OUT)/partition_size.mk +ifneq ($(strip $(MTK_AB_OTA_UPDATER)), yes)BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE := ext4 +endifBOARD_FLASH_BLOCK_SIZE := 4096-MTK_INTERNAL_CDEFS := $(foreach t,$(AUTO_ADD_GLOBAL_DEFINE_BY_NAME),$(if $(filter-out no NO none NONE false FALSE,$($(t))),-D$(t)))MTK_INTERNAL_CDEFS += $(foreach t,$(AUTO_ADD_GLOBAL_DEFINE_BY_VALUE),$(if $(filter-out no NO none NONE false FALSE,$($(t))),$(foreach v,$(shell echo $($(t)) | tr '[a-z]' '[A-Z]'),-D$(v))))MTK_INTERNAL_CDEFS += $(foreach t,$(AUTO_ADD_GLOBAL_DEFINE_BY_NAME_VALUE),$(if $(filter-out no NO none NONE false FALSE,$($(t))),-D$(t)=\"$(strip $($(t)))\")) diff --git a/mediateksample/k39tv1_64_bsp/ProjectConfig.mk b/mediateksample/k39tv1_64_bsp/ProjectConfig.mk index 99aebd3..3419c65 100755 --- a/mediateksample/k39tv1_64_bsp/ProjectConfig.mk +++ b/mediateksample/k39tv1_64_bsp/ProjectConfig.mk @@ -685,7 +685,8 @@ TRUSTONIC_TEE_SUPPORT = noUSE_FRAUNHOFER_AAC = noUSE_XML_AUDIO_POLICY_CONF = 1WIFI_WEP_KEY_ID_SET = no -MTK_AB_OTA_UPDATER = no +CONFIG_MTK_AB_OTA_UPDATER = yes +MTK_AB_OTA_UPDATER = yes
搜索宏控相关文件如下
libiaobiao:~/code/s219_ab/device$ grep -rni "MTK_AB_OTA_UPDATER"mediateksample/k39tv1_64_bsp/BoardConfig.mk:9:ifneq ($(strip $(MTK_AB_OTA_UPDATER)), yes) mediateksample/k39tv1_64_bsp/ProjectConfig.mk:688:CONFIG_MTK_AB_OTA_UPDATER = yes mediateksample/k39tv1_64_bsp/ProjectConfig.mk:689:MTK_AB_OTA_UPDATER = yesmediatek/common/device.mk:3643:ifeq ($(strip $(MTK_AB_OTA_UPDATER)), yes) mediatek/common/BoardConfig.mk:228: ifeq ($(strip $(MTK_AB_OTA_UPDATER)), yes) mediatek/mt6739/BoardConfig.mk:132:ifneq ($(strip $(MTK_AB_OTA_UPDATER)),yes)mediatek/build/build/tools/ptgen/MT6739/ptgen.mk:71: MTK_AB_OTA_UPDATER=${MTK_AB_OTA_UPDATER} \ mediatek/build/build/tools/ptgen/MT6739/ptgen.pl:195: $ArgList{MTK_AB_OTA_UPDATER} = $ENV{MTK_AB_OTA_UPDATER}; mediatek/build/build/tools/ptgen/MT6739/ptgen.pl:260: if ($ArgList{MTK_AB_OTA_UPDATER} eq "yes") mediatek/build/build/tools/ptgen/MT6739/ptgen.pl:271: if ($ArgList{MTK_AB_OTA_UPDATER} eq "yes") mediatek/build/build/tools/ptgen/MT6739/ptgen.pl:768: if ($ArgList{MTK_AB_OTA_UPDATER} eq "yes") mediatek/build/build/tools/partition/gen-partition.py:123: if os.getenv("MTK_AB_OTA_UPDATER") == "yes":
1、mediatek/common/device.mk
# A/B System updates ifeq ($(strip $(MTK_AB_OTA_UPDATER)), yes)# Squashfs config system文件格式使用squashfs,这是与ext4不同的格式,目前没用到 #BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE := squashfs #PRODUCT_PACKAGES += mksquashfs #PRODUCT_PACKAGES += mksquashfsimage.sh #PRODUCT_PACKAGES += libsquashfs_utils #将recovery ramdisk放到boot.img文件内 BOARD_USES_RECOVERY_AS_BOOT := true #不再编译recovery.img TARGET_NO_RECOVERY := true #打开AB_OTA_UPDATER宏,这个本身是google原生的打开AB的主控开 AB_OTA_UPDATER := true# A/B OTA partitions AB升级中可升级的分区 AB_OTA_PARTITIONS := \ boot \ system \ lk \ preloader #编译update_engine update_verifier brillo_update_payload模块 PRODUCT_PACKAGES += \ update_engine \ shflags \ delta_generator \ bsdiff \ brillo_update_payload \ update_engine_sideload \ update_verifier \ #这两个时debug的调试工具update_engine_client 可以在adblog中输出升级流程 #bootctl是boot_control模块调用的工具,可以通过命令调用接口 PRODUCT_PACKAGES_DEBUG += \ update_engine_client \ bootctl# bootctrl HAL and HIDL 编译启动相关的bootctrl hal层 PRODUCT_PACKAGES += \bootctrl.$(MTK_PLATFORM_DIR) \android.hardware.boot@1.0-impl \android.hardware.boot@1.0-servicePRODUCT_STATIC_BOOT_CONTROL_HAL := bootctrl.$(MTK_PLATFORM_DIR)# A/B OTA dexopt package PRODUCT_PACKAGES += otapreopt_script# Install odex files into the other system image #编译了system_other 并将odex文件放入 BOARD_USES_SYSTEM_OTHER_ODEX := true# A/B OTA dexopt update_engine hookup AB_OTA_POSTINSTALL_CONFIG += \RUN_POSTINSTALL_system=true \POSTINSTALL_PATH_system=system/bin/otapreopt_script \FILESYSTEM_TYPE_system=ext4 \POSTINSTALL_OPTIONAL_system=true# Tell the system to enable copying odexes from other partition. PRODUCT_PACKAGES += \cppreopts.shPRODUCT_PROPERTY_OVERRIDES += \ro.cp_system_other_odex=1DEVICE_MANIFEST_FILE += device/mediatek/common/project_manifest/manifest_boot.xml endif #下面一个单独的判断,定义是否将rootfs放入system, ifneq ($(strip $(SYSTEM_AS_ROOT)), no) BOARD_BUILD_SYSTEM_ROOT_IMAGE := true endif
2、mediatek/common/BoardConfig.mk
这部分的修改与odex化的编译有关,以下时相关的资料
开odex优化首次开机速度,是牺牲空间换取时间的做法,仅限于空间足够的设备。开了odex之后,在编译的时候,整个system image就会被预先优化。由于在启动时不再需要进行app的dex文件进行优化(dex2oat操作)从而提升其启动速度。
关于odex,有几个下面几个宏开关:
1、WITH_DEXPREOPT
这个开关在6.0 USER版本上是默认开启的,意思就是USER版本要开odex预编译。会导致system image中的所有东西都被提前优化(pre-optimized)。这可能导致system image非常大。
那么问题就来了,既然 WITH_DEXPREOPT := true 默认开启,那么为什么首次启动依然耗时很长呢?这个就和第二个宏开关——DONT_DEXPREOPT_PREBUILTS有关了。
2、DONT_DEXPREOPT_PREBUILTS
如果我们不想把prebuilts目录中的第三方应用进行预先优化(这些应用在他们的Android.mk文件中有include$(BUILD_PREBUILT) ),而是希望这些app通过playstore 或者app提供商进行升级,那么我们可以打开这个宏开关。
事实上,6.0上面,这个宏开关也是默认开启的。我们全局搜索一下“(BUILD_PREBUILT) ”会发现很多结果,这也就是为什么默认odex都开了,为什么开机并没有觉得快的原因了。
因此我们在做odex优化的时候,都会关闭DONT_DEXPREOPT_PREBUILTS,然后重新给我们预置的App添加 LOCAL_DEX_PREOPT :=false 让它们不进行预编译,这样也就能节省一些不必要的空间消耗。同时因为关闭了DONT_DEXPREOPT_PREBUILTS,很多可以随ROM升级的系统App也就进行了预编译,因此开机速度就有了明显的提高。
开AB后DONT_DEXPREOPT_PREBUILTS :=false 看到这里其实看不出什么,后面分析Makefile可以看出具体生成,不过暂时根据out下生成的system 和 system_other system下面是单独的APK 而system_other是单独的odex和vdex,
ifeq ($(BUILD_GMS),yes)ifeq ($(strip $(MTK_AB_OTA_UPDATER)), yes)DONT_DEXPREOPT_PREBUILTS := falseelseDONT_DEXPREOPT_PREBUILTS := trueendif elseifeq ($(TARGET_BUILD_VARIANT),userdebug)DEX_PREOPT_DEFAULT := nostrippingendif endif
3、mediatek/mt6739/BoardConfig.mk
以下是谷歌原生文档关于该宏的介绍,如果打开AB升级,将会关闭该宏
在非 A/B 设备的恢复映像中添加 DTBO
为防止非 A/B 设备上出现 OTA 失败的情况,恢复分区必须“自给自足”,不得依赖于其他分区。
启动到恢复模式时,引导加载程序必须加载与恢复映像兼容的 DTBO 映像。在执行 OTA 期间,如果在 DTBO 映像更新后(但在完成全部更新之前)出现问题,设备将尝试启动到恢复模式,以完成 OTA。不过,由于 DTBO 分区已更新,恢复映像(尚未更新)可能会出现不匹配的情况。
为防止出现这种情况,在 Android 9 中,恢复映像也必须包含来自 DTBO 映像的信息。非 A/B 设备的恢复映像还必须包含附加到内核的设备 DTB,以便在更新期间不依赖于 DTB 分区。
实现
虽然搭载 Android 9 的所有设备都必须使用新的启动映像标头(版本 1),但只有非 A/B 设备才必须填充恢复映像的 recovery_dtbo
部分。要在 BoardConfig.mk
设备的 recovery.img
中添加 recovery_dtbo
,请执行以下操作:
- 将
BOARD_INCLUDE_RECOVERY_DTBO
配置设置为true
:
BOARD_INCLUDE_RECOVERY_DTBO := true
- 扩展
BOARD_MKBOOTIMG_ARGS
变量以指定启动映像标头版本:
BOARD_MKBOOTIMG_ARGS := --ramdisk_offset $(BOARD_RAMDISK_OFFSET) --tags_offset $(BOARD_KERNEL_TAGS_OFFSET) --header_version $(BOARD_BOOTIMG_HEADER_VERSION)
- 确保将
BOARD_PREBUILT_DTBOIMAGE
变量设置为 DTBO 映像的路径。Android 编译系统会使用该变量在创建恢复映像时设置 mkbootimg 工具的recovery_dtbo
参数。 - 如果变量
BOARD_INCLUDE_RECOVERY_DTBO
、BOARD_MKBOOTIMG_ARGS
和BOARD_PREBUILT_DTBOIMAGE
均正确设置,Android 编译系统会将变量BOARD_PREBUILT_DTBOIMAGE
指定的 DTBO 添加到recovery.img
中。
ifeq ($(strip $(MTK_DTBO_FEATURE)),yes) ifeq ($(strip $(MTK_DTBO_UPGRADE_FROM_ANDROID_O)), yes) BOARD_PREBUILT_DTBOIMAGE := $(MTK_PTGEN_PRODUCT_OUT)/obj/PACKAGING/dtboimage/odmdtbo.img else BOARD_PREBUILT_DTBOIMAGE := $(MTK_PTGEN_PRODUCT_OUT)/obj/PACKAGING/dtboimage/dtbo.img endif ifneq ($(strip $(MTK_AB_OTA_UPDATER)),yes) BOARD_INCLUDE_RECOVERY_DTBO := true endif endif
mediatek/build/build/tools/ptgen/MT6739/ptgen.mk
4、mediatek/build/build/tools/ptgen/MT6739/ptgen.pl
MTK分区表存放位置:device/mediatek/build/build/tools/ptgen/xxx/xxx.xls
ptgen.pl文件会把xls文件解析成xxxAndroid_scatter.txt放在out/target/product/xxx/中
mtk的flashtool工具会读取这个文件把相关的镜像烧写到rom中
这里是根据判断选择对应分区文件,如果开了MTK_AB_OTA_UPDATER,选择我们emmc_ab的分区表,否则选择emmc的
$Partition_layout_xls = "$ptgen_location/partition_table_$ArgList{PLATFORM}";if ($ArgList{EMMC_SUPPORT} eq "yes"){if ($ArgList{MTK_AB_OTA_UPDATER} eq "yes"){$ArgList{SHEET_NAME} = "emmc_ab";}else{$ArgList{SHEET_NAME} = "emmc";}}elsif ($ArgList{UFS_BOOTING} eq "yes"){if ($ArgList{MTK_AB_OTA_UPDATER} eq "yes"){$ArgList{SHEET_NAME} = "ufs_ab";}else{$ArgList{SHEET_NAME} = "ufs";}}else{$ArgList{SHEET_NAME} = "nand";}}
二、images生成流程
device修改很多开关主要作用时更改image的生成,而这部分全部是在makefile中,这是我们主要分析的文件,继续开车
1、recovery.img
ifeq (,$(filter true, $(TARGET_NO_KERNEL) $(TARGET_NO_RECOVERY))) INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img else INSTALLED_RECOVERYIMAGE_TARGET := endif
device.mk中定义了TARGET_NO_RECOVERY := true 由于TARGET_NO_KERNEL :=false ,所以条件不成立,
INSTALLED_RECOVERYIMAGE_TARGET :=,也就是不再生成recovery.img
2、boot.img
INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img MTK_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img INSTALLED_BOOTIMAGE_TARGET := $(call intermediates-dir-for,PACKAGING,boot)/boot.img #这里判断条件较多,不过android对那个if 对应那个endif 还进行了标注,方便我们查看代码 #TARGET_NO_KERNEL 如果是true 条件不成立,这里我加了打印,其实它的值是 fasle,进入if条件 ifneq ($(strip $(TARGET_NO_KERNEL)),true) $(warning "TARGET_NO_KERNEL is not supported anymore") # ----------------------------------------------------------------- # the boot image, which is a collection of other images. INTERNAL_BOOTIMAGE_ARGS := \$(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET)) \--kernel $(INSTALLED_KERNEL_TARGET)ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true) INTERNAL_BOOTIMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET) endifINTERNAL_BOOTIMAGE_FILES := $(filter-out --%,$(INTERNAL_BOOTIMAGE_ARGS))ifdef BOARD_KERNEL_BASEINTERNAL_BOOTIMAGE_ARGS += --base $(BOARD_KERNEL_BASE) endififdef BOARD_KERNEL_PAGESIZEINTERNAL_BOOTIMAGE_ARGS += --pagesize $(BOARD_KERNEL_PAGESIZE) endififeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true) ifeq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true) VERITY_KEYID := veritykeyid=id:`openssl x509 -in $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VERITY_SIGNING_KEY).x509.pem -text \| grep keyid | sed 's/://g' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]' | sed 's/keyid//g'` endif endifINTERNAL_KERNEL_CMDLINE := $(strip $(BOARD_KERNEL_CMDLINE) buildvariant=$(TARGET_BUILD_VARIANT) $(VERITY_KEYID)) ifdef INTERNAL_KERNEL_CMDLINE INTERNAL_BOOTIMAGE_ARGS += --cmdline "$(INTERNAL_KERNEL_CMDLINE)" endifINTERNAL_MKBOOTIMG_VERSION_ARGS := \--os_version $(PLATFORM_VERSION) \--os_patch_level $(PLATFORM_SECURITY_PATCH)# BOARD_USES_RECOVERY_AS_BOOT = true must have BOARD_BUILD_SYSTEM_ROOT_IMAGE = true. #这里的意思是说BOARD_USES_RECOVERY_AS_BOOT和BOARD_BUILD_SYSTEM_ROOT_IMAGE必须同时定义 ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true) ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)$(error BOARD_BUILD_SYSTEM_ROOT_IMAGE must be enabled for BOARD_USES_RECOVERY_AS_BOOT.) endif endif# We build recovery as boot image if BOARD_USES_RECOVERY_AS_BOOT is true. #如果BOARD_USES_RECOVERY_AS_BOOT为true,不走下面所有的代码,所以开启这个宏后,普通生成boot.img #的代码将全部失效 ifneq ($(BOARD_USES_RECOVERY_AS_BOOT),true) ifeq ($(TARGET_BOOTIMAGE_USE_EXT2),true) $(error TARGET_BOOTIMAGE_USE_EXT2 is not supported anymore)else ifeq (true,$(BOARD_AVB_ENABLE)) # TARGET_BOOTIMAGE_USE_EXT2 != true$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(AVBTOOL) $(INTERNAL_BOOTIMAGE_FILES) $(BOARD_AVB_BOOT_KEY_PATH)$(call pretty,"Target boot image: $@")$(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $@$(hide) $(call assert-max-image-size,$@,$(call get-hash-image-max-size,$(BOARD_BOOTIMAGE_PARTITION_SIZE)))$(hide) $(AVBTOOL) add_hash_footer \--image $@ \--partition_size $(BOARD_BOOTIMAGE_PARTITION_SIZE) \--partition_name boot $(INTERNAL_AVB_BOOT_SIGNING_ARGS) \$(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS).PHONY: bootimage-nodeps bootimage-nodeps: $(MKBOOTIMG) $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH)@echo "make $@: ignoring dependencies"$(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $(INSTALLED_BOOTIMAGE_TARGET)$(hide) $(call assert-max-image-size,$(INSTALLED_BOOTIMAGE_TARGET),$(call get-hash-image-max-size,$(BOARD_BOOTIMAGE_PARTITION_SIZE)))$(hide) $(AVBTOOL) add_hash_footer \--image $(INSTALLED_BOOTIMAGE_TARGET) \--partition_size $(BOARD_BOOTIMAGE_PARTITION_SIZE) \--partition_name boot $(INTERNAL_AVB_BOOT_SIGNING_ARGS) \$(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)else ifeq (true,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_BOOT_SIGNER)) # BOARD_AVB_ENABLE != true$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_FILES) $(BOOT_SIGNER)$(call pretty,"Target boot image: $@")$(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $@$(BOOT_SIGNER) /boot $@ $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VERITY_SIGNING_KEY).pk8 $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VERITY_SIGNING_KEY).x509.pem $@$(hide) $(call assert-max-image-size,$@,$(BOARD_BOOTIMAGE_PARTITION_SIZE)).PHONY: bootimage-nodeps bootimage-nodeps: $(MKBOOTIMG) $(BOOT_SIGNER)@echo "make $@: ignoring dependencies"$(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $(INSTALLED_BOOTIMAGE_TARGET)$(BOOT_SIGNER) /boot $(INSTALLED_BOOTIMAGE_TARGET) $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VERITY_SIGNING_KEY).pk8 $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VERITY_SIGNING_KEY).x509.pem $(INSTALLED_BOOTIMAGE_TARGET)$(hide) $(call assert-max-image-size,$(INSTALLED_BOOTIMAGE_TARGET),$(BOARD_BOOTIMAGE_PARTITION_SIZE))else ifeq (true,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT)) # PRODUCT_SUPPORTS_BOOT_SIGNER != true$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_FILES) $(VBOOT_SIGNER) $(FUTILITY)$(call pretty,"Target boot image: $@")$(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $@.unsigned$(VBOOT_SIGNER) $(FUTILITY) $@.unsigned $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VBOOT_SIGNING_KEY).vbpubk $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VBOOT_SIGNING_KEY).vbprivk $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VBOOT_SIGNING_SUBKEY).vbprivk $@.keyblock $@$(hide) $(call assert-max-image-size,$@,$(BOARD_BOOTIMAGE_PARTITION_SIZE)).PHONY: bootimage-nodeps bootimage-nodeps: $(MKBOOTIMG) $(VBOOT_SIGNER) $(FUTILITY)@echo "make $@: ignoring dependencies"$(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $(INSTALLED_BOOTIMAGE_TARGET).unsigned$(VBOOT_SIGNER) $(FUTILITY) $(INSTALLED_BOOTIMAGE_TARGET).unsigned $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VBOOT_SIGNING_KEY).vbpubk $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VBOOT_SIGNING_KEY).vbprivk $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_VBOOT_SIGNING_SUBKEY).vbprivk $(INSTALLED_BOOTIMAGE_TARGET).keyblock $(INSTALLED_BOOTIMAGE_TARGET)$(hide) $(call assert-max-image-size,$(INSTALLED_BOOTIMAGE_TARGET),$(BOARD_BOOTIMAGE_PARTITION_SIZE))else # PRODUCT_SUPPORTS_VBOOT != true$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_FILES)$(call pretty,"Target boot image: $@")$(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $@$(hide) $(call assert-max-image-size,$@,$(BOARD_BOOTIMAGE_PARTITION_SIZE)).PHONY: bootimage-nodeps bootimage-nodeps: $(MKBOOTIMG)@echo "make $@: ignoring dependencies"$(hide) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $(INSTALLED_BOOTIMAGE_TARGET)$(hide) $(call assert-max-image-size,$(INSTALLED_BOOTIMAGE_TARGET),$(BOARD_BOOTIMAGE_PARTITION_SIZE)) #这里是对每个判断条件的结尾进行了注释 endif # TARGET_BOOTIMAGE_USE_EXT2 endif # BOARD_USES_RECOVERY_AS_BOOTelse # TARGET_NO_KERNEL
根据代码的判断,因为BOARD_USES_RECOVERY_AS_BOOT 为true 所以不走普通的生成方式,那么真正生成boot.img是在哪里呢,字面翻译这个宏的意思时说我们要用recovery作为现在的boot,代码如下
#条件成立,进入ifeq ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true) #添加依赖BOOT_SIGNER ifeq (true,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_BOOT_SIGNER)) $(INSTALLED_BOOTIMAGE_TARGET) : $(BOOT_SIGNER) endif #添加依赖VBOOT_SIGNER ifeq (true,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT)) $(INSTALLED_BOOTIMAGE_TARGET) : $(VBOOT_SIGNER) endif #添加依赖AVBTOOL BOARD_AVB_BOOT_KEY_PATH ifeq (true,$(BOARD_AVB_ENABLE)) $(INSTALLED_BOOTIMAGE_TARGET) : $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH) endif #具体生成boot的部分,添加依赖,并调用build-recoveryimage-target 生成boot.img $(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) $(ADBD) \$(INSTALLED_RAMDISK_TARGET) \$(INTERNAL_RECOVERYIMAGE_FILES) \$(recovery_initrc) $(recovery_sepolicy) $(recovery_kernel) \$(INSTALLED_2NDBOOTLOADER_TARGET) \$(recovery_build_props) $(recovery_resource_deps) \$(recovery_fstab) \$(RECOVERY_INSTALL_OTA_KEYS) \$(INSTALLED_VENDOR_DEFAULT_PROP_TARGET) \$(BOARD_RECOVERY_KERNEL_MODULES) \$(DEPMOD)$(call pretty,"Target boot image from recovery: $@")$(call build-recoveryimage-target, $@) endif #以下是生成recovery.img的代码,其实跟上面是一模一样的 $(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) $(ADBD) \$(INSTALLED_RAMDISK_TARGET) \$(INSTALLED_BOOTIMAGE_TARGET) \$(INTERNAL_RECOVERYIMAGE_FILES) \$(recovery_initrc) $(recovery_sepolicy) $(recovery_kernel) \$(INSTALLED_2NDBOOTLOADER_TARGET) \$(recovery_build_props) $(recovery_resource_deps) \$(recovery_fstab) \$(RECOVERY_INSTALL_OTA_KEYS) \$(INSTALLED_VENDOR_DEFAULT_PROP_TARGET) \$(BOARD_RECOVERY_KERNEL_MODULES) \$(DEPMOD)$(call build-recoveryimage-target, $@)
device.mk中定义了BOARD_USES_RECOVERY_AS_BOOT :=true,所以不再走原来生成boot.img的流程,使用生成recovery.img的方法打包成boot.img
3、cache.img
# ----------------------------------------------------------------- # cache partition image #因为我们更改了BoardConfig.mk,所以BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE没有定义,不再生成cache.img ifdef BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE INTERNAL_CACHEIMAGE_FILES := \$(filter $(TARGET_OUT_CACHE)/%,$(ALL_DEFAULT_INSTALLED_MODULES))cacheimage_intermediates := \$(call intermediates-dir-for,PACKAGING,cache) BUILT_CACHEIMAGE_TARGET := $(PRODUCT_OUT)/cache.imgdefine build-cacheimage-target$(call pretty,"Target cache fs image: $(INSTALLED_CACHEIMAGE_TARGET)")@mkdir -p $(TARGET_OUT_CACHE)@mkdir -p $(cacheimage_intermediates) && rm -rf $(cacheimage_intermediates)/cache_image_info.txt$(call generate-userimage-prop-dictionary, $(cacheimage_intermediates)/cache_image_info.txt, skip_fsck=true)$(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \build/make/tools/releasetools/build_image.py \$(TARGET_OUT_CACHE) $(cacheimage_intermediates)/cache_image_info.txt $(INSTALLED_CACHEIMAGE_TARGET) $(TARGET_OUT)$(hide) $(call assert-max-image-size,$(INSTALLED_CACHEIMAGE_TARGET),$(BOARD_CACHEIMAGE_PARTITION_SIZE)) endef# We just build this directly to the install location. INSTALLED_CACHEIMAGE_TARGET := $(BUILT_CACHEIMAGE_TARGET) $(INSTALLED_CACHEIMAGE_TARGET): $(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_CACHEIMAGE_FILES) $(BUILD_IMAGE_SRCS)$(build-cacheimage-target).PHONY: cacheimage-nodeps cacheimage-nodeps: | $(INTERNAL_USERIMAGES_DEPS)$(build-cacheimage-target)else # BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE # we need to ignore the broken cache link when doing the rsync #我们需要在执行rsync时忽略已损坏的缓存链接 IGNORE_CACHE_LINK := --exclude=cache endif # BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
BoardConfig.mk中更改了代码
ifneq ($(strip $(MTK_AB_OTA_UPDATER)), yes)
BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE := ext4
endif
BOARD_FLASH_BLOCK_SIZE := 4096
所以生成cache.img的方法将不再执行,并忽略与cache相关的损坏的链接
4、system.img
# $(1): output file define build-systemimage-target@echo "Target system fs image: $(1)"#调用$(call create-system-vendor-symlink)创建符号链接$(call create-system-vendor-symlink)#调用$(call create-system-product-symlink)创建符号链接$(call create-system-product-symlink)#删除之前的system_image_info.txt@mkdir -p $(dir $(1)) $(systemimage_intermediates) && rm -rf $(systemimage_intermediates)/system_image_info.txt#调用call generate-userimage-prop-dictionary,重新生成system_image_info.txt$(call generate-userimage-prop-dictionary, $(systemimage_intermediates)/system_image_info.txt, \skip_fsck=true)#调用build_image.py 传入system_image_info.txt和$(PRODUCT_OUT)/system创建system.img文件$(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \build/make/tools/releasetools/build_image.py \$(TARGET_OUT) $(systemimage_intermediates)/system_image_info.txt $(1) $(TARGET_OUT) \|| ( echo "Out of space? the tree size of $(TARGET_OUT) is (MB): " 1>&2 ;\du -sm $(TARGET_OUT) 1>&2;\if [ "$(INTERNAL_USERIMAGES_EXT_VARIANT)" == "ext4" ]; then \maxsize=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE); \echo "The max is $$(( maxsize / 1048576 )) MB." 1>&2 ;\else \echo "The max is $$(( $(BOARD_SYSTEMIMAGE_PARTITION_SIZE) / 1048576 )) MB." 1>&2 ;\fi; \mkdir -p $(DIST_DIR); cp $(INSTALLED_FILES_FILE) $(DIST_DIR)/installed-files-rescued.txt; \exit 1 ) endef$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE) $(BUILD_IMAGE_SRCS)$(call build-systemimage-target,$@)INSTALLED_SYSTEMIMAGE := $(PRODUCT_OUT)/system.img SYSTEMIMAGE_SOURCE_DIR := $(TARGET_OUT)
这部分在之前分析make 生成 system.img时候提到过,传入的info文件将决定system.img中打包的内容
# $(1): the path of the output dictionary file # $(2): additional "key=value" pairs to append to the dictionary file. #调用该方法生成system_image_info.txt,其中BOARD_BUILD_SYSTEM_ROOT_IMAGE 宏是打开的会将如下两个值 #写入到info文件中 define generate-userimage-prop-dictionary $(if $(filter true,$(BOARD_BUILD_SYSTEM_ROOT_IMAGE)),\$(hide) echo "system_root_image=true" >> $(1);\echo "ramdisk_dir=$(TARGET_ROOT_OUT)" >> $(1)) $(if $(2),$(hide) $(foreach kv,$(2),echo "$(kv)" >> $(1);)) endef
ext_mkuserimg=mkuserimg_mke2fs.sh fs_type=ext4 system_size=2684354560 userdata_size=3221225472 vendor_fs_type=ext4 vendor_size=578813952 extfs_sparse_flag=-s squashfs_sparse_flag=-s selinux_fc=out/target/product/k39tv1_64_bsp/obj/ETC/file_contexts.bin_intermediates/file_contexts.bin boot_signer=true verity=true verity_key=build/target/product/security/verity verity_signer_cmd=verity_signer verity_fec=true system_verity_block_device=/dev/block/platform/bootdevice/by-name/system vendor_verity_block_device=/dev/block/platform/bootdevice/by-name/vendor recovery_as_boot=true system_root_image=true ramdisk_dir=out/target/product/k39tv1_64_bsp/root skip_fsck=true
这两个参数的加入
system_root_image=true
ramdisk_dir=out/target/product/k39tv1_64_bsp/root
决定了真正最后生成的system.img文件会将ramdisk和filesystem一并打包,也就是说目前的system.img包含了rootfs(查看9.0的代码可以发现,即使是非ab,也有这两个参数从存在了)
5、system_other.img
# ----------------------------------------------------------------- # system_other partition image #该BOARD_USES_SYSTEM_OTHER_ODEX打开后 会设定BOARD_USES_SYSTEM_OTHER ifeq ($(BOARD_USES_SYSTEM_OTHER_ODEX),true) BOARD_USES_SYSTEM_OTHER := true# Marker file to identify that odex files are installed INSTALLED_SYSTEM_OTHER_ODEX_MARKER := $(TARGET_OUT_SYSTEM_OTHER)/system-other-odex-marker ALL_DEFAULT_INSTALLED_MODULES += $(INSTALLED_SYSTEM_OTHER_ODEX_MARKER) $(INSTALLED_SYSTEM_OTHER_ODEX_MARKER):$(hide) touch $@ endif #BOARD_USES_SYSTEM_OTHER该宏为true,进入if生成system_other.img ifdef BOARD_USES_SYSTEM_OTHER INTERNAL_SYSTEMOTHERIMAGE_FILES := \$(filter $(TARGET_OUT_SYSTEM_OTHER)/%,\$(ALL_DEFAULT_INSTALLED_MODULES)\$(ALL_PDK_FUSION_FILES)) \$(PDK_FUSION_SYMLINK_STAMP)INSTALLED_FILES_FILE_SYSTEMOTHER := $(PRODUCT_OUT)/installed-files-system-other.txt $(INSTALLED_FILES_FILE_SYSTEMOTHER) : $(INTERNAL_SYSTEMOTHERIMAGE_FILES) $(FILESLIST)@echo Installed file list: $@@mkdir -p $(dir $@)@rm -f $@$(hide) $(FILESLIST) $(TARGET_OUT_SYSTEM_OTHER) > $(@:.txt=.json)$(hide) build/make/tools/fileslist_util.py -c $(@:.txt=.json) > $@systemotherimage_intermediates := \$(call intermediates-dir-for,PACKAGING,system_other) BUILT_SYSTEMOTHERIMAGE_TARGET := $(PRODUCT_OUT)/system_other.img# Note that we assert the size is SYSTEMIMAGE_PARTITION_SIZE since this is the 'b' system image. define build-systemotherimage-target$(call pretty,"Target system_other fs image: $(INSTALLED_SYSTEMOTHERIMAGE_TARGET)")@mkdir -p $(TARGET_OUT_SYSTEM_OTHER)@mkdir -p $(systemotherimage_intermediates) && rm -rf $(systemotherimage_intermediates)/system_other_image_info.txt$(call generate-userimage-prop-dictionary, $(systemotherimage_intermediates)/system_other_image_info.txt, skip_fsck=true)$(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \build/make/tools/releasetools/build_image.py \$(TARGET_OUT_SYSTEM_OTHER) $(systemotherimage_intermediates)/system_other_image_info.txt $(INSTALLED_SYSTEMOTHERIMAGE_TARGET) $(TARGET_OUT)$(hide) $(call assert-max-image-size,$(INSTALLED_SYSTEMOTHERIMAGE_TARGET),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)) endef# We just build this directly to the install location. INSTALLED_SYSTEMOTHERIMAGE_TARGET := $(BUILT_SYSTEMOTHERIMAGE_TARGET) ifneq (true,$(SANITIZE_LITE)) # Only create system_other when not building the second stage of a SANITIZE_LITE build. $(INSTALLED_SYSTEMOTHERIMAGE_TARGET): $(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_SYSTEMOTHERIMAGE_FILES) $(INSTALLED_FILES_FILE_SYSTEMOTHER)$(build-systemotherimage-target) endif.PHONY: systemotherimage-nodeps systemotherimage-nodeps: | $(INTERNAL_USERIMAGES_DEPS)$(build-systemotherimage-target)endif # BOARD_USES_SYSTEM_OTHER
device.mk打开ab后定义了生成system_other相关的宏# A/B BOARD_USES_SYSTEM_OTHER_ODEX := true,将system/app 和 system/priv-app下的odex文件存储到system_other.img中
这个img的生成有什么作用呢,查到这样一个说明
50% of system image is precompiled odex files (-2048MiB)
- Moved them to B partition
- Copied to /data on first boot
- See BOARD_USES_SYSTEM_OTHER_ODEX BoardConfig option
- Now A/B /system takes same space as non-A/B /system
意思是说将原来system里面的odex文件放到system_other.img中,刷机的时候放入B分区,并在首次开机的时候拷贝到data区进行预加载,这样ab升级中的system分区与非ab的情况一样,目的是为了减小system分区的大小,不过百分之50有些夸张了,我们编译出来的实际只有100多M,
开启AB升级方案的项目,因为很多需要升级的镜像都有两份,所以存储空间将会增大。为缓解此问题,有个针对odex的优化方案。
编译版本会生成两个system镜像:system.img和system_other.img,其中,system_other.img中存储的就是odex文件,这样system.img就能小很多,意味着可以为system分区划分较小的空间。
在首次开机时,假设system.img镜像存储在A slot,那么此时的B slot是闲置的。所以可以把system.img刷入A slot的system分区,把system_other.img刷入B slot的system分区。在首次开机时,再把system_other.img中的odex文件拷贝到data分区。
6、vendor.img 和 userdata.img
这两个img的生成在打开ab后没有变化,生成流程于system.img相同,根据对应info,调用build_image.py生成对应的文件
三、make 和make otapackage过程中的变化
1、build.prop中加入字段体现
make/tools/buildinfo.sh加入
if [ -n "$AB_OTA_UPDATER" ] ; then
echo "ro.build.ab_update=$AB_OTA_UPDATER"
fi
Makefile中会执行buildinfo.sh文件
$(intermediate_system_build_prop): $(BUILDINFO_SH) $(INTERNAL_BUILD_ID_MAKEFILE) $(BUILD_SYSTEM)/version_defaults.mk $(system_prop_file) $(INSTALLED_ANDROID_INFO_TXT_TARGET)............bash $(BUILDINFO_SH) >> $@
system/build.prop中增加:ro.build.ab_update=true
2、签名相关 Makefile
# Carry the public key for update_engine if it's a non-IoT target that # uses the AB updater. We use the same key as otacerts but in RSA public key # format. #如果update_engine是非IoT目标,则携带公钥 #使用AB更新程序。 我们使用与otacerts相同的密钥但使用RSA公钥format。 ifeq ($(AB_OTA_UPDATER),true) ifneq ($(PRODUCT_IOT),true) ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT_ETC)/update_engine/update-payload-key.pub.pem $(TARGET_OUT_ETC)/update_engine/update-payload-key.pub.pem: $(addsuffix .x509.pem,$(DEFAULT_KEY_CERT_PAIR))$(hide) rm -f $@$(hide) mkdir -p $(dir $@)$(hide) openssl x509 -pubkey -noout -in $< > $@ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_RECOVERY_ROOT_OUT)/etc/update_engine/update-payload-key.pub.pem $(TARGET_RECOVERY_ROOT_OUT)/etc/update_engine/update-payload-key.pub.pem: $(TARGET_OUT_ETC)/update_engine/update-payload-key.pub.pem$(hide) cp -f $< $@ endif endif
3、obj包的生成相关
#指定对应依赖 这个built_ota_tools其实生成的就是updater,如果是ab升级,obj包里不需要打包这个文件 ifeq ($(AB_OTA_UPDATER),true) updater_dep := system/update_engine/update_engine.conf else # Build OTA tools if not using the AB Updater. updater_dep := $(built_ota_tools) endif $(BUILT_TARGET_FILES_PACKAGE): $(updater_dep)
$(BUILT_TARGET_FILES_PACKAGE): ...... ......#如果是非AB,拷贝对应image和生成ota_update_list.txt,如果是ab,生成ab_partitions.txt,拷贝对应image@# Copy raw images which need OTA updates from out folder to zip_root/IMAGES folder$(hide) BOARD_AVB_ENABLE="$(BOARD_AVB_ENABLE)" AB_OTA_UPDATER="$(AB_OTA_UPDATER)" AB_OTA_PARTITIONS="$(AB_OTA_PARTITIONS)" $(TARGET_RELEASETOOLS_EXTENSIONS)/mt_ota_preprocess.py $(zip_root) $(PRODUCT_OUT) $(PRODUCT_OUT)/ota_update_list.txt ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)$(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \build/make/tools/releasetools/make_recovery_patch $(zip_root) $(zip_root) endif ifeq ($(AB_OTA_UPDATER),true)@# When using the A/B updater, include the updater config files in the zip.#拷贝update_engine.conf 到META/update_engine_config.txt$(hide) cp $(TOPDIR)system/update_engine/update_engine.conf $(zip_root)/META/update_engine_config.txt$(hide) for part in $(AB_OTA_PARTITIONS); do \#把AB_OTA_PARTITIONS定义的分区拷贝到META/ab_partitions.txtecho "$${part}" >> $(zip_root)/META/ab_partitions.txt; \done$(hide) for conf in $(AB_OTA_POSTINSTALL_CONFIG); do \#把AB_OTA_POSTINSTALL_CONFIG定义的内容拷贝到META/postinstall_config.txtecho "$${conf}" >> $(zip_root)/META/postinstall_config.txt; \done@# Include the build type in META/misc_info.txt so the server can easily differentiate production builds.#build_type放入misc_info$(hide) echo "build_type=$(TARGET_BUILD_VARIANT)" >> $(zip_root)/META/misc_info.txt#ab_update放入misc_info$(hide) echo "ab_update=true" >> $(zip_root)/META/misc_info.txt ifdef BRILLO_VENDOR_PARTITIONS$(hide) mkdir -p $(zip_root)/VENDOR_IMAGES$(hide) for f in $(BRILLO_VENDOR_PARTITIONS); do \pair1="$$(echo $$f | awk -F':' '{print $$1}')"; \pair2="$$(echo $$f | awk -F':' '{print $$2}')"; \src=$${pair1}/$${pair2}; \dest=$(zip_root)/VENDOR_IMAGES/$${pair2}; \mkdir -p $$(dirname "$${dest}"); \cp $${src} $${dest}; \done; endif ...... ......
4、整包生成相关
AB情况下添加依赖,可执行文件brillo_update_payload,非AB情况下添加依赖brotli
ifeq ($(AB_OTA_UPDATER),true) $(INTERNAL_OTA_PACKAGE_TARGET): $(BRILLO_UPDATE_PAYLOAD) else $(INTERNAL_OTA_PACKAGE_TARGET): $(BROTLI) endif
关于device和build大致整理了这么多内容,因为我也是刚开始看,可能没有整理全,后面再继续填坑。
一、A/B升级之系统image的生成相关推荐
- p40 升级鸿蒙,等了这么久,我的P40终于可以升级鸿蒙系统了
原标题:等了这么久,我的P40终于可以升级鸿蒙系统了 期待了好久,我的p40终于可以用上华为鸿蒙OS了,因为华为说了今年4月将会在华为旗舰手机上进行升级,首批支持P40系列.Mate40系列以及Mat ...
- 华为p4支持鸿蒙功能吗_吹过的牛都一步一步给实现了!明年华为手机支持升级鸿蒙系统!...
12月16日,华为举行 HarmonyOS 2.0 手机开发者 Beta 活动,现场正式发布了 HarmonyOS 2.0 手机开发者 Beta 版本.据悉,本次 HarmonyOS 2.0 公测设备 ...
- matepad什么时候升级鸿蒙,华为MatePad Pro迎来EMUI 11正式版升级 后续可直接升级鸿蒙系统...
原标题:华为MatePad Pro可升级EMUI 11正式版了 快来尝鲜! 据华为EMUI官方消息,华为MatePad Pro与MatePad Pro 5G两款平板迎来EMUI 11正式版升级.用户打 ...
- 荣耀手机都不更新鸿蒙系统吗,华为EMUI不会更新了!直接升级鸿蒙系统,荣耀手机也不会放弃...
今年华为是肯定会推出手机版以及平板电脑上的鸿蒙系统的,只不过现在华为还没有正式宣布什么时候更新.之前传闻华为会在三月份推出EMUI系统的最后一个版本--EMUI 11.1,并且会采用鸿蒙的内核.但是现 ...
- 华为手机如何升级鸿蒙系统_华为杨海松:明年所有华为自研设备升级鸿蒙系统...
欢迎点击上面ZAKER关注 本文内容来自ZAKER合作媒体PingWest品玩 品玩 12 月 16 日讯,鸿蒙 Harmony OS 2.0 手机开发 Beta 版会后,华为消费者业务软件部副总裁杨 ...
- 如何预约升级鸿蒙,超过66万人预约,华为亮出真正王牌旗舰,支持优先升级鸿蒙系统...
新年刚过不久,华为便在智能手机领域投下了一颗重磅炸弹--华为Mate X2折叠屏. 华为已经是折叠屏领域的老将,已经发布Mate X.Mate Xs两款折叠屏产品,积攒下了丰富的经验.因而,华为Mat ...
- 安卓9全局圆角_三星S9+升级最新系统ONE UI体验,安卓9.0带来哪些惊喜?
戳上面的蓝字关注我们哦! 大家好!我是小马哥! 精致有趣的科技数码体验与测评 尽在"来回科技" 有锁机全面科普 科普丨什么是有锁机/卡贴机 信号测试丨有锁机vs无锁机 全新来回商城 ...
- 升级鸿蒙系统的手机名单,倒计时2天!首批鸿蒙OS适配名单确定,你的手机在列吗?...
华为鸿蒙系统一直是最近的一个热点话题,早在2012年,任正非就已经意识到了安卓系统垄断会对国内手机行业造成威胁,因此任正非也是在此时正式建立了鸿蒙系统的研发基地,一晃九年过去了,鸿蒙系统如今已经羽翼丰 ...
- 手机如何升级为鸿蒙系统,华为手机如何升级鸿蒙系统
华为手机是大家非常喜欢的国产品牌,不少使用华为手机的朋友想要升级鸿蒙系统.今天小编给各位讲解一下华为手机如何升级鸿蒙系统,有需要的朋友们就来兔叽下载站看一下华为手机升级鸿蒙系统的详细步骤分享.相信小编 ...
最新文章
- python使用difflib对比文件示例
- 如何避免重构带来的危险
- 格力入局的数控机床,掌握“核心科技”有多难?
- sqlanyshere转mysql_【SQL】Oracle和Mysql的分页、重复数据查询(limit、rownum、rowid)
- JQuery 选择器处理特殊字符
- mysql主从skip1677_解决字符集不同引起的主从同步异常1677报错问题
- Linux下搭建Tomcat服务器
- 通过命令行方式批量设置保留IP地址的代码
- 动态规划 dp02 最长非降子序列问题 c代码
- Flink从入门到精通100篇(二十三)-Apache Flink在滴滴的应用与实践
- python中集合用法大全_python中集合的用法
- Java的JDBC事务详解
- Hook API (C++)
- 【英语学习】【WOTD】brummagem 释义/词源/示例
- bzoj 4236: JOIOJI(map+pair)
- C#网站发布在IIS10上,Access数据库读取为空白的解决方案
- Android创建并响应选项菜单
- WebService之CXF框架
- Winform调用风云二号卫星云图(更改后版本)
- 高级测试开发工程师简历模板
热门文章
- 车载以太网线束-“燃”AEM传输性能测试方案
- Python || 青蛙跳台阶
- std::atomic、std::async深入研究
- 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java垃圾回收系统j16l0
- Java工程师培训课(十)
- 智能无线路由器硬件连接步骤——以JCG智能无线路由器为例
- 转让(出售)二手多功能小吃车
- python 创建excel,操作excel,保存excel,修改excel,删除sheet页
- 史兴国对谈于佳宁:从经济模式到落地应用,Web3的中国之路怎么走?
- 新版白话空间统计(20)空间关系概念化之点临近