使用Taro开发iOS App触发额外权限请求的问题

问题描述

基于Taro开发原生应用,在iOS端会在业务代码完全没有使用相关API的情况下,提示用户授权运动与健身权限

版本信息

ini 复制代码
Taro CLI 3.6.24 environment info:
    System:
      OS: macOS 15.1.1
      Shell: 5.9 - /bin/zsh
    Binaries:
      Node: 20.3.1 - ~/.nvm/versions/node/v20.3.1/bin/node
      Yarn: 1.22.19 - ~/.nvm/versions/node/v20.3.1/bin/yarn
      npm: 9.6.7 - ~/.nvm/versions/node/v20.3.1/bin/npm
    npmPackages:
      @tarojs/cli: 3.6.1 => 3.6.1 
      @tarojs/components: 3.6.1 => 3.6.1 
      @tarojs/helper: 3.6.1 => 3.6.1 
      @tarojs/plugin-framework-react: 3.6.1 => 3.6.1 
      @tarojs/react: 3.6.1 => 3.6.1 
      @tarojs/rn-runner: 3.6.1 => 3.6.1 
      @tarojs/router: 3.6.1 => 3.6.1 
      @tarojs/runtime: 3.6.1 => 3.6.1 
      @tarojs/shared: 3.6.1 => 3.6.1 
      @tarojs/taro: 3.6.1 => 3.6.1 
      @tarojs/taro-rn: 3.6.1 => 3.6.1 
      @tarojs/webpack5-runner: 3.6.1 => 3.6.1 
      babel-preset-taro: 3.6.1 => 3.6.1 
      expo: ~47.0.3 => 47.0.13 
      react: 18.1.0 => 18.1.0 
      react-native: 0.70.7 => 0.70.7 

复现方法

该问题是在线上应用通过code-push热更新之后出现的,在触发热更新之前,整个应用在使用期间都不会请求该权限(因为毕竟没有使用到嘛),但是当热更新成功(即重新挂载jsbundle)之后,用户就会看到运动与健身权限的使用申请。

开发环境的话,可以通过npm run start启动metro服务,然后在应用内ReloadJS触发

问题分析

一开始我认为是其他同事在开发别的业务的时候使用到了相关API,但是通过全局搜索,发现并没有。

直到我精简了所有代码,让整个js业务代码部分只剩一个hello world以后,问题依然存在,我才确认,问题并不存在于js层,而应该是原生层的问题。

通过查阅苹果官方对于NSMotionUsageDescription这个权限的描述,该权限仅在应用使用了CMSensorRecorder, CMPedometer, CMMotionActivityManager, 和 CMMovementDisorderManager这4个底层API时才会触发,通过全局搜索,发现只有expo-sensors这个依赖使用到了CMPedometer

通过查询taro-native-shell的GitHub仓库,在readme中有列出各个expo包对应的Taro API如下:

