IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

React Native解决跳转新RN容器之后返回上级页面Modal不显示问题

IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

一、问题描述

React Native工程,如果上级页面A是一个RN页面并且此页面中有一个Modal弹层,下级页面B是通过路由的方式跳转,由一个原生RN容器去承载一个新的RN页面。这个时候如果跳转下级页面B,再次返回上级页面A你会发现点击不再显示Modal弹窗。

二、原因分析

我们可以先看一下Modal弹层的原生实现ReactModalHostView.java,关键代码分析

  /**
   * showOrUpdate will display the Dialog. It is called by the manager once all properties are set
   * because we need to know all of them before creating the Dialog. It is also smart during updates
   * if the changed properties can be applied directly to the Dialog or require the recreation of a
   * new Dialog.
   */
  protected void showOrUpdate() {
    // If the existing Dialog is currently up, we may need to redraw it or we may be able to update
    // the property without having to recreate the dialog
    if (mDialog != null) {
      if (mPropertyRequiresNewDialog) {
        dismiss();
      } else {
        updateProperties();
        return;
      }
    }

    // Reset the flag since we are going to create a new dialog
    mPropertyRequiresNewDialog = false;
    int theme = R.style.Theme_FullScreenDialog;
    if (mAnimationType.equals("fade")) {
      theme = R.style.Theme_FullScreenDialogAnimatedFade;
    } else if (mAnimationType.equals("slide")) {
      theme = R.style.Theme_FullScreenDialogAnimatedSlide;
    }
    Activity currentActivity = getCurrentActivity();
    Context context = currentActivity == null ? getContext() : currentActivity;
    mDialog = new Dialog(context, theme);
    mDialog
        .getWindow()
        .setFlags(
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

    mDialog.setContentView(getContentView());
    updateProperties();

    mDialog.setOnShowListener(mOnShowListener);
    mDialog.setOnKeyListener(
        new DialogInterface.OnKeyListener() {
          @Override
          public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_UP) {
              // We need to stop the BACK button from closing the dialog by default so we capture
              // that
              // event and instead inform JS so that it can make the decision as to whether or not
              // to
              // allow the back button to close the dialog.  If it chooses to, it can just set
              // visible
              // to false on the Modal and the Modal will go away
              if (keyCode == KeyEvent.KEYCODE_BACK) {
                Assertions.assertNotNull(
                    mOnRequestCloseListener,
                    "setOnRequestCloseListener must be called by the manager");
                mOnRequestCloseListener.onRequestClose(dialog);
                return true;
              } else {
                // We redirect the rest of the key events to the current activity, since the
                // activity
                // expects to receive those events and react to them, ie. in the case of the dev
                // menu
                Activity currentActivity = ((ReactContext) getContext()).getCurrentActivity();
                if (currentActivity != null) {
                  return currentActivity.onKeyUp(keyCode, event);
                }
              }
            }
            return false;
          }
        });

    mDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    if (mHardwareAccelerated) {
      mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
    }
    if (currentActivity != null && !currentActivity.isFinishing()) {
      mDialog.show();
      if (context instanceof Activity) {
        mDialog
            .getWindow()
            .getDecorView()
            .setSystemUiVisibility(
                ((Activity) context).getWindow().getDecorView().getSystemUiVisibility());
      }
      mDialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
    }
  }

注意到有以下一段逻辑,获取当前ReactContext所属Activity

  private @Nullable Activity getCurrentActivity() {
    return ((ReactContext) getContext()).getCurrentActivity();
  }

  /**
   * Get the activity to which this context is currently attached, or {@code null} if not attached.
   * DO NOT HOLD LONG-LIVED REFERENCES TO THE OBJECT RETURNED BY THIS METHOD, AS THIS WILL CAUSE
   * MEMORY LEAKS.
   */
  public @Nullable Activity getCurrentActivity() {
    if (mCurrentActivity == null) {
      return null;
    }
    return mCurrentActivity.get();
  }

