React Native中的Appstate在Android上不断变化

13

我正在开发React Native项目,需要获取位置权限。同时,我必须始终跟踪位置权限,例如,如果用户在安装应用后授予权限,并在一段时间后转到设备设置中的应用程序设置并禁用/撤销权限,则再次从后台返回前台时,我必须根据权限检查是否显示消息。

因此,我使用AppState。但是,在Android上,如果用户通过“不再显示”复选框拒绝权限,则状态将始终在“后台”和“活动”之间不断变化。这会导致循环。

componentDidMount = async () => {
    AppState.addEventListener('change', this.handleAppStateChange);
  };

  componentWillUnmount() {
    AppState.removeEventListener('change', this.handleAppStateChange);
    Geolocation.clearWatch(this.watchID);
  }

  handleAppStateChange = async nextAppState => {
    const {appState} = this.state;
    console.log('nextAppState -->', nextAppState);
    console.log('appState -->', appState);
    if (appState === 'active') {
      // do this
      this.showLoader();
      await this.requestAndroidLocationPermission();
    } else if (appState === 'background') {
      // do that
    } else if (appState === 'inactive') {
      // do that other thing
    }

    this.setState({appState: nextAppState});
  };

requestAndroidLocationPermission = async () => {
    try {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
        {},
      );
      if (granted === PermissionsAndroid.RESULTS.GRANTED) {
        this.getLatitudeLongitude();
      } else if (granted === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
        this.hideLoader();
        this.setState({
          errorMessage: 'Location permission is denied',
          isLoading: false,
        });
      } else {
        this.hideLoader();
        this.requestAndroidLocationPermission();
      }
    } catch (err) {
      console.warn(err);
    }
  };

不再显示被拒绝许可后,它会继续打印(循环)

appState --> active
nextAppState --> background
appState --> active
nextAppState --> background
appState --> active
nextAppState --> background
appState --> active

它继续下去,永不停歇。

如何处理?有什么建议吗?


你正在使用哪个版本的React Native?最新文档如下所述。[https://facebook.github.io/react-native/docs/appstate#change]“通过监听更改事件类型并提供处理程序来添加AppState更改处理程序待办事项:现在AppState是NativeEventEmitter的子类,我们可以弃用addEventListener和removeEventListener,并直接使用addListener和listener.remove()。但这将是一个破坏性的变化,因为方法和事件名称都不同(addListener事件当前需要全局唯一)。" - yazmnh87
你好,你解决了你的问题吗?我遇到了同样的问题,不确定是什么问题!!! - Yasir
@Yasir,不是我没有解决。 - Anilkumar iOS - ReactNative
有人解决了吗?我也遇到了同样的问题。 - SVG
5个回答

11

我也遇到了同样的问题。不要使用AppState,它有问题。

问题出在RN对"background"的定义上。React Native使用android的activity(UI线程所在的持有者和UI所在位置)onPause回调作为发送"background"信号的触发器。但是,每当有东西出现在你的activity视图层次结构前面时,例如对话框(如权限框),其他活动(如文件选择器)等,都会调用onPause,因此对于Android React Native而言,“background”意味着“被外部UI元素/Android任务遮挡”而不是“暂停并发送到后台进行其他操作”,从而导致您看到的循环。最简短的解决方法是在ReactActivity中覆盖onPause,并添加控制条件,以确保只有在您真正进入后台时才调用super.onPause,例如检查您的任务堆栈或是否正在调用权限对话框,这样可以避免此类循环/错误调用。第二个选项是提供自己的应用程序生命周期事件,并具有清晰的触发条件。


1
不错!你能给我一些修复它的想法吗?如何提供自己的应用程序生命周期事件? - haxpanel
你需要检查你的应用程序任务堆栈。通常情况下,你会在顶部只有一个活动。如果你的活动被另一个应用程序隐藏,但是从你的应用程序内部或由你的应用程序调用,则你的任务堆栈中将有两个进程。这样你就知道你不在后台。你还可以检查是否有任何DialogFragments处于活动状态,这也会隐藏应用程序。基于这些指标,你需要通过DeviceEventEmitter发送一个事件,指示应用程序的实际状态。这有点取决于你的应用程序设计。 - Fco P.
1
@FcoP。任何文件或参考链接将不胜感激。 - Rahul

4
在Android中使用“focus”,在iOS中使用“change”。
我有一个自定义钩子,像这样:
import { useEffect } from 'react';
import { AppState, Platform } from 'react-native';

const focusEvent = Platform.OS === 'android' ? 'focus' : 'change';

const useLocationListener = () => {


  useEffect(() => {
    AppState.addEventListener(focusEvent, handleAppStateChange);

    getLocationAsync();
    return () => {
      AppState.removeEventListener(focusEvent, handleAppStateChange);
    };
  }, []);

  const handleAppStateChange = (nextAppState: string) => {
    if (nextAppState === 'active') {
      getLocationAsync();
    }
  };

  const getLocationAsync = async () => {
    const { canAskAgain, status } = await Permissions.getAsync(
      Permissions.LOCATION
    );

    if (canAskAgain) {
      const response = await Permissions.askAsync(Permissions.LOCATION);
       // handle location 
    } 
       // handle location with "status"
    
  };
};

export default useLocationListener;

4
嘿,伙计,工作正常。不过,在你的例子中,iOS 返回了“change”,而 Android 返回了“focus”(你弄反了 ;))。 - Nick Prozee
1
对我也有效,但是focus事件不会发送应用程序状态,因此在此示例中永远不会触发getLocationAsync。我认为您还需要在这里进行平台检查,例如 if (Platform.OS === 'android' || nextAppState === 'active') - RasTheDestroyer

