• [技术干货] 元服务调测无需上架,App Linking助力高效分发元服务链接
           元服务已经开发完却无法进行效果测试,上架后又担心影响现网用户体验。为解决开发者在元服务调测场景中的核心痛点,App Linking 全新升级实用功能 —— 支持未上架元服务进行全阶段调测,无需等待上架且不影响现网,让链接运营更高效、用户体验更流畅!核心功能:1. 开发者配置App Linking元服务链接后,在开发、邀请测试等阶段均可使用元服务链接进行功能调测;2. 针对二维码测试链接,若元服务链接关联了二维码,可以单独创建测试链接进行端到端真机调测,确认无误后再正式发布到现网,避免对存量的二维码产生影响,可以有效提升元服务质量!具体操作步骤如下:1、创建元服务链接时可正常在AGC控制台进行配置,若不关联二维码,可直接使用元服务链接进行测试。2、若选择关联二维码,可创建测试链接进行调试,链接需符合匹配规则,多个测试链接使用英文分号分隔。3、点击“保存”后,元服务链接为“草稿态”,您可以查看、编辑或者删除链接信息。开发者可扫描由测试链接生成的二维码进行端到端调测。4、调测确认无误后可点击“发布”,AGC会判断当前时间是否在链接有效期内,并实时更新元服务链接状态:“待生效”、“已生效”或“已失效”。        App Linking元服务链接和融合码是华为AppGallery Connect(简称AGC)向鸿蒙开发者提供的链接跳转元服务能力,可用于通过点击链接或扫描存量二维码实现跳转元服务指定页面的功能,实现元服务的点击触达、即点即用,为鸿蒙用户带来便捷的体验。        对于开发者而言,App Linking 不只是简单的链接工具,更是提升用户使用体验的核心利器。它打通 “用户触达” 与 “服务落地”,让应用与用户连接更高效。点击下方链接,即刻开启鸿蒙生态场景化运营新篇章 ——App Linking 。        AppGallery Connect致力于为应用的创意、开发、分发、运营、经营各环节提供一 站式服务,构建全场景智慧化的应用生态体验。为给您带来更好服务,请扫描下方二维码或者点击此处免费咨询。        如您有任何疑问,请发送邮件至agconnect@huawei.com咨询,感谢您对HUAWEI AppGallery Connect的支持!
  • [技术干货] 鸿蒙APMS:开箱即用,崩溃卡顿耗电秒级捕捉
    在移动应用生态中,用户体验直接决定产品成败。面对崩溃、卡顿、耗电等现网性能问题,华为AppGallery Connect(简称AGC)向开发者提供了一个现网质量监测解决方案—应用性能监测服务(Application Performance Management Service,简称APMS)——开箱即用,崩溃卡顿耗电秒级捕捉,守护应用全生命周期,为开发者打造免集成、全覆盖、实时化的质量监测方案。​​核心优势:四大优势助力性能监测​​l  ​​免集成,开箱即用​​APMS以鸿蒙系统服务形式运行,​​无需应用内嵌SDK或初始化组件​​,开通即可启用,大幅降低接入成本。l  ​​全生命周期覆盖​​APMS的异常信息采集动作不受应用初始化、组件加载影响,全面覆盖应用启动、运行、前/后台、异常终止各阶段、状态的异常。l  ​​实时上报与精准统计​​通过系统级服务独立执行数据上传,​​秒级捕捉崩溃、ANR(应用无响应)、帧率异常等问题​​,并提供环境上下文、完整堆栈轨迹及多维度分析报表。异常信息上传动作由系统服务执行,与应用运行状态无关,不依赖应用重新启动,实时上报。l  ​​多维分析灵活查询​​可提供每个问题发生时的环境信息、堆栈信息等分析数据,并支持基于堆栈关键行进行准确的同类异常汇聚,支持问题标记、指标告警等辅助能力,帮助开发者高效聚焦和解决高频问题。​​案例展示: 助力游戏应用崩溃率从0.48%优化至0.35%(降幅27%)​​ 某游戏应用接入APMS服务后,依托其开箱即用的便捷性、全程守护及快速响应的高效性,关键指标实现显著改善:应用崩溃率从0.48%优化至0.35%(降幅27%),7日内触发崩溃的独立用户数环比下降52%。APMS提供的实时监测数据与异常根因分析报告,助力开发团队高效闭环问题,持续夯实用户体验基线。点击链接即可体验:APMS业务。(上述数据来源于合作伙伴实践反馈,具体效果以实际场景为准)AppGallery Connect致力于为应用的创意、开发、分发、运营、经营各环节提供一 站式服务,构建全场景智慧化的应用生态体验。为给您带来更好服务,请扫描下方二维码或者点击此处免费咨询。  如您有任何疑问,请发送邮件至agconnect@huawei.com咨询,感谢您对HUAWEI AppGallery Connect的支持!
  • [技术干货] 「软件包管理 + 上架自检」双管齐下,提升上架审核通过率
    还在为应用上架反复碰壁而头疼?别担心,HUAWEI AppGallery Connect 云测试推出的“软件包管理--上架自检”能力,通过前置合规校验、模拟真实环境测试并出具详细的测试报告。平台配置多种型号鸿蒙真机,覆盖主流/热门机型,满足多样化测试场景需求。每天提供300分钟的免费使用时间,足够支撑新手尝鲜、轻量级项目测试或多次验证,​​0成本起步测试,立省真机购买投入!一、解锁新技能:这个 “应用管家” 不一般​全新上线的“软件包管理”能力,从应用提交伊始即提供全流程辅助,像一个贴心的私人“应用管家”。开发者上传软件包后,系统将​​自动进行基础合法性检测​​,校验其是否符合鸿蒙生态基本规范。支持开发者按需选择“仅测试”或“测试及正式上架”场景,满足不同阶段的验证需求。上传后即刻获取软件包合规状态(“已达标” “待优化”或“不通过”),清晰呈现应用与上架基础要求的差距。更为核心的是“上架自检”功能:严格参照华为应用市场准入标准,在热门的移动终端设备上,对应用的​​兼容性、稳定性、性能、功耗及用户体验(UX)​​ 进行全面自动化检测,让开发者在上架前及时发现并修复问题。每次检测均生成详尽报告,明确罗列问题点并提供优化建议,大幅减少反复修改的成本和时间。​二、操作指南:三步高效管理软件包​​​​1.登录配置:​​ 登录 ​​AppGallery Connect​​,点击“APP”,选择要发布的应用。​​导航入口:​​ 在左侧导航栏选择 ​​“应用上架 > 软件包管理”​​。​​2.上传与确认:​​ 点击右上角“上传”,选择应用使用场景(“仅测试”或“测试和正式上架”),添加软件包文件。  ​​3.结果解读:​​上传成功后,页面生成软件包记录。您可在“合法性”栏查看合法性检测结果。l  ​​已达标:​​ 符合鸿蒙生态规范,可提交上架。点击“报告”可查看详细的检测报告。l  ​​待优化:​​ 可尝试提交上架,但存在驳回风险。建议点击“报告”查看并修复问题。l  ​​不通过:​​ 不满足上架基本要求,不允许上架,需根据报告建议修改后重新上传。  三、进阶技巧:上架自检,为应用质量加码强烈建议开发者充分利用​​“上架自检”​​功能,为应用质量增加一层保障。1.在软件包记录的“操作”列点击“启动自检”。​​请注意:​​ 每个应用同时仅允许一个自检任务运行;即使删除相关软件包,启动自检仍会计入服务使用配额。2.启动后在“上架自检”栏可查看状态及结果:l  ​​检测中:表示正在检测软件包,检测时长可能受终端设备数量和排队情况影响。l  通过:​​ 可安心提交上架申请。l  ​​待优化:​​ 虽可提交,但驳回风险较高,强烈建议按报告指引进行优化。  HUAWEI AppGallery Connect 云测试提供​​海量鸿蒙真机​​在线免预约,可全面检测应用兼容性、性能、稳定性、功耗及UX等关键指标,​​首次试用赠300分钟​​!轻量测试0成本起步,极简操作,高效输出报告。成本低、易上手,​​点此立即试用 >>​ AppGallery Connect致力于为应用的创意、开发、分发、运营、经营各环节提供一站式服务,构建全场景智慧化的应用生态体验。为给您带来更好服务,请扫描下方二维码或者点击此处免费咨询。  如您有任何疑问,请发送邮件至agconnect@huawei.com咨询,感谢您对HUAWEI AppGallery Connect的支持!
  • [问题求助] 鸿蒙版如何安装vsix插件?
    看到其他的端有插件的入口,鸿蒙版还没有,请问后续有没有开放计划?
  • [问题求助] 【问题咨询:如何在鸿蒙系统的笔记本上安装python包】鸿蒙操作系统笔记本 使用 code art ide 时,安装python包时报错,无法自动识别pip,使用python -m pip install --user 安装时报错
    鸿蒙操作系统笔记本 使用 code art ide 时,安装python包时报错,无法自动识别pip,使用python -m pip install --user 安装时报错.1.在hishell 无法识别python  2.在code art ide 的终端可以识别python 但无法识别pip 3.使用  python -m pip install --user numpy 报错   
  • [技术干货] 【HarmonyOS Next之旅】DevEco Studio使用指南(三十五) -> 配置构建(二)-转载
    1 -> 定制HAP多目标构建产物每一个Entry/Feature模块均支持定制不同的target,通过在模块中的build-profile.json5文件中实现差异化定制,当前支持HAP包名、设备类型(deviceType)、源码集(source)、资源(resource)、buildOption配置项(如C++依赖的.so、混淆配置、abi类型、cppFlags等)、分发规则(distributionFilter)的定制。定义目标产物target每一个target对应一个定制的HAP,因此,在定制HAP多目标构建产物前,应提前规划好需要定制的target名称。例如,以ArkTS Stage模型为例,定义一个免费版和付费版,模块级build-profile.json5文件示例如下:{   "apiType": 'stageMode',   "buildOption": {     },   "targets": [  //定义不同的target     {       "name": "default",  //默认target名称default     },     {       "name": "free",  //免费版target名称     },     {       "name": "pay",  //付费版target名称     }   ] }AI写代码按照上述target的定义,在编译构建时,会同时打包生成default、free和pay三个不同的HAP。1.1 -> 定义产物的HAP包名每一个target均可以指定产物命名。{   "apiType": "stageMode",   "buildOption": {   },   "targets": [     {       "name": "default",       "output": {         "artifactName": "customizedTargetOutputName-1.0.0"  //产物名称为customizedTargetOutputName-1.0.0      }     },     {       "name": "free",       "output": {         "artifactName": "customizedTargetOutputName1-1.0.0"  //产物名称为customizedTargetOutputName1-1.0.0      }     },     {       "name": "pay",       "output": {         "artifactName": "customizedTargetOutputName2-1.0.0"  //产物名称为customizedTargetOutputName2-1.0.0      }     }   ] }AI写代码如果已配置签名,target产物对应的HAP包名为开发者定制的名称;如果未配置签名,target产物对应的HAP包名为开发者定制的名称+unsigned。1.2 -> 定义产物的deviceType每一个target均可以指定支持的设备类型deviceType,也可以不定义。如果不定义,则该target默认支持config.json或module.json5中定义的设备类型。同时,在定义每个target的deviceType时,支持的设备类型必须在config.json或module.json5中已经定义。例如,在上述定义的3个target中,分别定义default默认支持所有设备类型,free和pay版本只支持phone设备。{   "apiType": 'stageMode',   "buildOption": {   },   "targets": [     {       "name": "default",  //未定义deviceType,默认支持config.json或module.json5中定义的设备类型       },     {       "name": "free",       "config": {         "deviceType": [  //定义free支持的设备类型为phone           "phone"         ]       }     },     {       "name": "pay",       "config": {         "deviceType": [  //定义pay支持的设备类型为phone           "phone"         ]       }     }   ] }AI写代码1.3 -> 定义产物的distributionFilter在未定义target的分发规则distributionFilter时,以module配置distroFilter/distributionFilter分发规则为准。针对多target存在相同设备类型deviceType的场景,相同设备类型的target需要指定分发规则distributionFilter。如果是FA工程,请将distributionFilter字段替换为distroFilter。{   "apiType": "stageMode",   "buildOption": {   },   "targets": [     {       "name": "default",       },     {       "name": "free",       "config": {         "distributionFilter": {  // 具体请参考distributionFilter标签          "screenShape": { // 屏幕形状枚举             "policy": "include",             "value": ["circle"]           }         }       }     },     {       "name": "pay",       "config": {         "distributionFilter": {           "screenShape": {             "policy": "include",             "value": ["rect"]           }         }       }     }   ] }AI写代码1.4 -> 定义产物preloads的分包对于元服务,每一个target均可以指定preloads的分包,也可以不定义。如果不定义,则以module.json5中的配置为准。{   "apiType": 'stageMode',   "showInServiceCenter": true,   "buildOption": {   },   "targets": [       {       "name": "default",       },    {       "name": "free",       },    {       "name": "pay",         "config": {         "atomicService": {           "preloads": [  //指定preloads的分包             {               "moduleName": "preloadSharedLibrary"            }           ]         }       }     }   ] }AI写代码1.5 -> 定义产物的source源码集-pages对于source源码集的定制,由于Stage模型和FA模型的差异,Stage模型支持对pages源码目录的page页面进行定制,FA模型则支持对Ability源码目录下的page页面进行定制。例如,Stage模型中的工程,在模块的pages目录下分别定义了Index.ets、Page1.ets和Page2.ets三个页面。其中default使用了Index.ets页面;free使用了Index.ets和Page1.ets页面;pay使用了Index.ets和Page2.ets页面,则示例代码如下所示:{    "apiType": 'stageMode',    "buildOption": {    },    "targets": [      {        "name": "default",        "source": {  //定义Stage模型中默认版target的pages源码文件         "pages": [            "pages/Index"          ]        }      },      {        "name": "free",        "config": {          "deviceType": [            "phone"          ]        },        "source": {  //定义Stage模型中免费版target的pages源码文件         "pages": [            "pages/Index",            "pages/Page1"          ]        }      },      {        "name": "pay",        "config": {          "deviceType": [            "phone"          ]        },        "source": {  //定义Stage模型中付费版target的pages源码文件         "pages": [            "pages/Index",            "pages/Page2"          ]        }      }    ]  }AI写代码例如,FA模型中的工程,在模块的MainAbility中定义了index.ets、page1.ets和page2.ets,其中:default使用了index.ets 页面;free使用了index.ets和page1.ets页面;pay使用了index.ets和page2.ets页面。{    "apiType": 'faMode',    "buildOption": {    },    "targets": [      {        "name": "default",        "source": {  //定义FA模型中默认版target的pages源码文件         "abilities": [            {              "name": ".MainAbility",              "pages": [                "pages/index"              ]            }          ],        }      },      {        "name": "free",        "config": {          "deviceType": [            "phone"          ]        },        "source": {  //定义FA模型中免费版target的pages源码文件         "abilities": [            {              "name": ".MainAbility",              "pages": [                "pages/index",                "pages/page1"              ]            }          ],        }      },      {        "name": "pay",        "config": {          "deviceType": [            "phone"          ]        },        "source": {  //定义FA模型中付费版target的pages源码文件         "abilities": [            {              "name": ".MainAbility",              "pages": [                "pages/index",                "pages/page2"              ]            }          ],        }      }    ]  }AI写代码1.6 -> 定义产物的source源码集-sourceRoots在模块的主代码空间(src/main)下,承载着开发者编写的公共代码。如果开发者需要实现不同target之间的差异化逻辑,可以使用差异化代码空间(sourceRoots)。配合差异化代码空间的能力,可以在主代码空间中代码不变的情况下,针对不同的target,编译对应的代码到最终产物中。概念说明packageName:当前模块的oh-package.json5中的name字段对应的值。sourceRoot:<defaultSourceRoot> | <targetSourceRoot> ,其中<defaultSourceRoot>是 src/main,<targetSourceRoot>可自定义,寻址优先级为 <targetSourceRoot> > <defaultSourceRoot>。sourcePath:在sourceRoot中的代码结构目录。sourceFileName:代码目录下的ets文件名。例如以下工程目录:entry|--src|----main|------ets|--------code|----------test.ets|----target|------util|--------util.ets|--index.etspackageName为entry。sourceRoot为src/main、src/target。sourcePath为ets/code、util。sourceFileName为test.ets、util.ets。规格限制1. import xxx from '<packageName>/sourcePath/sourceFileName' :通过packageName的方式,省略sourceRoot,可以实现不同target下的差异化构建。2. 支持hap、hsp、har(请注意:开启文件/文件夹名称混淆的har模块需要使用-keep-file-name指定sourceRoot,sourcePath,sourceFileName对应的文件/文件夹名称不被混淆)。3. 不支持跨模块引用。4. 不支持动态import。编译时模块target的选择优先级说明在模块编译的过程中,该模块使用的sourceRoots由当前模块编译时的target来决定。当前模块编译时选择target的优先级则为:命令行显式指定 > 直接引用方target > default。如以下示例:hap -> hsp -> har(->表示依赖)其中hap和hsp存在三个target:default、custom、static,而har存在两个target:default、static。执行编译命令:hvigorw -p module=hap@custom assembleHap,hap指定target为custom进行编译,那么三个模块编译时的target分别为:hap: custom,命令行显式指定;hsp: custom,命令行没有显式指定,则基于直接引用方查找,hsp的直接引用方为hap,hap的target为custom,hsp存在该target,则hsp的target为custom;har: default,命令行没有显式指定,则基于直接引用方查找,har的直接引用方为hsp,hsp的target为custom,har不存在该target,则har的target为default;执行编译命令:hvigorw -p module=hap@custom,hsp@static assembleHap assembleHsp,hap指定target为custom,hsp则指定target为static进行编译,那么三个模块编译时的target分别为:hap: custom,命令行显式指定;hsp: static,命令行显式指定;har: static,命令行没有显式指定,则基于直接引用方查找,har的直接引用方为hsp,hsp的target为static,har存在该target,则har的target为static。在当前依赖关系的基础上,添加依赖:hap -> har。执行编译命令:hvigorw -p module=hap@custom,hsp@static assembleHap assembleHsp。由于har没有显示指定target,且存在两个target不同的直接引用方(hap和hsp,对应的target分别为custom和static),所以编译过程中har的target只能二选一。基于这种场景,建议开发者显示指定模块的target进行编译:hvigorw -p module=hap@custom,hsp@static,har@static assembleHap assembleHsp assembleHar。示例1. 在entry模块的build-profile.json5中添加sourceRoots:{  "apiType": "stageMode",  "buildOption": {},  "targets": [     {       "name": "default",       "source": {         "sourceRoots": ["./src/default"] // 配置target为default的差异化代码空间      }     },     {       "name": "custom",       "source": {         "sourceRoots": ["./src/custom"] // 配置target为custom的差异化代码空间      }     }   ]}AI写代码2. 在src目录下新增default/Test.ets和custom/Test.ets,新增后的模块目录结构:entry  |--src    |--main      |--ets        |--pages          |--Index.ets    |--default      |--Test.ets  // 新增    |--custom      |--Test.est  // 新增  3. 在default/Test.ets中写入代码:export const getName = () => "default"AI写代码4. 在custom/Test.ets中写入代码:export const getName = () => "custom"AI写代码5. 修改src/main/ets/pages/Index.ets的代码:import { getName } from 'entry/Test'; // 其中entry为模块级的oh-package.json5中的name字段的值@Entry@Componentstruct Index {   @State message: string = getName();   build() {     RelativeContainer() {       Text(this.message)     }     .height('100%')     .width('100%')   }}AI写代码6. 在工程级的build-profile.json5中配置targets:{  "app": {    "signingConfigs": [],    "products": [      {        "name": "default",        "signingConfig": "default",        "compatibleSdkVersion": "5.0.2(14)",        "runtimeOS": "HarmonyOS",      },      {        "name": "custom",        "signingConfig": "default",        "compatibleSdkVersion": "5.0.2(14)",        "runtimeOS": "HarmonyOS",      }    ],    "buildModeSet": [      {        "name": "debug",      },      {        "name": "release"      }    ]  },  "modules": [    {      "name": "entry",      "srcPath": "./entry",      "targets": [        {          "name": "default",          "applyToProducts": [            "default"          ]        },        {          "name": "custom",          "applyToProducts": [            "default"          ]        }      ]    }  ]}AI写代码7. Sync完成后,选择entry的target为default,点击Run,界面展示default;选择entry的target为custom,点击Run,则界面展示custom。1.7 -> 定义产物的资源每个target使用的资源文件可能存在差异,在开发过程中,开发者可以将每个target所使用的资源存放在不同的资源目录下。其中,ArkTS工程支持对main目录下的资源文件目录(resource)进行定制;JS工程支持对main目录下的资源文件目录(resource)及 Ability下的资源文件目录(res)进行定制。如下为ArkTS工程的资源文件目录定制示例:{   "apiType": 'stageMode',   "buildOption": {   },   "targets": [     {       "name": "default",       "source": {         "pages": [           "pages/Index"         ]       },       "resource": {  //定义默认版target使用的资源文件目录         "directories": [           "./src/main/resources_default"         ]       }     },     {       "name": "free",       "config": {         "deviceType": [           "phone"         ]       },       "source": {           "pages": [           "pages/Index",           "pages/Page1"         ]       },       "resource": {  //定义免费版target使用的资源文件目录         "directories": [           "./src/main/resources_default",           "./src/main/resources_free"         ]       }     },     {       "name": "pay",       "config": {         "deviceType": [           "phone"         ]       },       "source": {           "pages": [           "pages/Index",           "pages/Page2"         ]       },       "resource": {  //定义付费版target使用的资源文件目录,该功能在API 9及以上版本的工程中生效         "directories": [           "./src/main/resources_default",           "./src/main/resources_pay"         ]       }     }   ] }AI写代码请注意,如果target引用的多个资源文件目录下,存在同名的资源,则在构建打包过程中,将按照配置的资源文件目录顺序进行选择。例如,上述付费版target引用的资源中,resource_default和resource_pay中存在同名的资源文件,则resource_default中的资源会被打包到HAP中。1.8 -> 定义产物的icon、label、launchType针对每一个的target的ability,均可以定制不同的icon、label和launchType。如果不定义,则该target采用module.json5中module.abilities配置的icon、label,launchType默认为"singleton"。示例如下所示:{    "apiType": 'stageMode',    "buildOption": {    },    "targets": [      {        "name": "default",        "source": {        "abilities": [          {            "name": "EntryAbility",            "icon":"$media:layered_image",            "label":"$string:EntryAbility_label",            "launchType": "singleton"          }        ]      }     },      {        "name": "free",        "source": {        "abilities": [          {            "name": "EntryAbility",            "icon":"$media:layered_image",            "label":"$string:EntryAbility_label",            "launchType": "multiton"          }        ]      }     }   ]  }AI写代码1.9 -> 定义C++工程依赖的.so文件在 C++ 工程中,可以对每个target依赖的.so文件进行定制。例如某模块依赖了function1.so、function2.so和function3.so三个文件,其中target为default的产物依赖了function1.so和function2.so;其中target为vip的产物依赖了function1.so和 function3.so,则示例代码如下所示:{  "apiType": 'stageMode',  "buildOption": {    "externalNativeOptions": {      "path": "./src/main/cpp/CMakeLists.txt",      "arguments": [],      "abiFilters": [        "arm64-v8a",        "x86_64"      ],      "cppFlags": "",    }  },  "targets": [  //定义不同的target     {      "name": "default",      "config": {        "buildOption": {          "nativeLib": {            "filter": {              //按照.so文件的优先级顺序,打包最高优先级的function1.so文件               "pickFirsts": [                "**/function1.so"              ],              //排除不打包的function3.so文件               "excludes": [                "**/function3.so"              ],              //允许当.so中资源重名冲突时,使用高优先级的.so文件覆盖低优先级的.so文件               "enableOverride": true            }          }        }      }    },    {      "name": "vip",      "config": {        "buildOption": {          "nativeLib": {            "filter": {              //按照.so文件的优先级顺序,打包最高优先级的function1.so文件               "pickFirsts": [                "**/function1.so"              ],              //排除不打包的function2.so文件               "excludes": [                "**/function2.so"              ],              //允许当.so中资源重名冲突时,使用高优先级的.so文件覆盖低优先级的.so文件               "enableOverride": true            }          }        }      }    }  ]}AI写代码2 -> 构建定义的目标产物每个target对应一个HAP,每个product对应一个APP包,在编译构建时,如果存在多product或多target时,您可以指定编译具体的包。单击右上角的 图标,指定需要打包的Product及Target,然后单击Apply保存。例如选择"ProductA"中,entry模块对应的"free" Target。Product:选择需要构建的 APP 包。Build Mode:选择编译模式。Product Info:该APP包的BundleName和SigningConfig信息。Target Select:选择各个模块的Target,该Target需要包含在定义的Product中才能选择,如果未包含则显示"No Target to apply"。然后执行编译构建APP/HAP的任务:单击菜单栏的Build > Build Hap(s)/APP(s) > Build APP(s) ,构建指定的Product对应的APP。例如,按照上述设置,此时DevEco Studio将构建生成 ProductA 的 APP 包。default和ProductB的APP均不会生成。单击菜单栏的Build > Build Hap(s)/APP(s) > Build Hap(s),构建指定Product下的所有Target对应发的HAP。例如,按照上述配置,此时DevEco Studio将构建生成entry模块下default和free的HAP。如果您想将某个模块下的指定target打包生成HAP,可以在工程目录中,单击模块名,然后再单击Build > Make Module ‘模块名 ’,此时DevEco Studio将构建生成模块下指定target对应的包。例如,按照上述配置,此时DevEco Studio将构建生成entry模块下free的HAP,不会生成default的HAP。————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/weixin_74809706/article/details/148765945
  • “秒开”时代,HarmonyOS预加载让页面加载快如闪电
    在用户体验为王的时代,应用“秒开”已成为用户对移动应用的核心期待。HarmonyOS预加载服务将应用页面的数据提前加载到本地缓存,页面打开直接从本地获取数据渲染,有效提升页面打开速度,解决应用页面加载慢、白屏的困境。一、安装预加载:助力“凯叔讲故事”页面加载提速37.9%​安装预加载服务适用于安装后首次打开,应用首页加载提速场景。在应用安装时,将一些必要的资源,例如图片、音频、视频或数据文件,提前加载到本地进行缓存。当用户安装后首次打开应用时,直接调用本地缓存数据渲染页面,跳过网络请求环节。优质儿童内容品牌“凯叔讲故事”非常重视用户体验,接入安装预加载后,页面加载提速37.9%。  实际案例应用:凯叔讲故事 二、周期性预加载:动态场景即发即现​​周期性预加载服务适用于任意页面加载提速的场景,可与安装预加载结合使用。系统每隔12小时拉取一次指定页面(不局限首开页面)的云侧数据并将其缓存到本地,并可将静态资源放置到云端,减少包体大小。开发者可在节日活动开始前通过周期性预加载服务提前将主题资源获取到本地,用户访问时直接从本地获取即可,减少了网络请求的时间和带宽消耗,实现即发即现的效果,提升用户体验。三、FastWeb:移动端H5页面的“隐形加速器”FastWeb组件是一个高性能Web容器,针对App中H5页面可以提供预启动、预渲染、预编译、离线资源免拦截注入等能力。其可提前加载高频页面资源并优化渲染流程,有效解决传统H5页面的白屏、卡顿问题。某省政务服务App接入FastWeb后,页面打开速度提升超32.9%。  (上述数据来源于合作伙伴实践反馈,具体效果以实际场景为准)AppGallery Connect致力于为应用的创意、开发、分发、运营、经营各环节提供一站式服务,构建全场景智慧化的应用生态体验。为给您带来更好服务,请扫描下方二维码或者点击此处免费咨询。         如您有任何疑问,请发送邮件至agconnect@huawei.com咨询,感谢您对HUAWEI AppGallery Connect的支持!
  • 节约测试成本50%,崩溃率直降72%,云测云调助力质效双提升
    产品质量不仅是企业的生命线,更是用户体验的第一道门。 HUAWEI AppGallery Connect “云测+云调”双引擎,助力开发者轻松跨越从代码到产品的最后一公里! 一、云测试:真机云端护航,高效测试省成本移动应用生命周期中的测试环节,常面临成本高企、效率低下、技术门槛高等挑战。HUAWEI AppGallery Connect 云测试提供一站式解决方案:海量热门主流真机,可全面检测应用兼容性、性能、稳定性、功耗及UX等关键指标。测试完成后快速输出详尽专业的测试报告,精准定位问题并提供修复建议,显著提升应用质量与发布效率,大幅降低测试成本与资源投入。二、云调试:彻底告别"设备荒",调试效率提升50%HUAWEI AppGallery Connect云调试为开发者提供海量移动终端设备支持,解决设备机型不足、设备管理困难及bug无法复现等问题,支持7x24小时远程调测,彻底告别"设备荒"。真实运行环境精准复现用户场景,断点、日志即时获取,调试效率提升50%。用云端弹性资源替代固定资产投入,实现开发效能与成本支出双优化,让调试瓶颈迎刃而解。三、双剑合璧,节约测试成本50%,崩溃率直降72%15日天气预报应用,应用版本更新快,新版本上线时间紧迫。通过云测试全面的测试报告,开发者快速发现了不同机型上出现的无响应、UI异常、崩溃等问题,节约测试成本50%。并通过云调试快速定位,崩溃率下降了72%。(上述数据来源于合作伙伴实践反馈,具体效果以实际场景为准)AppGallery Connect致力于为应用的创意、开发、分发、运营、经营各环节提供一站式服务,构建全场景智慧化的应用生态体验。为给您带来更好服务,请扫描下方二维码或者点击此处免费咨询。  如您有任何疑问,请发送邮件至agconnect@huawei.com咨询,感谢您对HUAWEI AppGallery Connect的支持!
  • [技术干货] 开发周期缩短50%,云开发服务让开发更高效
    在数字化浪潮下,开发者们始终在追寻更高效的开发方式。传统开发模式效率低,人力、运维、服务器等成本问题,逐渐成为开发者创新的“绊脚石”。 Cloud Foundation Kit(云开发服务)为开发者提供了鸿蒙应用/元服务高效开发的解决方案,您可按需使用云函数、云数据库、云存储、预加载等云端服务,专注于应用的创新和业务逻辑的实现,开发周期缩短50%! ​​一、低成本运维:从“负重前行”到“轻装上阵”​云开发服务​​通过提供开箱即用的云函数、数据库、存储等后端核心能力,使开发者无需手动构建和管理云端资源,即可快速搭建完整的后端服务,显著提升应用功能的开发与上线效率。同时,​​云开发服务​​为各类能力配置了免费资源额度,降低新用户的试错成本和初期资源投入。  二、资源弹性伸缩, 减少闲置资源浪费云开发服务​​通过弹性伸缩与按量计费机制,有效解决了传统架构应对业务波峰波谷的痛点。在业务高峰时,系统可自动扩容保障稳定性;当业务量回落低谷时,资源自动收缩,​​避免因采购高性能服务器集群导致的资源闲置浪费​​,使开发者​​仅需为实际消耗的资源付费​​,显著提升资源利用率。同时,​​云开发服务为各类能力配置了免费资源额度, ​​对于测试验证阶段原型或功能简单的轻量级APP​​,其免费额度已能覆盖核心业务需求,大幅降低初期投入门槛。  三、端云一体化,端侧开发轻松变全栈DevEco Studio的端云一体化开发能力​​通过整合HarmonyOS应用/元服务的端侧与云侧开发流程,为开发者提供高效开发“快车道”——开发者仅需一套开发工具、一个开发团队,即可同时完成HarmonyOS应用/元服务的端侧与云侧开发,前端工程师轻松化身为全栈工程师,大幅缩短应用上线周期。(数据为典型场景下的参考值,实际效果会因应用复杂度等因素有所不同)AppGallery Connect致力于为应用的创意、开发、分发、运营、经营各环节提供一站式服务,构建全场景智慧化的应用生态体验。为给您带来更好服务,请扫描下方二维码或者点击此处免费咨询。  如您有任何疑问,请发送邮件至agconnect@huawei.com咨询,感谢您对HUAWEI AppGallery Connect的支持!
  • [技术干货] App Linking助力应用一键直达,操作效率提升30%以上!
    如何缩短内容与用户的触达路径,如何覆盖多样化的分发渠道并差异化运营?华为AppGallery Connect(简称AGC)向开发者推出App Linking技术服务。此服务是HarmonyOS系统层为开发者开放的统一链接能力,不仅有效提升了用户体验,而且也提升了链接转换率。案例展示:1、 哔哩哔哩,碰一碰覆盖线下内容分享,操作步骤立省60%哔哩哔哩接入App Linking与碰一碰分享服务,通过碰一碰分享B站视频。相较于传统的通信软件分享视频,操作步骤立省60%。2、美团一扫即达,服务快人一步,操作效率提升30%以上美团App接入App Linking,无需用户打开App,通过系统扫码直接解锁共享单车、租借充电宝。负一屏、控制中心、系统相机均可解锁,操作入口增加3倍。一步扫码直达,操作效率提升30%以上。  3、多乐掼蛋游戏,手机碰一碰快速闭环游戏邀请,操作步骤立省60%多乐掼蛋游戏,接入App Linking+碰一碰分享服务,实现手机碰一碰快速要求玩家加入游戏。全新的游戏邀请体验,操作便捷,趣味十足,适合宿舍、节假日家庭聚会等场景。相较于传统的通信软件分享视频,操作步骤立省60%。 鸿蒙App Linking现已助力多类型应用实现场景化链接跳转,目前该能力已开放给HarmonyOS开发者,诚邀您体验智能链接分发带来的效率革新!点击下方链接,即刻开启鸿蒙生态场景化运营新篇章——点击链接即可体验:App Linking AppGallery Connect致力于为应用的创意、开发、分发、运营、经营各环节提供一 站式服务,构建全场景智慧化的应用生态体验。为给您带来更好服务,请扫描下方二维码或者点击此处免费咨询。如您有任何疑问,请发送邮件至agconnect@huawei.com咨询,感谢您对HUAWEI AppGallery Connect的支持!
  • [技术干货] 操作步骤缩减75%!HarmonyOS近场服务助力轻松交互,精准直达
    线下服务场景中,开发者常面临用户触达率低、推荐不精准、转化效果差等问题。传统推送方式依赖用户主动搜索或广告曝光,无法结合实时位置精准匹配需求,导致服务推荐滞后、用户体验不佳。对此,HarmonyOS为开发者准备了一种智能、低门槛、高转化的近场服务解决方案——POI场景及信标设备场景:基于用户实时位置或通过用户手机定位与信标设备联动,智能识别用户场景,并经由小艺建议等入口推荐服务,从而提升用户触达率和满意度。POI及信标类近场服务的典型特点包括:精准定位:1-200米范围内动态感知用户位置。场景化推荐:结合POI或信标设备,匹配用户当下需求。无缝体验:通过系统级入口(如小艺建议)直接触达用户,常规点选多层级步骤缩减约75%至一步直达操作。一、近场服务典型应用场景 二、实际应用案例场景一(POI):智慧文旅——效率、体验双升级,减少约80%操作步骤      游客靠近景区时,小艺建议获取购票服务卡片推荐,一键直达购票页面,相较常规点评软件的购票方式可减少约80%操作步骤;      进入景区游览时,基于景区内不同景点推荐景点介绍详情页,景点讲解、景点推荐、游览路线推荐等一目了然,再也不用边走边问。场景二(信标设备):商铺引流——坪效提升新引擎      商铺部署信标设备后,用户进入信标连接范围即可收到传输信号,通过小艺建议获取商铺热门产品、优惠活动、折扣套餐等推荐;      相较未部署信标设备的商铺依赖自然流量,信标部署商铺可主动“出击”,提前曝光吸引用户关注眼球,从而提升商铺的客流量,进而潜在提升会员转化及客单成交率。  欢迎广大鸿蒙开发者体验近场服务。如果您是一位鸿蒙开发者,赶快加入体验服务,让我们携手共进,共享鸿蒙发展的无限机遇,点击链接即可体验:POI场景、信标设备场景。AppGallery Connect致力于为应用的创意、开发、分发、运营、经营各环节提供一站式服务,构建全场景智慧化的应用生态体验。为给您带来更好服务,可扫描下方二维码或者点击此处免费咨询。  如您有任何疑问,请发送邮件至agconnect@huawei.com咨询,感谢您对HUAWEI AppGallery Connect的支持! 
  • [技术干货] “来电显示”助力沟通,办公效率提升75%!
         叮叮叮……“喂,您好哪位?”“小张,XXX”领导同事来电被误以为是骚扰电话直接挂断,或无法判断来电人小心翼翼试探,这些职场上的尴尬时刻你遇到过吗?华为AppGallery Connect(简称AGC)推出的企业联系人信息来去电页面显示能力让你彻底告别这些尴尬时刻,来电显示一览无遗,重要电话绝不错过,沟通效率up up!一、什么是企业联系人信息来去电页面显示?       华为企业联系人信息来去电页面显示是基于Call Service Kit(通话服务)提供的面向企业办公场景的能力,来去电时,页面可以显示已安装企业应用的联系人信息,方便用户识别来去电人信息,快速回应,增强企业内部沟通效率。(本功能仅供企业应用开发者接入)       企业来电显示能力从传统的8步可缩减至2步,沟通效率提升75%,让沟通简单又高效!(此数据为伙伴侧提供案例参考,具体以实际应用落地效果为准)二、如何接入企业联系人信息来去电页面显示能力?企业应用开发者将申请信息发送至公共邮箱:agconnect@huawei.com。邮件标题:【申请公司名】—企业来电显示能力—Developer ID。邮件内容需包括:开发者接入企业来电显示能力的应用使用主体、应用名称、应用ID、应用包名、场景说明(具体描述该应用对应通讯录量级等使用的必要信息)。申请成功后,需要重新申请调试Profile,并且在DevEco Studio中替换新申请的调试Profile。三、用户手机如何进行设置?     打开“电话”> 点击右上角“更多”图标 > 前往“设置”> 找到“陌生号码和信息识别” > 打开对应企业应用的号码识别功能开关  页面效果展示 AppGallery Connect致力于为应用的创意、开发、分发、运营、经营各环节提供一 站式服务,构建全场景智慧化的应用生态体验。为给您带来更好服务,请扫描下方二维码或者点击此处免费咨询。如您有任何疑问,请发送邮件至agconnect@huawei.com咨询,感谢您对HUAWEI AppGallery Connect的支持!
  • [干货汇总] 鸿蒙应用架构设计:组件化复用与模块化项目案例
    问题背景针对App产品存在多个客户端版本的情况下,同时开发 多 个 App 时,由于业务目标、用户群体可能存在差异,且需兼顾协同效率与质量稳定性,容易暴露出比单一 App 开发更复杂的问题多产品App核心问题,本质是 “个性需求与共性能力的平衡失控”:资源分散导致效率低,协同缺失导致体验乱,版本混乱导致风险高。无 模块化架构设计,项目陷入 “开发慢、改不动、问题多” 的恶性循环。     安卓开发现状人力资源分配矛盾:若 多 个 App 并行开发,核心开发人员(如架构师、资深工程师)需同时跟进多个项目,精力被稀释,导致技术决策延迟、关键问题响应变慢。基层开发人员若按 “1 个 App 对应 1 个团队” 划分,会出现 “同一项基础功能(如图片上传、异常监控)3 个团队各做一套” 的情况,重复劳动率高达 40%-60%,直接拉长整体开发周期。技术栈与规范难统一若 多个 App 由不同团队开发,可能因 “团队习惯” 采用差异技术方案,导致后续跨 App 协作(如人员轮岗、问题排查)成本陡增即使预先制定规范,也可能因 “赶进度” 出现执行偏差(如命名规则、接口格式不统一),后期需额外投入人力做标准化整改共性能力重复开发,维护难度翻倍:多个 App 必然存在共性能力(如登录、支付、网络请求、数据埋点),若未提前抽象复用,会导致:同一功能出现 多套代码,修复一个共性 Bug(如登录接口超时逻辑)需在 3多个 App 中分别修改,漏改概率增加共性能力升级(如支付渠道新增)需 多个团队同步适配,协调成本随 App 数量呈指数级增长版本规划与测试压力陡增多 个 App 的版本迭代节奏可能不同(如 A App 需每月一更,B App 每两周一更,C App 紧急上线),测试资源(如测试设备、自动化脚本)需在 3 个项目间频繁切换,导致测试覆盖率下降,漏测风险升高。若 多 个 App 依赖同一基础组件(如自研的网络库),该组件升级后,需 多个 App 同步完成兼容性测试才能发布,任何一个 App 的测试延迟都会拖慢整体进度。线上问题连锁反应若共性能力(如埋点 SDK)存在隐藏 Bug,可能导致 多个 App 同时出现数据异常,线上故障排查时需 “多线并行定位”,定位时间比单一 App 问题长 2-3 倍。某一个 App 的紧急发布(如修复崩溃 Bug)可能因 “打包环境共享”“配置文件混淆” 影响其他 App 的发布包稳定性(如误打包旧版本代码)。业务与扩展性:差异需求失控多 个 App 的业务差异(如 A App 需社交功能,B App 需电商功能,C App 需工具功能)可能要求对共性能力做 “定制化修改”(如登录模块为 A App 新增 “第三方社交账号登录”,为 B App 新增 “手机号一键登录”),若修改未抽象成可配置逻辑,会导致共性模块逐渐 “臃肿”,最终失去复用价值 鸿蒙解决方案   整体架构设计思路:          备注:一个业务功能,即为一个工程(整个工程下的一个文件夹),编译出后是一个HAR/HSP类型的包。多个HAR/HSP组合打包出的包为HAP包。(HAR、HSP、HAP包区别参考:https://developer.huawei.com/consumer/cn/doc/architecture-guides/tools-v1_2-ts_35-0000002343405565)        在鸿蒙生态中,通过 ArkTS 语言和 ArkUI 框架的原生支持,可以高效实现 "一套工程、多 App 发布" 的架构。具体实现策略:功能模块包模块化设计:可插拔组件化开发。由组件复用提供基础能力,例如:一键加油、爱车服务、无感支付、在线订单、高德、在线商城等业务功能,每个都由HAR/HSP工程创建,实现业务功能与业务无关的网络库、埋点SDK、图片加载等,每个都由HAR/HSP工程创建,实现基础功能。由业务功能HAR/HSP包调用,为业务功能提供基础能力上述业务功能HAR/HSP包,基础功能HAR/HSP包,可自由组合,被HAP工程引入,由HAP工程打包出用户版、商户版、供应商版三个版本工程架构设计,组件复用,实现一套代码库支撑多 App用户版HAP工程打包:创建hapTasks类型的工程(运行出的包为HAP包),将多个需要的多个业务功能包( HAR/HSP工程(文件夹))引入,编码实现用户版的功能。商户版、供应商版也是如此。用户版打包:需为车主提供便捷的车辆养护、维修、紧急救援等服务,引入一键加油、爱车服务、无感支付HAR/HSP,实现相关业务逻辑后,打包成HAP包商户版打包:需帮助维修店/4S店高效管理客户和服务流程,引入在线订单、高德HAR/HSP,实现相关业务逻辑后,打包成HAP包供应商版打包:为配件供应商提供B2B销售渠道和管理工具,引入在线商城、充值相关渠道配置HAR/HSP,实现相关业务逻辑后,打包成HAP包如何打HAP包(多个app的差异化打包):。上述用户版、商户版、供应商版工程,每个工程需要配置:包名、签名、证书、打包输出的文件夹路径、相关资源(如主题资源、图片资源等)每个 HAP 的 module.json 中,bundleName、bundleType、versionCode、debug、minAPIVersion 保持一致;module 的 name 字段互不相同;minCompatibleVersionCode、targetAPIVersion 保持一致配置后,通过执行Hvigor命令,打包成HAP包(Hvigor脚本参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/packing-tool#多工程打包指令)    解决痛点模块化设计,代码复用率提升:基础组件统一维护,避免重复开发(复用率可达 70%-90%)。功能模块通过配置按需加载,无需为每个 App 单独编写代码,并且可以将可插拔的模块组合打包多个App。组件复用,开发效率提升:修改公共组件自动同步到所有 App,减少重复测试和发布流程。新增功能只需在对应模块开发,通过配置快速集成到需要的 App。维护成本降低:单一工程结构减少代码仓库管理复杂度,团队协作更高效。版本控制更简单,所有 App 基于同一代码基线演进。灵活扩展能力:新增 App 只需创建新的配置文件和专属资源,无需复制代码。功能模块可独立升级,不影响其他 App。      组件设计思路:   组件复用,设计思路:基础层组件:网络库、埋点SDK、图片加载等与业务无关联的,放入基础层,可供任何App提供底层能力业务层:支付模块、订单、商品详情等,与业务强关联,需要考虑多个App版本的不同差异化能力、公共能力,进一步抽出例如可选复用的业务功能,作为多个App集成的公共业务差异化的使用动态Feature包由多个App灵活调用,并且可设计多个Feature包,可插拔给多个App组合使用产品层:根据不同App版本,将公共资源统一管理、特定产品特定资源文件、代码中动态加载资源,封装在不同的App中每个App就固定使用这些资源、动态加载业务层的业务包,灵活配置不用App版本之间所需要的业务功能基础层、业务层功能维护: 每个基础能力、业务模块完全独立开发,无需关心是哪个App来调用,仅需关注本身能力、业务的迭代开发 基础组件案例案例1,验证码组件:  import { inputMethod } from '@kit.IMEKit';import { emitter } from '@kit.BasicServicesKit';import { hilog } from '@kit.PerformanceAnalysisKit';@Extend(Text)function verifyCodeUnitStyle() { .fontSize($r("sys.float.ohos_id_text_size_body1")) .fontWeight(60) .textAlign(TextAlign.Center) .width($r("app.integer.verify_code_code_unit_with")) .height('100%') .margin({ left: $r("app.integer.verify_code_code_unit_margin"), right: $r("app.integer.verify_code_code_unit_margin") }) .border({ width: { bottom: $r("app.integer.verify_code_code_border_width") }, color: { bottom: Color.Grey }, style: { bottom: BorderStyle.Solid } })}@Componentstruct VerifyCodeComponentWithoutCursor { @State codeText: string = ""; private readonly verifyID: string = "verifyCodeComponent"; private inputController: inputMethod.InputMethodController = inputMethod.getController(); // 监听键盘弹出收起状态 @State isKeyboardShow: boolean = false; private verifyCodeLength: number = 6; private isListen: boolean = false; private textConfig: inputMethod.TextConfig = { inputAttribute: { textInputType: inputMethod.TextInputType.NUMBER, enterKeyType: inputMethod.EnterKeyType.GO }, }; private codeIndexArray: Array<number> = Array.from([0, 1, 2, 3, 4, 5]); // 注册路由返回函数,案例插件不触发 popRouter: () => void = () => { }; aboutToAppear(): void { // 注册返回监听,包括点击手机返回键返回与侧滑返回 this.listenBackPress(); } async attachAndListen(): Promise<void> { focusControl.requestFocus(this.verifyID); await this.inputController.attach(true, this.textConfig); logger.info("attached"); this.listen(); this.isKeyboardShow = true; } listenBackPress() { let innerEvent: emitter.InnerEvent = { eventId: 5 }; // 收到eventId为5的事件后执行回调函数 emitter.on(innerEvent, () => { if (this.isKeyboardShow) { // 退出文本编辑状态 this.inputController.hideTextInput(); this.isKeyboardShow = false; } else { this.popRouter(); } }); } aboutToDisappear(): void { this.off(); // 关闭事件监听 emitter.off(5); } /** * TODO 知识点:绑定输入法 */ async attach() { await this.inputController.attach(true, this.textConfig); logger.info("attached"); } /** * TODO:知识点:解绑 */ off(): void { this.inputController.off("insertText"); this.inputController.off("deleteLeft"); this.isListen = false; logger.info("detached"); // 退出文本编辑状态 this.inputController.hideTextInput(); this.isKeyboardShow = false; } /** * TODO 知识点:订阅输入法代插入、向左删除事件,从而获得键盘输入内容 */ listen() { if (this.isListen) { return; } this.inputController.on("insertText", (text: string) => { if (this.codeText.length >= this.verifyCodeLength || isNaN(Number(text)) || text === ' ') { return; } this.codeText += text; if (this.codeText.length === this.verifyCodeLength) { logger.info("VerifyCode: %{public}s", this.codeText); } logger.info("VerifyCode [insert]: %{public}s", this.codeText); }) this.inputController.on("deleteLeft", (length: number) => { this.codeText = this.codeText.substring(0, this.codeText.length - 1); logger.info("VerifyCode [delete left]: %{public}s", this.codeText); }) this.isListen = true; logger.info("listener added"); } /** * TODO 知识点:部分验证码场景要完全禁止对输入验证码的选中、复制等功能,因此可以使用Text组件完成 */ @Builder buildVerifyCodeComponent() { Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { ForEach(this.codeIndexArray, (item: number, index: number) => { Text(this.codeText[item]) .verifyCodeUnitStyle() }, (item: number, index: number) => item.toString()) } .id(this.verifyID) /** * TODO:知识点:当可视面积变化时进行绑定注册与解绑 */ .onBlur(() => { this.off(); }) .backgroundColor(Color.Transparent) .height($r("app.integer.verify_code_verify_code_height")) .margin({ left: $r("sys.float.ohos_id_card_margin_start"), right: $r("sys.float.ohos_id_card_margin_start") }) .defaultFocus(true) .onClick(() => { // TODO 知识点:点击本组件时弹出输入法,因为这里使用的是Text组件,因此需要重新attach,而不能直接使用showSoftKeyboard this.attachAndListen(); }) } build() { Row() { this.buildVerifyCodeComponent() } }}@Builderexport function VerifyCodeViewBuilder() { VerifyCodeView()}/** * 验证码组件:禁用选中、复制、光标 */@Componentexport struct VerifyCodeView { popRouter: () => void = () => { }; build() { NavDestination(){ Column() { VerifyCodeComponentWithoutCursor({ popRouter: this.popRouter }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) } .title('验证码界面') }}/** * 日志打印类 */class Logger { private domain: number; private prefix: string; private format: string = '%{public}s, %{public}s'; constructor(prefix: string) { this.prefix = prefix; this.domain = 0xFF00; this.format.toUpperCase(); } debug(...args: string[]) { hilog.debug(this.domain, this.prefix, this.format, args); } info(...args: string[]) { hilog.info(this.domain, this.prefix, this.format, args); } warn(...args: string[]) { hilog.warn(this.domain, this.prefix, this.format, args); } error(...args: string[]) { hilog.error(this.domain, this.prefix, this.format, args); }}export let logger = new Logger('[CommonAppDevelopment]') 案例2,地址选择器组件: import { window } from '@kit.ArkUI';import { AddressInfo, AddressType, CommonAddressList, Location, Province } from '../model/AddressModel';import { JsonUtils } from '../utils/JsonUtils';/** * 常量 */export default class Constants { // 自定义TabBar切换tab动画分隔线宽度 public static readonly DIVIDER_WIDTH: number = 20; // 顶部省市区间隔 public static readonly AREA_SPACE: number = 12; // rawfile目录下的省市区json文件 public static readonly JSON_FILE: string = 'address';}/** * 自定义地址选择组件CustomAddressPicker */@Componentexport struct CustomAddressPicker { // 底部导航条区域高度 @State bottomHeight: number = 0; // 选择的省市区 @State provinceCityRegion: string = '省、市、区'; // 用于对外提供选择后的省市区信息或者传入地址信息 @Link address: AddressInfo; // 地址选择半模态弹窗显隐标志位 @State isShow: boolean = false; // 当前选择的省、市、区tab页签的index。0表示省,1表示市,2表示区 @State currentIndex: number = AddressType.Province; // 调用changeIndex切换TabContent动画时长 @State animationDuration: number = 300; // 省List @State provinceList: CommonAddressList[] = []; // 市List @State cityList: CommonAddressList[] = []; // 区List @State regionList: CommonAddressList[] = []; // 记录上一次市List @State lastCityList: CommonAddressList[] = []; // 记录上一次区List @State lastRegionList: CommonAddressList[] = []; // 存放选择的省数据 @State province: Province = new Province('', '', []); // 记录当前省市区选择信息 @State currentSelectInfo: AddressInfo = new AddressInfo(); // 记录上一次省市区选择信息 @State lastSelectInfo: AddressInfo = new AddressInfo(); // 选择的省市区名下方的下滑线水平偏移量 @State leftMargin: number = 0; // 存放上一次选择的省市区名下方的下滑线水平偏移量 private lastLeftMargin: number = 0; // 存放选择的省市区名下方的下滑线位置信息 private textInfos: [number, number][] = []; // 存放从json读取的省市区数据 private data: Province[] = []; private controller: TabsController = new TabsController(); async aboutToAppear() { // 获取导航条高度,半模态弹窗内容进行避让 window.getLastWindow(getContext(), (err, data) => { const avoidAreaBottom = data.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) this.bottomHeight = avoidAreaBottom.bottomRect.height }) // 从json文件读取省市区数据 const addressData: Province[] = await JsonUtils.getAddressJson(Constants.JSON_FILE) if (!addressData || addressData.length === 0) { console.error('省市区数据加载失败'); return; } for (let index = 0; index < addressData.length; index++) { this.data.push(addressData[index]) this.provinceList.push(new CommonAddressList(addressData[index].code, addressData[index].name)); } // 首次加载AddressPickerComponent如果传入了有效的地址信息,拉起地址选择半模态页面时,会按传入的地址信息进行显示 this.initAddressSelect() } /** * 首次加载AddressPickerComponent如果传入了有效的地址信息时,在拉起地址选择半模态页面时,会按传入的地址信息进行显示 */ initAddressSelect() { if (this.address.province !== '' && this.address.city !== '' && this.address.region !== '') { this.provinceCityRegion = this.address.province + this.address.city + this.address.region this.currentSelectInfo.province = this.address.province this.currentSelectInfo.city = this.address.city this.currentSelectInfo.region = this.address.region //查找对应的市,区地址信息 this.data.forEach(province => { if (province.name === this.address.province) { this.currentSelectInfo.provinceId = province.code; this.address.provinceId = province.code; province.children.forEach(city => { // 只提取市级的code和name this.cityList.push(new CommonAddressList(city.code, city.name)) if (city.name === this.address.city) { this.currentSelectInfo.cityId = city.code this.address.cityId = city.code city.children.forEach(region => { // 只提取区级的code和name this.regionList.push(new CommonAddressList(region.code, region.name)) if (region.name === this.address.region) { this.currentSelectInfo.regionId = region.code this.address.regionId = region.code // 深拷贝保存到相应的变量中 this.lastSelectInfo = JSON.parse(JSON.stringify(this.currentSelectInfo)) this.lastCityList = JSON.parse(JSON.stringify(this.cityList)); this.lastRegionList = JSON.parse(JSON.stringify(this.regionList)); this.animationDuration = 0; } }) } }) } }) } } /** * 选择的省市区名下方的下滑线动画 * @param duration 动画时长 * @param leftMargin 下划线动画偏移量 */ startAnimateTo(duration: number, leftMargin: number) { animateTo({ duration: duration, // 动画时长 curve: Curve.Linear, // 动画曲线 iterations: 1, // 播放次数 playMode: PlayMode.Normal // 动画模式 }, () => { this.leftMargin = leftMargin; }) } /** * 用于显示选择的省、市、区名 * @param params 传入要显示的省、市、区名 */ @Builder locationItem(params: Location) { Text(params.name === '' ? "请选择" : params.name) .height("100%") .fontSize(16) .fontWeight(this.currentIndex === params.index ? 500 : 400) .fontColor(this.currentIndex === params.index ? "#cc000000" : "#ff8d8d8d") .constraintSize({ maxWidth: "33%" }) .textOverflow({ overflow: TextOverflow.Ellipsis }) .maxLines(1) .margin({ right: 12 }) .onClick(() => { this.controller.changeIndex(params.index) }) .id(params.index.toString()) .onAreaChange((oldValue: Area, newValue: Area) => { //使用组件区域变化回调onAreaChange获取选择的省市区Text组件宽度,存入textInfos数组,用于后续计算选择省市区名后下方下滑线动画水平偏移量leftMargin // 组件区域变化时获取当前Text的宽度newValue.width和x轴相对位置newValue.position.x this.textInfos[params.index] = [newValue.position.x as number, newValue.width as number]; if (this.currentIndex === params.index && params.index === AddressType.Province) { // 计算选择的省市区名下方的下滑线偏移量 this.leftMargin = (this.textInfos[this.currentIndex][1] - 20) / 2 } }) } @Builder customTabs() { Tabs({ controller: this.controller }) { // 省列表 TabContent() { List() { ForEach(this.provinceList, (item: CommonAddressList) => { ListItem() { this.areaNameItem(AddressType.Province, item) } .onClick(() => { // 如果当前点击选择的省与之前选择一样,跳过省、市数据获取,直接调用changeIndex(AddressType.City)切换到市列表,减少冗余查询以提升性能 if (this.currentSelectInfo.province == item.name) { this.controller.changeIndex(AddressType.City) return } else { // 重置市和区数据 this.currentSelectInfo.cityId = ''; this.currentSelectInfo.city = ''; this.currentSelectInfo.regionId = ''; this.currentSelectInfo.region = ''; } this.cityList = [] this.regionList = [] this.data.forEach(province => { if (province.name === item.name) { this.province = JSON.parse(JSON.stringify(province)); province.children.forEach(city => { this.cityList.push(new CommonAddressList(city.code, city.name)); }) } }) this.currentSelectInfo.provinceId = item.code; this.currentSelectInfo.province = item.name; this.controller.changeIndex(AddressType.City) }) }, (item: CommonAddressList) => JSON.stringify(item)) } .width("100%") .height("100%") .scrollBar(BarState.Off) .friction(0.6) // 设置摩擦系数 .edgeEffect(EdgeEffect.Spring) // 边缘效果设置为Spring .listDirection(Axis.Vertical) // 排列方向 } // 市列表 TabContent() { List() { ForEach(this.cityList, (item: CommonAddressList) => { ListItem() { this.areaNameItem(AddressType.Province, item) } .onClick(() => { // 如果点击的市和上一次点击的市一样,则不用刷新,减少冗余操作以提升性能 if (this.currentSelectInfo.city === item.name) { this.controller.changeIndex(AddressType.Region) return } else { //重置数据 this.currentSelectInfo.region = '' this.currentSelectInfo.regionId = '' } this.regionList = [] // 点击市,获取该市所有区,存入regionList this.province.children.forEach(city => { if (city.name === item.name) { city.children.forEach(region => { this.regionList.push(new CommonAddressList(region.code, region.name)) }) } }) this.currentSelectInfo.cityId = item.code this.currentSelectInfo.city = item.name this.controller.changeIndex(AddressType.Region) }) }, (item: CommonAddressList) => JSON.stringify(item)) } .width("100%") .height("100%") .scrollBar(BarState.Off) .friction(0.6) .edgeEffect(EdgeEffect.Spring) .listDirection(Axis.Vertical) } // 区列表 TabContent() { List() { ForEach(this.regionList, (item: CommonAddressList) => { ListItem() { this.areaNameItem(AddressType.Province, item) } .onClick(() => { // 记录选择的区信息 this.currentSelectInfo.regionId = item.code; this.currentSelectInfo.region = item.name; this.provinceCityRegion = this.currentSelectInfo.province + this.currentSelectInfo.city + this.currentSelectInfo.region //退出半模态 this.isShow = false // 将当前选中省市区信息保存到lastSelectInfo this.lastSelectInfo.provinceId = this.currentSelectInfo.provinceId; this.lastSelectInfo.province = this.currentSelectInfo.province; this.lastSelectInfo.cityId = this.currentSelectInfo.cityId; this.lastSelectInfo.city = this.currentSelectInfo.city; this.lastSelectInfo.regionId = this.currentSelectInfo.regionId; this.lastSelectInfo.region = this.currentSelectInfo.region; // 在选择完区名后,使用JSON.parse(JSON.stringify(xxx))深拷贝选择的省市区数据,用于后续操作中需要加载上一次选择的完整省市区数据 // 深拷贝保存到相应的变量中 this.lastCityList = JSON.parse(JSON.stringify(this.cityList)); this.lastRegionList = JSON.parse(JSON.stringify(this.regionList)); this.address = JSON.parse(JSON.stringify(this.lastSelectInfo)); }) }, (item: CommonAddressList) => JSON.stringify(item)) } .width("100%") .height("100%") .scrollBar(BarState.Off) .friction(0.6) .edgeEffect(EdgeEffect.Spring) .listDirection(Axis.Vertical) } } .onAppear(() => { if (this.lastSelectInfo.region !== '') { // 上一次选择如果选择到区,再次打开半模态弹窗页面时会显示到区的TabContent this.currentIndex = AddressType.Region; if (this.cityList.length === 0 && this.regionList.length === 0) { // 在已经选择过省市区后,再次打开地址选择半模态弹窗页面,但是没有选择到区就关闭了半模态页面,此时如果再次打开半模态页面,需要显示之前完整选择的省区市数据 this.currentSelectInfo.provinceId = this.lastSelectInfo.provinceId; this.currentSelectInfo.cityId = this.lastSelectInfo.cityId; this.currentSelectInfo.regionId = this.lastSelectInfo.regionId; this.currentSelectInfo.province = this.lastSelectInfo.province; this.currentSelectInfo.city = this.lastSelectInfo.city; this.currentSelectInfo.region = this.lastSelectInfo.region; this.cityList = JSON.parse(JSON.stringify(this.lastCityList)); this.regionList = JSON.parse(JSON.stringify(this.lastRegionList)); this.leftMargin = this.lastLeftMargin; } else { this.leftMargin = this.textInfos[0][1] + this.textInfos[1][1] + (this.textInfos[2][1] - 20) / 2 + 12 * 2 this.lastLeftMargin = this.leftMargin; } this.controller.changeIndex(AddressType.Region) } this.animationDuration = 300 }) .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => { if (index === targetIndex) { return; } this.currentIndex = targetIndex; let leftMargin: number = 0; let isAnimating: boolean = false; if (index === AddressType.Province && targetIndex === AddressType.City) { // 从省切到市时,重新计算选择的省市区名下方的下滑线偏移量 leftMargin = this.textInfos[0][1] + (this.textInfos[1][1] - Constants.DIVIDER_WIDTH) / 2 + Constants.AREA_SPACE; isAnimating = this.currentSelectInfo.city === '' ? false : true; } else if (index === AddressType.City && targetIndex === AddressType.Region) { // 从市切到区,重新计算选择的省市区名下方的下滑线偏移量 leftMargin = this.textInfos[0][1] + this.textInfos[1][1] + (this.textInfos[2][1] - Constants.DIVIDER_WIDTH) / 2 + Constants.AREA_SPACE * 2; isAnimating = this.currentSelectInfo.region === '' ? false : true; } else if (index === AddressType.City && targetIndex === AddressType.Province) { // 从市切到省,重新计算选择的省市区名下方的下滑线偏移量 leftMargin = (this.textInfos[0][1] - Constants.DIVIDER_WIDTH) / 2; isAnimating = this.currentSelectInfo.city === '' ? false : true; } else if (index === AddressType.Region && targetIndex === AddressType.City) { // 从区切到市,重新计算选择的省市区名下方的下滑线偏移量 leftMargin = this.textInfos[0][1] + (this.textInfos[1][1] - Constants.DIVIDER_WIDTH) / 2 + Constants.AREA_SPACE; isAnimating = this.currentSelectInfo.region === '' ? false : true; } else if (index === AddressType.Region && targetIndex === AddressType.Province) { // 点击自定义TabBar从区切到省,重新计算选择的省市区名下方的下滑线偏移量 leftMargin = (this.textInfos[0][1] - Constants.DIVIDER_WIDTH) / 2; isAnimating = this.currentSelectInfo.region === '' ? false : true; } else if (index === AddressType.Province && targetIndex === AddressType.Region) { // 点击自定义TabBar从省切到区,重新计算选择的省市区名下方的下滑线偏移量 leftMargin = this.textInfos[0][1] + this.textInfos[1][1] + (this.textInfos[2][1] - Constants.DIVIDER_WIDTH) / 2 + Constants.AREA_SPACE * 2; isAnimating = this.currentSelectInfo.region === '' ? false : true; } // 只有在已经选择过的TabContent之间切换时,才会做下划线水平偏移动画 if (isAnimating) { this.startAnimateTo(this.animationDuration, leftMargin); } else { this.leftMargin = leftMargin; } }) .width("100%") .barHeight(0) .layoutWeight(1) } /** * 自定义省/市/区名项 * @param addressType 省/市/区类型 * @param item 省、市、区地址项 */ @Builder areaNameItem(addressType: AddressType, item: CommonAddressList) { Column() { Text(item.name) .width("90%") .height(48) .fontSize(16) .fontColor(this.getFontColor(addressType, item)) Divider().width("90%") .strokeWidth(1) .color("#F1F3F5") } .width("100%") } /** * 获取省、市、区名需要显示的字体颜色 * @param addressType 省/市/区类型 * @param item 省、市、区地址项 * @returns 需要显示的字体颜色 */ getFontColor(addressType: AddressType, item: CommonAddressList): Color | string | Resource { // 省/市/区名字体颜色 let isSelect: boolean = false; if (addressType === AddressType.Province) { isSelect = this.currentSelectInfo.province !== '' && item.name === this.currentSelectInfo.province; } else if (addressType === AddressType.City) { isSelect = this.currentSelectInfo.city !== '' && item.name === this.currentSelectInfo.city; } else if (addressType === AddressType.Region) { isSelect = this.currentSelectInfo.region !== '' && item.name === this.currentSelectInfo.region; } const color = isSelect ? "#fffcb850" : Color.Black; return color; } /** * 地址选择半模态弹窗页面 */ @Builder addressSelectPage() { Column() { this.customTabBar() Divider().width("90%") .strokeWidth(1) .color("#F1F3F5") this.customTabs() } .width("100%") .height("100%") .backgroundColor(Color.White) .padding({ bottom: this.bottomHeight + 'px' }) } /** * 自定义TabBar */ @Builder customTabBar() { RelativeContainer() { Row() { //选择的省名 this.locationItem({ index: AddressType.Province, name: this.currentSelectInfo.province }) // 选择的市名 this.locationItem({ index: AddressType.City, name: this.currentSelectInfo.city }) // 选择的区名 this.locationItem({ index: AddressType.Region, name: this.currentSelectInfo.region }) } .width("85%") .height("80%") .alignRules({ center: { anchor: '__container__', align: VerticalAlign.Center } }) .margin({ bottom: 10 }) .padding({ left: 20, top: 15 }); // 选择的省市区名下方的下滑线 Row() { Divider() .width(20) .strokeWidth(2) .color("#fffcb850") .margin({ left: this.leftMargin }) } .alignItems(VerticalAlign.Top) .width("85%") .height("20%") .alignRules({ bottom: { anchor: '__container__', align: VerticalAlign.Bottom } }) .padding({ left: 20 }) Row() { Image($r("app.media.address_picker_close")) .objectFit(ImageFit.Contain) .width(14) .height(14) .margin({ left: 20 }); } .height("100%") .width("15%") .alignRules({ right: { anchor: '__container__', align: HorizontalAlign.End } }) .onClick(() => { //关闭半模态 this.isShow = false; }); } .width("100%") .height(48) } build() { Column() { Row() { Text("所在地区") .fontSize(16) .fontWeight(500) .margin({ right: 20 }) Text(this.provinceCityRegion) .fontSize(15) .fontColor(this.provinceCityRegion === '省、市、区' ? "#ffacacac" : Color.Black) .fontWeight(300) .constraintSize({ maxWidth: "68%" }) .textOverflow({ overflow: TextOverflow.Ellipsis }) .maxLines(1) } .width("100%") .height(100) .onClick(() => { this.isShow = true this.currentIndex = AddressType.Province }) .bindSheet($$this.isShow, this.addressSelectPage(), { height: "70%", showClose: false, // 设置不显示自带的关闭图标 dragBar: false, onDisappear: () => { this.animationDuration = 0; if (this.currentSelectInfo.region === '') { // 重置所有状态 this.currentSelectInfo.provinceId = ''; this.currentSelectInfo.cityId = ''; this.currentSelectInfo.regionId = ''; this.currentSelectInfo.province = ''; this.currentSelectInfo.city = ''; this.currentSelectInfo.region = ''; this.cityList = []; this.regionList = []; } } }) } .width("100%") .height(54) .padding(2) }}核心能力:组件复用   总结鸿蒙一多开发统一工程与模块化架构,解决人力资源分配矛盾与重复劳动问题。依托标准化组件复用,确保共性能力集中维护,解决功能重复开发、维护难的问题。组件化与配置化打包让测试聚焦差异点,缓解版本规划压力与测试资源冲突。统一资源管理与编译脚本精准控制打包,降低线上问题连锁反应概率,保障多 App 发布稳定性。
  • [干货汇总] 鸿蒙应用开发与华为地图之经纬度精度偏差-优化方案
    问题背景在鸿蒙App开发中,调用鸿蒙定位服务API获取的当前定位坐标后,传入华为地图后,在华为地图上显示的定位坐标,与实际预期的定位位置不一样例如:鸿蒙定位服务API获取的当前定位坐标,预期在华为地图上应该显示在湖附近,但是实际华为地图上显示的位置,在几百米外的陆地上。具体效果见下面截图即,应用内通过鸿蒙定位服务API获取的当前定位坐标,与华为地图中显示的坐标位置存在偏差  问题原因 鸿蒙定位服务API使用的是WGS84坐标系,但是在显示到华为地图上需要使用GCJ02 坐标系华为地图坐标系介绍:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/map-introduction 鸿蒙定位服务API坐标系介绍:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/location-guidelines 问题原因总结华为官方设计上存在不一致:华为地图需要使用标准的大陆的GCJ02 坐标系,与鸿蒙定位服务API的WGS84坐标系,设计上不一致 修复方案:核心答案封装一套坐标系转换的方法,将WGS84坐标系的坐标转换为GCJ02坐标系的坐标实现步骤鸿蒙原生通过鸿蒙定位服务API获取到坐标后,调用封装的经纬度坐标系转换方法,将转换后的坐标,传入到华为地图中显示经纬度坐标转换方法,见如下代码设计思路        经纬度坐标转换,代码设计思路 先定义一个接受经度、纬度两个参数的方法,并返回number数组,如下:gcj02ToWgs84(lng: number, lat: number)判断是否为国内坐标,若是则继续转化,否则退出封装一个转换经度的方法,如下:transformLng封装一个转换纬度的方法,如下:transformLat再经过固定算法,在gcj02ToWgs84返回number数组      完整代码getAddressPermission() { //位置权限 let atManager = abilityAccessCtrl.createAtManager(); console.log('requestPermissionsFromUser' + 1) try { atManager.requestPermissionsFromUser(getContext(), ['ohos.permission.INTERNET', 'ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION']) .then((data) => { console.log('requestPermissionsFromUser' + JSON.stringify(data)) try { geoLocationManager.getCurrentLocation(request) .then((result) => { // 调用getCurrentLocation获取当前设备位置,通过promise接收上报的位置 console.info('current location: ' + JSON.stringify(result)); // 通过wgs84ToGcj02转换为gcj02坐标 const lngLat = wgs84ToGcj02(result.longitude, result.latitude) setTimeout(() => { this.setMark(result.longitude, result.latitude, "位置(wgs84,位置偏移)", $r("app.media.position")) this.setMark(lngLat[0], lngLat[1], "位置(gcj02,位置准确)", $r("app.media.position")) }, 1000) }) .catch((error: BusinessError) => { // 接收上报的错误码 console.error('promise, getCurrentLocation: error=' + JSON.stringify(error)); }); } catch (err) { console.error("errCode:" + JSON.stringify(err)); } }) .catch((err: BusinessError) => { console.log('requestPermissionsFromUser' + 3) // Logger.error(TAG, `err: ${JSON.stringify(err)}`); }) } catch (err) { console.log('requestPermissionsFromUser' + 4) } } const PI = Math.PI;const a = 6378245.0;const ee = 0.00669342162296594323;function outOfChina(lng: number, lat: number): boolean {  if (lng < 72.004 || lng > 137.8347) {    return true;  }  if (lat < 0.8293 || lat > 55.8271) {    return true;  }  return false;}function transformLat(lng: number, lat: number): number {  let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));  ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;  ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;  ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;  return ret;}function transformLng(lng: number, lat: number): number {  let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));  ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;  ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;  ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;  return ret;}function gcj02ToWgs84(lng: number, lat: number): number[] {  if (outOfChina(lng, lat)) {    return [lng, lat];  }  let dlat = transformLat(lng - 105.0, lat - 35.0);  let dlng = transformLng(lng - 105.0, lat - 35.0);  let radlat = lat / 180.0 * PI;  let magic = Math.sin(radlat);  magic = 1 - ee * magic * magic;  let sqrtmagic = Math.sqrt(magic);  dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);  dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);  let mglat = lat + dlat;  let mglng = lng + dlng;  return [lng * 2 - mglng, lat * 2 - mglat];}总结鸿蒙地图相关开发中,若存在应用内app获取定位后,需要在华为地图中显示定位坐标位置,则需要转换坐标。开发者需了解鸿蒙中此种经纬度坐标系,不同标准。若遇到类似问题,可快速解决,无需查阅很多资料花费较多时间来定位此种类型的问题若遇到类似定位相关的问题,查阅鸿蒙官网API时,需留意坐标系相关的说明,可快速定界出是否是坐标系的问题
  • [技术干货] 6月13日《HCCDA-HarmonyOS & Cloud Apps认证考试辅导》直播热门问题解答
    本次直播专为备考华为云鸿蒙应用入门级开发者认证的学员设计,提供系统的考试辅导。深度解析认证核心知识点,涵盖HarmonyOS介绍、应用开发入门、ArkTS语言、声明式开发范式组件、Stage应用模型、玩转服务卡片、鸿蒙应用网络请求开发、鸿蒙应用云函数调用等内容。华为云鸿蒙应用入门级开发者认证分为理论考试和实验考试两部分,60分为通过。理论考试时间为60分钟,试题分为判断:单选:多选,比例为3:4:3(考试题量50);实验考试共120分钟,声明式开发范式实验、Stage模型实验、ArkTS卡片实验、HarmonyOS调用FunctionGraph实验为重点考点。直播链接:cid:link_0; Q:harmonyos三大技术特性?A: ①硬件互助与资源共享②一次开发多端部署③统一OS弹性部署 Q:HAP分为以下哪几项类型?A:Entry、Feature Q:在HarmonyOS中,当用户从桌面点击一个应用图标启动应用时,UIAbility组件会经历以下哪个生命周期状态的变化?A:从Create到Foreground。 Q:在HarmonyOS的ArkTS卡片开发中,什么情况会触发卡片生命周期回调函数的调用?A:①卡片被添加到桌面②卡片被从桌面移除③卡片中的数据更新 Q:DevEco Studio支持预览器工具开启双向预览功能后,工具可以支持哪几个之间相互联动?A: 代码编辑器、Component Tree、UI界面。 Q:使用浏览器输出网址进行查询搜索,实际上相当于发送了以下哪一种HTTP请求方式?A:GET请求。