鸿蒙掌上驾考宝典应用开发39:隐私协议与合规——隐私协议页面实现 第39篇隐私协议与合规——隐私协议页面实现一、引言隐私协议Privacy Policy是应用合规的重要组成部分它向用户明确说明应用收集哪些数据、如何使用这些数据、如何保护用户隐私。在鸿蒙生态中隐私合规更是应用上架的必要条件。DriverLicenseExam 项目实现了完整的隐私协议功能包括协议内容管理、协议页面展示、用户授权管理。本文将深入解析其实现。二、隐私协议合规要求2.1 鸿蒙应用的合规要求根据鸿蒙应用市场的上架要求应用需要满足以下隐私合规要求隐私协议明确清晰说明数据收集和使用规则用户同意机制在收集数据前获得用户明确同意协议可查阅用户随时可以查看隐私协议撤回同意用户有权撤回对隐私协议的同意2.2 项目中的隐私合规设计项目在以下环节实现了隐私合规应用启动 → 首次使用 → 隐私协议展示 → 用户同意 → 正常使用↓ 不同意 → 退出应用用户使用中 → 设置页面 → 隐私协议查阅 → 随时可查看三、隐私协议数据管理3.1 rawfile 数据存储隐私协议的 URL 等信息存储在AppScope/resources/rawfile/data.json中//AppScope/resources/rawfile/data.json {privacy_url:https://developer.huawei.com/consumer/cn/privacy/,user_agreement:https://developer.huawei.com/consumer/cn/agreement/}3.2 读取协议 URL// 从 rawfile 读取协议地址getProtocolUrl(privacyUrl: string): string {try{constcontext this.getUIContext().getHostContext()asContext;// 获取原始文件内容constvalue: Uint8Array context.resourceManager.getRawFileContentSync(data.json);// 转换为字符串并解析 JSONconstjsonStr buffer.from(value.buffer).toString();constconfig JSON.parse(jsonStr);returnconfig[privacyUrl]asstring; }catch(error) { hilog.error(this.domainId,this.logTag,getProtocolUrl Error: error.message);return; } }这个方法的执行流程如下获取 Context通过getUIContext().getHostContext()获取上下文读取 rawfilegetRawFileContentSync同步读取原始文件类型转换Uint8Array→buffer→stringJSON 解析解析为对象获取指定字段3.3 多语言支持协议地址可以支持多语言通过系统语言环境自动切换// 获取系统语言环境constsystemLanguage: string (this.getUIContext().getHostContext()ascommon.UIExtensionContext) .config.language ??;// 拼接语言参数constprotocolUrl this.getProtocolUrl(privacy_url) systemLanguage;四、隐私协议页面4.1 PrivacyAgreement 组件// PrivacyAgreement.etsComponentV2exportstructPrivacyAgreement{LocalprotocolUrl:string;privatewebController:WebControllernewWebController();aboutToAppear() {// 获取传递的协议 URLconstparams this.vm.navStack.getParamByIndex(this.vm.navStack.size() -1)asRecordstring,Object;if(params params.protocolUrl) {this.protocolUrl params.protocolUrlasstring; }else{// 默认获取隐私协议this.protocolUrlthis.getProtocolUrl(privacy_url); } }build() {NavDestination() {Column() {// Web 组件展示协议内容if(this.protocolUrl) {Web({src:this.protocolUrl,controller:this.webController}) .width(100%) .height(100%) .onPageBegin((event) {Logger.info(PrivacyAgreement,Page load started); }) .onPageEnd((event) {Logger.info(PrivacyAgreement,Page load completed); }) .onErrorReceive((event) {Logger.error(PrivacyAgreement,Page load error: event.error); }); }else{// 加载失败提示Column() {Image($r(app.media.ic_error)).width(48).height(48);Text(协议加载失败请检查网络连接) .fontSize(14) .fontColor(#999) .margin({top:16}); } .width(100%) .height(100%) .justifyContent(FlexAlign.Center); } } .width(100%) .height(100%); } .title(隐私协议); } }4.2 Web 组件的使用ArkUI 的Web组件用于加载网页内容支持以下特性Web({src:this.protocolUrl,controller:this.webController}).width(100%) .height(100%)// 页面加载事件.onPageBegin((event) {}) .onPageEnd((event) {}) .onErrorReceive((event) {})// 加载进度.onProgressChange((progress) {})// 是否支持 JavaScript.javaScriptAccess(true);五、协议勾选与授权5.1 登录页面的协议勾选在华为账号一键登录页面用户需要勾选同意隐私协议才能登录// QuickLoginPage.ets 中的协议勾选Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Start, }) {// 复选框Row() { Checkbox({ name:privacyCheckbox, group:privacyCheckboxGroup}) .width(24).height(24) .selectedColor($r(sys.color.comp_background_emphasize)) .select(this.isSelected) .enabled(this.enableStatus) .onChange((value: boolean) {if(value) {this.isSelected true;this.showPopUp false;// 设置协议状态为已同意this.controller.setAgreementStatus( loginComponentManager.AgreementStatus.ACCEPTED); }else{this.isSelected false;// 设置协议状态为未同意this.controller.setAgreementStatus( loginComponentManager.AgreementStatus.NOT_ACCEPTED); } }); } .padding({ right:12}) .bindPopup(this.showPopUp, { message:请阅读并同意相关协议, onStateChange: (event) {this.showPopUp event.isVisible; }, mask:false, });// 协议文本Row() { Text() { ForEach(this.privacyText, (item: loginComponentManager.PrivacyText) {if(item?.type loginComponentManager.TextType.PLAIN_TEXT) { Span(item?.text) .fontColor($r(sys.color.ohos_id_color_text_secondary)); }elseif(item?.type loginComponentManager.TextType.RICH_TEXT) { Span(item?.text) .fontColor($r(sys.color.ohos_id_color_text_primary)) .onClick(() {this.jumpToPrivacyWebView(item); }); } }); } .maxFontScale(1); } }5.2 协议文本配置协议文本通过PrivacyText数组配置支持富文本点击privacyText: loginComponentManager.PrivacyText[][{ text: $r(app.string.read_and_agree),type: loginComponentManager.TextType.PLAIN_TEXT, }, { text: $r(app.string.app_user_agreement),type: loginComponentManager.TextType.RICH_TEXT,tag:,// 用户协议标签}, { text: $r(app.string.app_privacy_policy),type: loginComponentManager.TextType.RICH_TEXT,tag:privacy_policy,// 隐私协议标签}, { text: $r(app.string.and),type: loginComponentManager.TextType.PLAIN_TEXT, }, { text: $r(app.string.huaweiId_user_authentication_protocol),type: loginComponentManager.TextType.RICH_TEXT,tag: this.getProtocolUrl(PRIVACY_URL),// 华为账号认证协议}, { text: $r(app.string.end),type: loginComponentManager.TextType.PLAIN_TEXT, },];5.3 协议跳转// 点击协议文本时跳转到对应页面jumpToPrivacyWebView(privacyText: loginComponentManager.PrivacyText) {if(!this.enableStatus)return;if(privacyText.tag privacy_policy) {// 跳转到隐私协议本地页面this.vm.navStack.pushPathByName(PrivacyAgreement, undefined);return; }// 检查网络连接consthasNet: boolean connection.hasDefaultNetSync();if(!hasNet) {consthint 网络连接不可用请检查网络设置; showToast(hint,this.getUIContext());return; }// 跳转到 Web 协议页面if(privacyText.tag privacyText.tag !) {constsystemLanguage (this.getUIContext().getHostContext()ascommon.UIExtensionContext) .config.language ??;constparams: Recordstring, Object {protocolUrl: privacyText.tag systemLanguage };this.vm.navStack.pushPathByName(PrivacyAgreement, params); } }六、合规性设计要点6.1 用户同意记录在用户同意隐私协议后应该记录同意状态避免每次启动都要求用户同意// 保存用户同意状态saveAgreementStatus(){ const preferencesUtil PreferencesUtil.getInstance(); const preference preferencesUtil.getPreferences(context); preferencesUtil.preferencesPut(preference, agreementAccepted,true); preferencesUtil.preferencesPut(preference, agreementDate, Date.now().toString()); }// 检查用户是否已同意isAgreementAccepted(): boolean { const preferencesUtil PreferencesUtil.getInstance(); const preference preferencesUtil.getPreferences(context); return preferencesUtil.getPreferencesValue(preference, agreementAccepted)true; }6.2 隐私协议更新通知当隐私协议更新时需要重新获得用户同意// 协议版本管理checkAgreementUpdate(){ const currentVersion 1.0;// 当前协议版本const preferencesUtil PreferencesUtil.getInstance(); const preference preferencesUtil.getPreferences(context); const acceptedVersion preferencesUtil.getPreferencesValue(preference, agreementVersion);if(acceptedVersion ! currentVersion) {// 协议已更新需要重新同意showPrivacyAgreementUpdate(); } }七、合规检查清单在应用上架前建议对照以下清单进行合规检查隐私协议内容完整覆盖数据收集、使用、共享、保护等条款隐私协议在应用首次启动时展示用户在使用敏感功能前必须同意隐私协议用户拒绝隐私协议时应用退出或限制功能用户随时可以查看隐私协议隐私协议更新后需要重新获得用户同意用户同意状态已持久化存储八、总结隐私协议与合规是应用中不可忽视的重要环节。DriverLicenseExam 项目通过以下方式实现了完整的隐私合规流程协议数据管理通过 rawfile 存储协议 URL支持多语言协议页面展示使用 Web 组件加载协议内容用户授权管理通过 Checkbox 和 AgreementStatus 控制授权状态协议跳转支持点击协议文本跳转到对应页面合规持久化记录用户同意状态协议更新时重新获取同意关键源码文件products/entry/src/main/ets/pages/mine/PrivacyAgreement.ets— 隐私协议页面products/entry/src/main/ets/pages/mine/QuickLoginPage.ets— 登录页协议勾选products/entry/src/main/ets/pages/mine/Setting.ets— 设置页协议入口AppScope/resources/rawfile/data.json— 协议 URL 配置commons/commonLib/src/main/ets/utils/PreferencesUtil.ets— 偏好持久化