2
你可以使用一个标志来检查应用程序是否应该处理后台,或者它只是一个权限调用。
const shouldHandleBackground = useRef(true)

const handler = (state) => { 
    if (state === 'active' && shouldHandleBackground.current) { 
        doStuff() 
    }
}

// when calling for permisson make the flag false

shouldHandleBackground.current = false
await Android.permission.LocationPermission()
shouldHandleBackground.current = true

在请求权限后,您可以将标志设置为true。

0
如@FcoP所解释的那样,你需要拥有自己的应用程序生命周期事件。 这可以通过更新你的MainActivity.java文件来实现。
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.modules.core.DeviceEventManagerModule;

@Override
  public void onResume() {
      super.onResume();
      ReactContext reactContext = getReactInstanceManager().getCurrentReactContext();
      WritableMap params = Arguments.createMap();
      params.putString("event", "active");

      // when app starts reactContext will be null initially until bridge between Native and React Native is established
      if(reactContext != null) {
          getReactInstanceManager().getCurrentReactContext()
              .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
              .emit("ActivityStateChange", params);
      }
  }
  @Override
  public void onPause() {
      super.onPause();
      ReactContext reactContext = getReactInstanceManager().getCurrentReactContext();
      WritableMap params = Arguments.createMap();
      params.putString("event", "inactive");

      if(reactContext != null) {
          getReactInstanceManager().getCurrentReactContext()
                  .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                  .emit("ActivityStateChange", params);
      }
  }

  @Override
  public void onStop() {
      super.onStop();
      ReactContext reactContext = getReactInstanceManager().getCurrentReactContext();
      WritableMap params = Arguments.createMap();
      params.putString("event", "background");

      if(reactContext != null) {
          getReactInstanceManager().getCurrentReactContext()
                  .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                  .emit("ActivityStateChange", params);
      }
  }


Then in your component 

import { NativeEventEmitter, NativeModules } from 'react-native';

 const appStateEmitter = new NativeEventEmitter();
      const subscription = appStateEmitter.addListener(
        'ActivityStateChange',
        (e) => {
          console.log(e.event);          
        }


希望这能帮到你。

0
我通过@Kushagra Agarwal的回答使我的代码工作起来了。
这是我的MainActivity.java的完整代码。
package [yourpackage];

import android.os.Build;
import android.os.Bundle;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import expo.modules.ReactActivityDelegateWrapper;

public class MainActivity extends ReactActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // Set the theme to AppTheme BEFORE onCreate to support 
    // coloring the background, status bar, and navigation bar.
    // This is required for expo-splash-screen.
    setTheme(R.style.AppTheme);
    super.onCreate(null);
  }

  /**
   * Returns the name of the main component registered from JavaScript.
   * This is used to schedule rendering of the component.
   */
  @Override
  protected String getMainComponentName() {
    return "main";
  }

  /**
   * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
   * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
   * (aka React 18) with two boolean flags.
   */
  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, new DefaultReactActivityDelegate(
        this,
        getMainComponentName(),
        // If you opted-in for the New Architecture, we enable the Fabric Renderer.
        DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
        // If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
        DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
        ));
  }

  /**
   * Align the back button behavior with Android S
   * where moving root activities to background instead of finishing activities.
   * @see <a href="https://developer.android.com/reference/android/app/Activity#onBackPressed()">onBackPressed</a>
   */
  @Override
  public void invokeDefaultOnBackPressed() {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
      if (!moveTaskToBack(false)) {
        // For non-root activities, use the default implementation to finish them.
        super.invokeDefaultOnBackPressed();
      }
      return;
    }

    // Use the default back button implementation on Android S
    // because it's doing more than {@link Activity#moveTaskToBack} in fact.
    super.invokeDefaultOnBackPressed();
  }


  // https://dev59.com/OlMH5IYBdhLWcg3wpgxe#77136574
  // Override the ReactActivity class, attach listener by ourself
  @Override
  protected void onResume() {
    super.onResume();
    ReactContext reactContext = getReactNativeHost().getReactInstanceManager().getCurrentReactContext();
    WritableMap params = Arguments.createMap();
    params.putString("event", "active");

    // when app starts reactContext will be null initially until bridge between Native and React Native is established
    if(reactContext != null) {
      reactContext
        .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
        .emit("ActivityStateChange", params);
    }
  }
  @Override
  protected void onPause() {
    super.onPause();
    ReactContext reactContext = getReactNativeHost().getReactInstanceManager().getCurrentReactContext();
    WritableMap params = Arguments.createMap();
    params.putString("event", "inactive");

    if(reactContext != null) {
      reactContext
        .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
        .emit("ActivityStateChange", params);
    }
  }

  @Override
  protected void onStop() {
    super.onStop();
    ReactContext reactContext = getReactNativeHost().getReactInstanceManager().getCurrentReactContext();
    WritableMap params = Arguments.createMap();
    params.putString("event", "background");

    if(reactContext != null) {
      reactContext
        .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
        .emit("ActivityStateChange", params);
    }
  }
}

1
点赞总是有帮助的 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接