使用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来解决问题了

相关推荐
郑叹200310 小时前
taro中使用captcha
微信小程序·taro
墨渊君2 天前
React Native 入门指南: 构建 UI 的必备核心组件
前端·react native·react.js
这个昵称也不能用吗?4 天前
react-native搭建开发环境过程记录
前端·react native·cocoapods
厨猿加加4 天前
FlatList 在 React Native 的最佳实践
前端·react native
jinzunqinjiu4 天前
学习react-native组件 1 Image加载图片的组件。
前端·react native
乐影5 天前
React Native踩坑记录之——屏幕适配
前端·react native
就是我6 天前
如何用lazy+ Suspense实现组件延迟加载
javascript·react native·react.js
就是我8 天前
React Native如何避免掉帧
前端·react native·react.js
墨渊君8 天前
React Native 与 React(Web) 开发的不同点, 如何快速上手?
前端·javascript·react native
前端熊猫9 天前
React Native (RN)的学习上手教程
学习·react native·react.js