那么我们可以猜测,没有显示出来大概率原因和这个Activity有关系,获取到的Activity并不是当前显示的Activity!走断点!果然和我想的一致,拿到的是新页面B容器的Activity,那么为啥拿到的Activity不是当前的显示的Activity! 追溯代码查看mCurrentActivity被赋值的地方

  /** Should be called by the hosting Fragment in {@link Fragment#onResume} */
  public void onHostResume(@Nullable Activity activity) {
    mLifecycleState = LifecycleState.RESUMED;
    mCurrentActivity = new WeakReference(activity);
    ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_START);
    for (LifecycleEventListener listener : mLifecycleEventListeners) {
      try {
        listener.onHostResume();
      } catch (RuntimeException e) {
        handleException(e);
      }
    }
    ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_END);
  }

  public void onNewIntent(@Nullable Activity activity, Intent intent) {
    UiThreadUtil.assertOnUiThread();
    mCurrentActivity = new WeakReference(activity);
    for (ActivityEventListener listener : mActivityEventListeners) {
      try {
        listener.onNewIntent(intent);
      } catch (RuntimeException e) {
        handleException(e);
      }
    }
  }

onNewIntent这边的赋值我们可以不用关心,因为我们是新打开页面不会走到这里,所以我们关注onHostResume被调用的地方,最后可以看到会在ReactInstanceManager.java的moveToResumedLifecycleState方法被调用

  private synchronized void moveToResumedLifecycleState(boolean force) {
    ReactContext currentContext = getCurrentReactContext();
    if (currentContext != null) {
      // we currently don't have an onCreate callback so we call onResume for both transitions
      if (force
          || mLifecycleState == LifecycleState.BEFORE_RESUME
          || mLifecycleState == LifecycleState.BEFORE_CREATE) {
        currentContext.onHostResume(mCurrentActivity);
      }
    }
    mLifecycleState = LifecycleState.RESUMED;
  }

而这个方法最终会被RN容器的onResume方法调用,分析到这里大致可以猜到原因了,就是我在页面B回页面A的时候,调用A容器的onResume方法时并没有去赋值,因为下面的条件没有过,此时的mLifecycleState还是 RESUMED状态

if (force
    || mLifecycleState == LifecycleState.BEFORE_RESUME
    || mLifecycleState == LifecycleState.BEFORE_CREATE) {
  currentContext.onHostResume(mCurrentActivity);
}

我们继续追溯ReactInstanceManager.java中的onHostDestroy方法,看看页面B销毁的时候做了哪些事情

  /**
   * Call this from {@link Activity#onDestroy()}. This notifies any listening modules so they can do
   * any necessary cleanup.
   *
   * @deprecated use {@link #onHostDestroy(Activity)} instead
   */
  @ThreadConfined(UI)
  public void onHostDestroy() {
    UiThreadUtil.assertOnUiThread();

    if (mUseDeveloperSupport) {
      mDevSupportManager.setDevSupportEnabled(false);
    }

    moveToBeforeCreateLifecycleState();
    mCurrentActivity = null;
  }

  private synchronized void moveToBeforeCreateLifecycleState() {
    ReactContext currentContext = getCurrentReactContext();
    if (currentContext != null) {
      if (mLifecycleState == LifecycleState.RESUMED) {
        currentContext.onHostPause();
        mLifecycleState = LifecycleState.BEFORE_RESUME;
      }
      if (mLifecycleState == LifecycleState.BEFORE_RESUME) {
        currentContext.onHostDestroy();
      }
    }
    mLifecycleState = LifecycleState.BEFORE_CREATE;
  }

