UNIAPP 华为应用商店 未同步告知权限申请的使用目的

国内的应用商店上架几乎每家都有每家的需求,在提交华为应用商店之后审核之后,不出意外的被驳回了。需要整改的内容非常多,但是多数都是隐私政策以及相关的权限申请的合规性。

信息安全合规,这本是件好事,但是现在这个要求却是一家一个样,并且都有自己的审核标准。驳回之后给了一堆整改项:

状态:
待修改
审核意见:
应用审核意见:

1.您的应用因缺少个人开发者的资质证明文件未通过审核。

修改建议:个人开发者需补充提交《个人开发者承诺函》。

资质审核要求请参考:https://developer.huawei.com/consumer/cn/doc/distribution/app/80301

资质常见问题请参考:

https://developer.huawei.com/consumer/cn/doc/50111

2.您应用内的隐私政策/在AppGallery Connect上提交的隐私政策未向用户突出明示处理的敏感信息,不符合华为应用市场审核标准。

修改建议:请在隐私政策中以字体加粗、增大字号、醒目颜色等方式突出展示APP处理的个人敏感信息。

请参考《审核指南》第7.2相关审核要求:https://developer.huawei.com/consumer/cn/doc/app/50104-07#h3-1683701612940-0

常见个人敏感信息包括但不限于如身份证号码、面部识别特征、银行账号、网页浏览信息、不满十四周岁未成年人的个人信息等,详情可参考《GB/T 35273-2020 信息安全技术 个人信息安全规范》附录B:http://c.gb688.cn/bzgk/gb/showGb?type=online&hcno=4568F276E0F8346EB0FBA097AA0CE05E

APP常见个人信息保护问题FAQ请参考:

https://developer.huawei.com/consumer/cn/doc/app/FAQ-faq-01#h1-1698326268221-0

3.您应用的隐私政策未以明示同意的方式征得用户同意,不符合华为应用市场审核标准。

测试步骤:未勾选同意隐私政策,可使用微博登录应用。

修改建议:请确保应用内的隐私政策有提供空白复选框且由用户自愿、明确勾选/弹窗有拒绝选项。

请参考《审核指南》第7.5相关审核要求:https://developer.huawei.com/consumer/cn/doc/app/50104-07#h3-1683701612940-1

APP常见个人信息保护问题FAQ请参考: https://developer.huawei.com/consumer/cn/doc/app/FAQ-faq-01#h3-1683538186544-7

4.您的应用在运行时,未同步告知权限申请的使用目的,向用户索取(存储)等权限,不符合华为应用市场审核标准。

测试步骤:个人信息-修改头像,申请存储权限。

修改建议:APP在申请敏感权限时,应同步说明权限申请的使用目的,包括但不限于申请权限的名称、服务的具体功能、用途;告知方式不限于弹窗、蒙层、浮窗、或者自定义操作系统权限弹框等。请排查应用内所有权限申请行为,确保均符合要求。

请参考《审核指南》第7.21相关审核要求:https://developer.huawei.com/consumer/cn/doc/app/50104-07#h3-1683701612940-2

APP常见个人信息保护问题FAQ请参考:https://developer.huawei.com/consumer/cn/doc/app/FAQ-faq-05#h1-1698326401789-0

5.您的应用内提示有更新版本,不符合华为应用市场审核标准。

修改建议:请参考测试结果进行修改,确保提交的应用为最新版本,不得存在自更新行为。

请参考《审核指南》第3.10相关审核要求:https://developer.huawei.com/consumer/cn/doc/app/50104-03

6.经检测发现,您的应用中集成了com.sina.weibo(微博;新浪)等SDK,但未在应用内的隐私政策/在AppGallery Connect上提交的隐私政策内容中进行明示,不符合华为应用市场审核标准。

修改建议:请确保应用内包含的所有SDK均已在应用内的隐私政策/在AppGallery Connect上提交的隐私政策内逐一罗列明示,并说明SDK收集使用的个人信息以及使用目的。请排查应用内包含的所有SDK,并在隐私政策内进行规范化的说明,以保证隐私检测准确性。

请参考《审核指南》第7.2相关审核要求:https://developer.huawei.com/consumer/cn/doc/app/50104-07#h3-1683701612940-0