css 复制代码
{
  authorize: Set(3) {
    'expo-camera@~14.0.0',
    'expo-image-picker@~14.7.0',
    'expo-location@~16.5.1'
  },
  chooseImage: Set(4) {
    '@react-native-camera-roll/camera-roll@~7.2.0',
    'expo-camera@~14.0.0',
    'expo-image-picker@~14.7.0',
    'react-native-safe-area-context@~4.8.0'
  },
  chooseMedia: Set(4) {
    '@react-native-camera-roll/camera-roll@~7.2.0',
    'expo-camera@~14.0.0',
    'expo-image-picker@~14.7.0',
    'react-native-safe-area-context@~4.8.0'
  },
  chooseVideo: Set(4) {
    '@react-native-camera-roll/camera-roll@~7.2.0',
    'expo-camera@~14.0.0',
    'expo-image-picker@~14.7.0',
    'react-native-safe-area-context@~4.8.0'
  },
  clearStorage: Set(1) { '@react-native-async-storage/async-storage@~1.21.0' },
  compressImage: Set(1) { '@bam.tech/react-native-image-resizer@~3.0.5' },
  createCameraContext: Set(1) { 'expo-camera@~14.0.0' },
  createInnerAudioContext: Set(1) { 'expo-av@~13.10.0' },
  downloadFile: Set(1) { 'expo-file-system@~16.0.0' },
  getAppBaseInfo: Set(1) { 'react-native-device-info@~10.12.0' },
  getClipboardData: Set(1) { '@react-native-clipboard/clipboard@~1.13.0' },
  getFileInfo: Set(1) { 'expo-file-system@~16.0.0' },
  getLocation: Set(2) {
    '@react-native-community/geolocation@~3.1.0',
    'expo-location@~16.5.1'
  },
  getNetworkType: Set(1) { '@react-native-community/netinfo@~11.2.0' },
  getRecorderManager: Set(2) { 'expo-av@~13.10.0', 'expo-file-system@~16.0.0' },
  getSavedFileInfo: Set(1) { 'expo-file-system@~16.0.0' },
  getSavedFileList: Set(1) { 'expo-file-system@~16.0.0' },
  getScreenBrightness: Set(1) { 'expo-brightness@~11.8.0' },
  getSetting: Set(3) {
    'expo-camera@~14.0.0',
    'expo-image-picker@~14.7.0',
    'expo-location@~16.5.1'
  },
  getStorage: Set(1) { '@react-native-async-storage/async-storage@~1.21.0' },
  getStorageInfo: Set(1) { '@react-native-async-storage/async-storage@~1.21.0' },
  getSystemInfo: Set(2) {
    'react-native-safe-area-context@~4.8.0',
    'react-native-device-info@~10.12.0'
  },
  getSystemInfoSync: Set(2) {
    'react-native-safe-area-context@~4.8.0',
    'react-native-device-info@~10.12.0'
  },
  offAccelerometerChange: Set(1) { 'expo-sensors@~12.9.0' },
  offDeviceMotionChange: Set(1) { 'expo-sensors@~12.9.0' },
  offGyroscopeChange: Set(1) { 'expo-sensors@~12.9.0' },
  offLocationChange: Set(1) { '@react-native-community/geolocation@~3.1.0' },
  offNetworkStatusChange: Set(1) { '@react-native-community/netinfo@~11.2.0' },
  onAccelerometerChange: Set(1) { 'expo-sensors@~12.9.0' },
  onDeviceMotionChange: Set(1) { 'expo-sensors@~12.9.0' },
  onGyroscopeChange: Set(1) { 'expo-sensors@~12.9.0' },
  onLocationChange: Set(1) { '@react-native-community/geolocation@~3.1.0' },
  onNetworkStatusChange: Set(1) { '@react-native-community/netinfo@~11.2.0' },
  openSetting: Set(3) {
    'expo-camera@~14.0.0',
    'expo-image-picker@~14.7.0',
    'expo-location@~16.5.1'
  },
  previewImage: Set(5) {
    '@react-native-camera-roll/camera-roll@~7.2.0',
    'expo-camera@~14.0.0',
    'expo-image-picker@~14.7.0',
    'react-native-safe-area-context@~4.8.0',
    'expo-file-system@~16.0.0'
  },
  removeSavedFile: Set(1) { 'expo-file-system@~16.0.0' },
  removeStorage: Set(1) { '@react-native-async-storage/async-storage@~1.21.0' },
  saveFile: Set(1) { 'expo-file-system@~16.0.0' },
  saveImageToPhotosAlbum: Set(4) {
    '@react-native-camera-roll/camera-roll@~7.2.0',
    'expo-camera@~14.0.0',
    'expo-image-picker@~14.7.0',
    'react-native-safe-area-context@~4.8.0'
  },
  saveVideoToPhotosAlbum: Set(4) {
    '@react-native-camera-roll/camera-roll@~7.2.0',
    'expo-camera@~14.0.0',
    'expo-image-picker@~14.7.0',
    'react-native-safe-area-context@~4.8.0'
  },
  scanCode: Set(5) {
    'react-native-safe-area-context@~4.8.0',
    'expo-camera@~14.0.0',
    'expo-barcode-scanner@~12.9.0',
    '@react-native-camera-roll/camera-roll@~7.2.0',
    'expo-image-picker@~14.7.0'
  },
  setClipboardData: Set(1) { '@react-native-clipboard/clipboard@~1.13.0' },
  setKeepScreenOn: Set(1) { 'expo-keep-awake@~12.8.0' },
  setScreenBrightness: Set(1) { 'expo-brightness@~11.8.0' },
  setStorage: Set(1) { '@react-native-async-storage/async-storage@~1.21.0' },
  showActionSheet: Set(1) { 'react-native-safe-area-context@~4.8.0' },
  startAccelerometer: Set(1) { 'expo-sensors@~12.9.0' },
  startDeviceMotionListening: Set(1) { 'expo-sensors@~12.9.0' },
  startGyroscope: Set(1) { 'expo-sensors@~12.9.0' },
  startLocationUpdate: Set(1) { '@react-native-community/geolocation@~3.1.0' },
  stopAccelerometer: Set(1) { 'expo-sensors@~12.9.0' },
  stopDeviceMotionListening: Set(1) { 'expo-sensors@~12.9.0' },
  stopGyroscope: Set(1) { 'expo-sensors@~12.9.0' },
  stopLocationUpdate: Set(1) { '@react-native-community/geolocation@~3.1.0' },
  uploadFile: Set(1) { 'expo-file-system@~16.0.0' }
}

