前言

开发 APP 的过程中有个更改用户头像的功能,当时做完后在真机上自测了几遍都能顺利流畅的更换头像,结果在其它开发同事手机上发现一直提示无权限打开相册,但是我这边的测试机又是好的,确认了代码同步、版本一致后,打了个包让其它同事帮忙测试,发现反映的情况各不同,有的能正常打开相册、甚至相机,但有的如果相册打不开的情况下,相机也是一样的问题:提示无权限,我立马查看对应的手机设置里对应的原生权限是否有开启,再三确认已经开启无误后,我是一点都摸不着头脑,这 Bug 是整哪一出?随即,我开始搜索相关问题的篇幅,不停的翻阅 Andriod 官方文档和相关插件 issues,功夫不负有心人,这虫终于让我给揪出来了。

问题:在不同安卓机型上出现,已知的有小米、oppo、荣耀、华为。

  • 开始我一直以为是不同品牌的问题(并不是,因为在后续中发现相同品牌的也会出现这个问题)

描述:更换头像选择图片库或者相机的时候会有权限获取问题,有的失败、有的成功。

相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用的是expo官方的
import * as ImagePicker from "expo-image-picker";

// 对用户图片库权限做判断
// 用户在设置里的应用存储空间已打开,但是status不是granted,而是未授权状态,这种情况在部分机型上会发生。
// 同一版本在不同机型上,有的能打开有的不能打开
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();

// 对用户调用相机的权限做判断
// 这个是正常的,在任何机型下
const { status } = await ImagePicker.requestCameraPermissionsAsync();

// 但是打开相机有个前提:ImagePicker.requestMediaLibraryPermissionsAsync()中的status和ImagePicker.requestCameraPermissionsAsync()两者都要为granted才能打开,不然也会报没权限的提示。

推测:经过多方试验,怀疑跟 android:SdkVersion 有关,但官方 issue 里面也有相同的问题,但是没有好的解决方案。

issue1:https://github.com/expo/expo/issues/11504

issue2:https://github.com/expo/expo/issues/20496

方案

  • 在 Android 官方文档上有说明 Android 6 对比 Android 13+,后者废弃了不少 API,其中就有关于原生权限这一块的,找到了原因就能对此做出对应的解决方案了,因为插件升级版本的话,其它一些依赖也要跟着升级,但是升级的风险就是很多 API 废弃、调用方式、参数等都有可能改变,包括对构建的 SDK 版本也会有更高版本的要求,考虑风险、成本后,还是决定弃用 expo 第三方插件,自己写一个工具函数来根据不同的 Android 版本进行兼容来获取相关的相册权限。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import { PermissionsAndroid, Platform, PermissionStatus } from "react-native";

    /**
    * 判断安卓是否有权限
    * @returns {Promise<{status:PermissionStatus}>}
    */
    export const hasAndroidPermission = async () => {
    const OsVer = Platform.constants["Release"];

    // GET SPECIFIC MEDIA PERMISSION ANDROID 13+
    const permission =
    parseInt(OsVer, 10) >= 13
    ? PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
    : PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE;

    const hasPermission = await PermissionsAndroid.check(permission);
    if (hasPermission) return { status: "granted" };

    const status = await PermissionsAndroid.request(permission);
    return { status };
    };

    感想

    ​ 在当下,使用 React Native、Flutter、Uniapp 来替代原生 Andoriod、Ios 来开发 App,已经是众多公司的选择了,但是相应的对前端来说也是有许多大大小小的挑战、困难,从样式、业务功能、涉及到原生功能,在市面众多机型上都要能够适配,并且因为技术更迭速度快,导致如果进行大版本升级都要“伤筋动骨”,而且手机应用市场对 SDK 版本的要求也会动态上升调整,这就使开发者不得不去进行升级,只有不停下学习的脚步,才能适应这多变的时代。道阻且长,行则将至。