APP常见个人信息保护问题FAQ请参考:

https://developer.huawei.com/consumer/cn/doc/app/FAQ-faq-01#h3-1683538186544-2

测试环境:Wi-Fi联网、EMUI11.0(Mate 40Pro)、中文环境;

整改的内容比较多,但是也给了相应的解决方案和要求,并且是实际的政策以及要求页面给出了示例。文本的内容好改,功能性的东西反而是最麻烦的。

主要是针对下面这条:

您的应用在运行时,未同步告知权限申请的使用目的,向用户索取(存储)等权限,不符合华为应用市场审核标准。 测试步骤:个人信息-修改头像,申请存储权限。 修改建议:APP在申请敏感权限时,应同步说明权限申请的使用目的,包括但不限于申请权限的名称、服务的具体功能、用途;告知方式不限于弹窗、蒙层、浮窗、或者自定义操作系统权限弹框等。请排查应用内所有权限申请行为,确保均符合要求。

官方给出了相关的样例:

除了按需申请,还需要同步告知权限申请的目的,这个的确遥遥领先。

搜索了一下,找到下面的一个解决方案:https://blog.csdn.net/m0_59203969/article/details/134146475

不过是基于 vuex 实现的

1.如何在 uniapp 中使用 vuex:https://blog.csdn.net/qq_51741194/article/details/124559734

创建 store 目录,在目录下创建 index.js

//  页面路径:store/index.js
    import Vue from 'vue'
    import Vuex from 'vuex'

    import permitions from '@/store/modules/permitions.js'
    // import moduleB from '@/store/modules/moduleB'

    Vue.use(Vuex)
    export default new Vuex.Store({
        modules:{
            permitions
            // moduleA,moduleB
        }
    })

在 store 目录下创建 modules目录,创建permitions.js,文件内容:

// 针对华为应用市场权限提示要求 相关代码实现
// https://blog.csdn.net/m0_59203969/article/details/134146475
// https://www.python100.com/html/84811.html
// https://blog.csdn.net/qq_51741194/article/details/124559734