通过以上列表,可以知道offAccelerometerChangeoffDeviceMotionChangeoffGyroscopeChangeonAccelerometerChangeonDeviceMotionChangeonGyroscopeChange这6个API都是依赖expo-sensors的,而全局检索后,我进一步确认,这6个API在我们的业务代码中都没有使用到,那么就可以考虑通过移除expo-sensors的方式尝试修复这个问题

修复方案

  1. 查看项目自身的package.json,移除所有expo-sensors相关的依赖项声明
  2. 移除node_modules/@tarojs/taro-rn/package.json中关于expo-sensors的依赖声明
  3. 移除node_modules/@tarojs/taro-rn/libList.jsoffAccelerometerChangeoffDeviceMotionChangeoffGyroscopeChangeonAccelerometerChangeonDeviceMotionChangeonGyroscopeChange这6个API的导出
  4. 使用patch-package将修改同步到我们自己的代码仓库里
java 复制代码
npx patch-package @tarojs/taro-rn --exclude 'nothing'

小tips,直接使用npx patch-package @tarojs/taro-rn默认情况下会忽略package.json文件内的改动,需要加上参数--exclude 'nothing'确保把package.json的改动也记录到patch中

  1. 更新iOS中相关的依赖模块,应该能看到Removing EXSensors字样
bash 复制代码
cd ios
pod install
  1. 重新运行react-native run-ios或者在Xcode中点击Run按钮重新构建应用程序,根据之前的复现方法验证,不再触发该权限

ToDo

虽然问题暂时是解决了,但是为什么在js代码被重新加载时会触发隐私权限请求依然没有研究明白,所以这个解决方案是有瑕疵的。

如果是"有使用运动与健身相关API,但是因为热更新的原因,在非使用场景触发了隐私权限请求"这样的场景,就不能简单通过移除expo-sensors来解决问题了

相关推荐
木西21 小时前
从0到1搭建一个RN应用从开发测试到上架全流程
android·前端·react native
哇哦谢谢你2 天前
React Native环境配置
前端·react native
getapi2 天前
Flutter和React Native在开发app中,哪个对java开发工程师更适合
java·flutter·react native
武当王丶也6 天前
React Native 状态管理:用 Jotai 替代 useState
前端·react native
武当王丶也6 天前
React Native 本地缓存:react-native-mmkv
前端·react native
武当王丶也6 天前
React Native 设备屏幕尺寸适配:react-native-size-matters
前端·react native
MshengYang_lazy7 天前
React Native离线级联选择器开发手记:当SQLite遇见小区房号选择
前端·react native·sqlite
No Silver Bullet9 天前
React Native进阶(六十一): WebView 替代方案 react-native-webview 应用详解
javascript·react native·react.js
武当王丶也9 天前
React Native 路由导航:React Navigation
react native
ThinkPet10 天前
【003安卓开发方案调研】之ReactNative技术开发安卓
android·react native·react.js