从代码中可以看到,在销毁的时候会去重置生命周期状态mLifecycleState值为BEFORE_CREATE,那么结合前面分析的代码,理应在执行moveToResumedLifecycleState方法时,那边的判断条件中mLifecycleState 的值为BEFORE_CREATE,进入判断条件,重新赋值mCurrentActivity为页面A的Activity才对!我们在RN的容器的基类onResume方法和onDestroy方法打入断点,观察执行顺序,发现原因:在页面B回到页面A时,首先会去执行页面A的onResume方法,再尔才会去执行页面B的onDestroy方法,所以导致当执行moveToResumedLifecycleState方法时,此时的生命周期状态mLifecycleState还没有被重置为 BEFORE_CREATE,所以就不会进入重新赋值mCurrentActivity的逻辑,在页面A点击事件显示Modal时,ReactModalHostView.java获取到的Activity不是当前Activity,而是上次赋值的页面B所在的Activity,所以就不会显示Modal

三、解决问题

分析上述代码,可以得知Modal显示不出来是RN容器生命周期执行顺序的问题导致,那么我们唯一要做的处理就是保证页面B的onDestroy方法先于页面A的onResume方法就可以解决此问题!我的处理是在页面B容器执行finish方法之前或者点击RN容器根视图返回时去执行RN代理方法的onDestroy,这样就保证了页面B的onDestroy方法先于页面A的onResume方法!

//由于onResume方法会先于onDestroy执行,所以在执行finish()之前就去执行onDestroy
getReactDelegate().onDestroy();
this.finish();

public boolean onBackPressed() {
    Activity rootActivity = ActivityRegisterUtil.getCurrentActivity();
    if (getReactNativeHost().hasInstance()) {
        if (rootActivity != null && !rootActivity.isFinishing() && rootActivity instanceof XReactActivity) {
            String rootPageName = ((XReactActivity) rootActivity).getPageName();
            //判断是否是RN容器根视图,如果是则执行原生返回,不是则执行RN返回事件
            ArrayList pageNameList = ((XReactActivity) rootActivity).getPageNameList();
            if (pageNameList.size() == 1 && rootPageName.equals(pageNameList.get(0))) {
                //由于onResume方法会先于onDestroy执行,所以在执行finish()之前就去执行onDestroy
                onDestroy();
                return false;
            } else {
                getReactNativeHost().getReactInstanceManager().onBackPressed();
                return true;
            }
        }
        getReactNativeHost().getReactInstanceManager().onBackPressed();
        return true;
    }
    return false;
}

文章永久链接:https://tech.souyunku.com/?p=44755

赞(62) 打赏



未经允许不得转载:搜云库技术团队 » React Native解决跳转新RN容器之后返回上级页面Modal不显示问题

IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码
IDEA2023.1.3破解,IDEA破解,IDEA 2023.1破解,最新IDEA激活码

评论 抢沙发

大前端WP主题 更专业 更方便

联系我们联系我们

觉得文章有用就打赏一下文章作者

微信扫一扫打赏

微信扫一扫打赏


Fatal error: Uncaught Exception: Cache directory not writable. Comet Cache needs this directory please: `/data/wangzhan/tech.souyunku.com.wp/wp-content/cache/comet-cache/cache/https/tech-souyunku-com/index.q`. Set permissions to `755` or higher; `777` might be needed in some cases. in /data/wangzhan/tech.souyunku.com.wp/wp-content/plugins/comet-cache/src/includes/traits/Ac/ObUtils.php:367 Stack trace: #0 [internal function]: WebSharks\CometCache\Classes\AdvancedCache->outputBufferCallbackHandler() #1 /data/wangzhan/tech.souyunku.com.wp/wp-includes/functions.php(5109): ob_end_flush() #2 /data/wangzhan/tech.souyunku.com.wp/wp-includes/class-wp-hook.php(303): wp_ob_end_flush_all() #3 /data/wangzhan/tech.souyunku.com.wp/wp-includes/class-wp-hook.php(327): WP_Hook->apply_filters() #4 /data/wangzhan/tech.souyunku.com.wp/wp-includes/plugin.php(470): WP_Hook->do_action() #5 /data/wangzhan/tech.souyunku.com.wp/wp-includes/load.php(1097): do_action() #6 [internal function]: shutdown_action_hook() #7 {main} thrown in /data/wangzhan/tech.souyunku.com.wp/wp-content/plugins/comet-cache/src/includes/traits/Ac/ObUtils.php on line 367