const state = {
    WRITE_EXTERNAL_STORAGE: false,
    READ_EXTERNAL_STORAGE: false,
    CALL_PHONE: false,
    /* #ifdef APP-PLUS */
    isIos: plus.os.name == "iOS",
    /* #endif */
    mapping: {
        'WRITE_EXTERNAL_STORAGE': {
            title: "存储空间/照片权限说明",
            content: "便于您使用该功能上传您的照片/图片/视频及用于更换头像、发布评论/分享、下载、与客服沟通等场景中读取和写入相册和文件内容。",
            methods: 'SET_WRITE_EXTERNAL_STORAGE'
        },
        'READ_EXTERNAL_STORAGE': {
            title: "存储空间/照片权限说明",
            content: "便于您使用该功能上传您的照片/图片用于更换您的用户头像。",
            methods: 'SET_READ_EXTERNAL_STORAGE'
        },
        'CALL_PHONE': {
            title: "拨打/管理电话权限说明",
            content: "便于您使用该功能联系商家或者商家与您联系等场景",
            methods: 'SET_CALL_PHONE'
        }
    }
}
const mutations = {
    SET_WRITE_EXTERNAL_STORAGE(state, val) {
        state.WRITE_EXTERNAL_STORAGE = val
    },
    SET_CALL_PHONE(state, val) {
        state.CALL_PHONE = val
    },
    SET_READ_EXTERNAL_STORAGE(state, val) {
        state.READ_EXTERNAL_STORAGE = val
    }
}
const actions = {
    //权限获取
    async requestPermissions({
        state,
        dispatch,
        commit
    }, permissionID) {
        try {
            if (!state[permissionID] && !state.isIos) {
                var viewObj = await dispatch('nativeObjView', permissionID);
                viewObj.show();
            }
            console.log('android.permission.' + permissionID, '当前手机权限');
            return new Promise(async (resolve, reject) => {
                 //苹果不需要这个
                if(state.isIos){
                    resolve(1);
                    return
                }
                // Android权限查询
                function requestAndroidPermission(permissionID_) {
                    return new Promise((resolve, reject) => {
                        plus.android.requestPermissions(
                            [
                                permissionID_
                            ], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
                            function (resultObj) {
                                var result = 0;
                                for (var i = 0; i < resultObj.granted.length; i++) {
                                    var grantedPermission = resultObj.granted[i];
                                    console.log('已获取的权限:' + grantedPermission);
                                    result = 1
                                }
                                for (var i = 0; i < resultObj.deniedPresent
                                    .length; i++) {
                                    var deniedPresentPermission = resultObj
                                        .deniedPresent[
                                        i];
                                    console.log('拒绝本次申请的权限:' + deniedPresentPermission);
                                    result = 0
                                }
                                for (var i = 0; i < resultObj.deniedAlways
                                    .length; i++) {
                                    var deniedAlwaysPermission = resultObj.deniedAlways[
                                        i];
                                    console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
                                    result = -1
                                }
                                resolve(result);
                            },
                            function (error) {
                                console.log('申请权限错误:' + error.code + " = " + error
                                    .message);
                                resolve({
                                    code: error.code,
                                    message: error.message
                                });
                            }
                        );
                    });
                }
 
                const result = await requestAndroidPermission(
                    'android.permission.' + permissionID
                );
                if (result === 1) {
                    //'已获得授权'
                    commit(state.mapping[permissionID].methods, true)
                } else if (result === 0) {
                    //'未获得授权'
                    commit(state.mapping[permissionID].methods, false)
                } else {
                    commit(state.mapping[permissionID].methods, true)
                    uni.showModal({
                        title: '提示',
                        content: '操作权限已被拒绝,请手动前往设置',
                        confirmText: "立即设置",
                        success: (res) => {
                            if (res.confirm) {
                                dispatch('gotoAppPermissionSetting')
                            }
                        }
                    })
                }
                if (viewObj) viewObj.close()
                resolve(result);
            });
        } catch (error) {
            console.log(error);
            reject(error);
        }
    },
    //提示框
    nativeObjView({
        state
    }, permissionID) {
        const systemInfo = uni.getSystemInfoSync();
        const statusBarHeight = systemInfo.statusBarHeight;
        const navigationBarHeight = systemInfo.platform === 'android' ? 48 :
            44; // Set the navigation bar height based on the platform
        const totalHeight = statusBarHeight + navigationBarHeight;
        let view = new plus.nativeObj.View('per-modal', {
            top: '0px',
            left: '0px',
            width: '100%',
            backgroundColor: '#444',
            //opacity: .5;
        })
        view.drawRect({
            color: '#fff',
            radius: '5px'
        }, {
            top: totalHeight + 'px',
            left: '5%',
            width: '90%',
            height: "100px",
        })
        view.drawText(state.mapping[permissionID].title, {
            top: totalHeight + 5 + 'px',
            left: "8%",
            height: "30px"
        }, {
            align: "left",
            color: "#000",
        }, {
            onClick: function (e) {
                console.log(e);
            }
        })
        view.drawText(state.mapping[permissionID].content, {
            top: totalHeight + 35 + 'px',
            height: "60px",
            left: "8%",
            width: "84%"
        }, {
            whiteSpace: 'normal',
            size: "14px",
            align: "left",
            color: "#656563"
        })
 
        function show() {
            view = plus.nativeObj.View.getViewById('per-modal');
            view.show()
            view = null//展示的时候也得清空,不然影响下次的关闭,不知道为啥
        }
 
        function close() {
            view = plus.nativeObj.View.getViewById('per-modal');
            view.close();
            view = null
        }
        return {
            show,
            close
        }
    },
 
    // 跳转到**应用**的权限页面
    gotoAppPermissionSetting({
        state
    }) {
        if (state.isIos) {
            var UIApplication = plus.ios.import("UIApplication");
            var application2 = UIApplication.sharedApplication();
            var NSURL2 = plus.ios.import("NSURL");
            // var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");		
            var setting2 = NSURL2.URLWithString("app-settings:");
            application2.openURL(setting2);
 
            plus.ios.deleteObject(setting2);
            plus.ios.deleteObject(NSURL2);
            plus.ios.deleteObject(application2);
        } else {
            // console.log(plus.device.vendor);
            var Intent = plus.android.importClass("android.content.Intent");
            var Settings = plus.android.importClass("android.provider.Settings");
            var Uri = plus.android.importClass("android.net.Uri");
            var mainActivity = plus.android.runtimeMainActivity();
            var intent = new Intent();
            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
            intent.setData(uri);
            mainActivity.startActivity(intent);
        }
    }
}
export default {
    namespaced: true,
    state,
    mutations,
    actions
};

