问题描述
基于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' }
}
通过以上列表,可以知道offAccelerometerChange
、offDeviceMotionChange
、offGyroscopeChange
、onAccelerometerChange
、onDeviceMotionChange
、onGyroscopeChange
这6个API都是依赖expo-sensors
的,而全局检索后,我进一步确认,这6个API在我们的业务代码中都没有使用到,那么就可以考虑通过移除expo-sensors
的方式尝试修复这个问题
修复方案
- 查看项目自身的
package.json
,移除所有expo-sensors
相关的依赖项声明 - 移除
node_modules/@tarojs/taro-rn/package.json
中关于expo-sensors
的依赖声明 - 移除
node_modules/@tarojs/taro-rn/libList.js
中offAccelerometerChange
、offDeviceMotionChange
、offGyroscopeChange
、onAccelerometerChange
、onDeviceMotionChange
、onGyroscopeChange
这6个API的导出 - 使用
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中
- 更新iOS中相关的依赖模块,应该能看到
Removing EXSensors
字样
bash
cd ios
pod install
- 重新运行
react-native run-ios
或者在Xcode中点击Run
按钮重新构建应用程序,根据之前的复现方法验证,不再触发该权限
ToDo
虽然问题暂时是解决了,但是为什么在js代码被重新加载时会触发隐私权限请求依然没有研究明白,所以这个解决方案是有瑕疵的。
如果是"有使用运动与健身相关API,但是因为热更新的原因,在非使用场景触发了隐私权限请求"这样的场景,就不能简单通过移除expo-sensors
来解决问题了