2.在 main.js 中引入 veux 资源

import App from './App'

import store from './store'

Vue.prototype.$store = store

3.在请求权限的地方引入代码:

async uploadImage() {
            /* #ifdef APP-PLUS */
            let result = await this.$store.dispatch("permitions/requestPermissions",
                'READ_EXTERNAL_STORAGE')
            if (result !== 1) return
            /* #endif */
            uni.chooseImage({
                sourceType: ['album'], //从相册选择
                success: chooseImageRes => {
                    console.log('成功', chooseImageRes);
                    const tempFilePaths = chooseImageRes.tempFilePaths;
                    uni.uploadFile({
                        url: this.$baseUrl + 'file/',
                        filePath: tempFilePaths[0],
                        name: 'file',
                        formData: {
                            'user': this.$getUID()
                        },
                        header: {
                            'Authorization': 'Token ' + this.$getToken()
                        },
                        success: res => {
                            // console.log('上传成功', JSON.parse(res.data));
                            // uploadFile上传成功后,根据和后台的约定msgCode判断接口调用状态
                            let data = JSON.parse(res.data);
                            // 成功:获取到头像
                            console.log(data)
                            this.avatarUrl = data.url;
                            this.updateUserAvatar(this.avatarUrl);
                        }
                    });
                },
                fail: err => {
                    this.showToast('头像上传失败');
                }
            });
        },

实际效果:

视频效果:

参考链接:

https://blog.csdn.net/m0_59203969/article/details/134146475
https://www.python100.com/html/84811.html
https://blog.csdn.net/qq_51741194/article/details/124559734

☆版权☆

* 网站名称:obaby@mars
* 网址:https://obaby.org.cn/
* 个性:https://oba.by/
* 本文标题: 《UNIAPP 华为应用商店 未同步告知权限申请的使用目的》
* 本文链接:https://obaby.org.cn/2024/02/15245
* 短链接:https://oba.by/?p=15245
* 转载文章请标明文章来源,原文标题以及原文链接。请遵从 《署名-非商业性使用-相同方式共享 2.5 中国大陆 (CC BY-NC-SA 2.5 CN) 》许可协议。


猜你喜欢:

14 comments

  1.  
    WebView 4 WebView 4 Android 12 Android 12 cnGuangdong Shenzhen

    实在太麻烦了,这些商店的要求,各家合起来都可以开发一个小应用了,期待鸿蒙统一天下啊

  2. Google Chrome 104 Google Chrome 104 Android 13 Android 13 unknown局域网 IP

    华为鸿蒙还未做大,以后华为应用商店的APP要求可能更严格。
    好奇如果用户不同意使用手机图片等会怎样?

    1.   
      Google Chrome 118 Google Chrome 118 Mac OS X 10.15 Mac OS X 10.15 cn山东省青岛市 联通

      现在的要求是权限按需申请,需要访问图片的地方请求权限。
      要求权限申请最小化,本来是好事,不过现在这种东西都会层层加码,就比较恶心。
      如果不同意只会影响头像上传,用户不能上传头像,其他的没有任何印象。

  3.  
    Google Chrome 121 Google Chrome 121 Mac OS X 10.15 Mac OS X 10.15 cn江苏省无锡市 电信

    太难了太难了 属实太难了,每家厂商都试图恶心人

    1.   
      Google Chrome 118 Google Chrome 118 Mac OS X 10.15 Mac OS X 10.15 cn山东省青岛市 联通

      没办法啊,在人家的地盘混就得听人家的。

  4. Google Chrome 121 Google Chrome 121 Windows 10 Windows 10 cn江西省赣州市 移动

    各有各家要求,大杂烩~麻烦了开发者。只能说博主强大,全平台都上。

    1.   
      Google Chrome 120 Google Chrome 120 Windows 10 Windows 10 cn山东省临沂市 联通

      没办法啊,这还是允许个人开发者发布应用的,ov一直不可以,小米也禁止个人开发者上传应用了。
      能上的还是想办法上吧,最起码是有解决办法的。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注