解决React-Native-Attached-DialogModule-to-host-with-pending-alert-but-no-FragmentManager

问题是这样的,线上的Bugly爆出这样一个错误,而且延续了好多个版本,一直没有解决,崩溃次数已经上千次,因为刚看过RN源码所以斗胆尝试解决一下。

Attached DialogModule to host with pending alert but no FragmentManager (not attached to an Activity).
com.facebook.infer.annotation.Assertions.java.lang.Object assertNotNull

下面是问题的解决过程:

google

首先想到的是把这个报错扔到google里面看看,找到了有类似的错误,但是没有看到好的解决方案,比如这个issue这个哥们应该是中国的,还没有人回应。还有找到的stackoverflow上一个问题有一个回答:

What does the rest of your activity look like? I ran into this but the problem was that I was not implementing DefaultHardwareBackBtnHandler in my activity.

因为某种原因,我们的RN版本一直使用的是0.23,也就是自己实现的reactActivity,DefaultHardwareBackBtnHandler我们也是实现了的,这个回答也被排除了。

那么网上找不到问题的解决方案也就没辙了,实际上上一次我尝试解决也是这样放弃的。

自己动手丰衣足食

没办法崩溃的人越来越多,逼着要尽快解决,其实也挺快的。

首先我找到了DialogModule.java,然后顺利的找到了报错的文本信息

public class DialogModule extends ReactContextBaseJavaModule implements LifecycleEventListener {

  private class FragmentManagerHelper {

  @Override
  public void onHostResume() {
    mIsInForeground = true;
    // Check if a dialog has been created while the host was paused,so that we can show it now.
    FragmentManagerHelper fragmentManagerHelper = getFragmentManagerHelper();
    Assertions.assertNotNull(
        fragmentManagerHelper,"Attached DialogModule to host with pending alert but no FragmentManager " +
        "(not attached to an Activity).");
    fragmentManagerHelper.showPendingAlert();
  }

}

报错信息完全对上了,意味着就是这个地方崩溃了的,那么接着就开始看onHostResume 方法在什么地方调用的。简单看了一下DialogModule实现了LifecycleEventListener 接口

public interface LifecycleEventListener {

  /** * Called when host (activity/service) receives resume event (e.g. {@link Activity#onResume} */
  void onHostResume();

  /** * Called when host (activity/service) receives pause event (e.g. {@link Activity#onPause} */
  void onHostPause();

  /** * Called when host (activity/service) receives destroy event (e.g. {@link Activity#onDestroy} */
  void onHostDestroy();

}

onHostResume 是生命周期中的一环,而且又是个接口,那么我开始怀疑Activity在执行onResume和他有某种关系。到BaseRNActivity.java(不用找了,这是我们自己写的一个类,这是Rn老版本的写法,现在直接使用ReactActivity就可以)

@Override
    protected void onResume() {
        super.onResume();
        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostResume(this,this);
        }
    }

因为之前分析过RN的源码,所以很清楚的知道,mReactInstanceManager的实现都在ReactInstanceManagerImpl.java中, so 直接找到onHostResume

@Override
  public void onHostResume(Activity activity,DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
    UiThreadUtil.assertOnUiThread();

    mDefaultBackButtonImpl = defaultBackButtonImpl;
    if (mUseDeveloperSupport) {
      mDevSupportManager.setDevSupportEnabled(true);
    }

    mCurrentActivity = activity;
    moveToResumedLifecycleState(false);
  }

代码很简单,判断是不是在主线程,设置DevSupport,执行moveToResumedLifecycleState

private void moveToResumedLifecycleState(boolean force) {
    if (mCurrentReactContext != 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) {
        mCurrentReactContext.onHostResume(mCurrentActivity);
      }
    }
    mLifecycleState = LifecycleState.RESUMED;
  }

继续进onHostResume

public void onHostResume(@Nullable Activity activity) {
    UiThreadUtil.assertOnUiThread();
    mCurrentActivity = new WeakReference(activity);
    for (LifecycleEventListener listener : mLifecycleEventListeners) {
      listener.onHostResume();
    }
  }

这里会然一笑,果然有个循环在执行这个LifecycleEventListener的onHostResume,到这里就不用再往下走了,我们已经确定了是在activity执行resume的时候,调用了DialogModule的onHostResume方法,这个时候fragmentManagerHelper为空照成了空指针错误。

那fragmentManagerHelper 什么时候为空尼??再回到DialogModle的onHostResume方法进去到
getFragmentManagerHelper

private @Nullable FragmentManagerHelper getFragmentManagerHelper() {
    Activity activity = getCurrentActivity();
    if (activity == null) {
      return null;
    }
    if (activity instanceof FragmentActivity) {
      return new FragmentManagerHelper(((FragmentActivity) activity).getSupportFragmentManager());
    } else {
      return new FragmentManagerHelper(activity.getFragmentManager());
    }
  }

从代码可以看出,只有当activity为空的时候,才会出现返回null,那activity什么时候会null,再进去

private @Nullable WeakReference<Activity> mCurrentActivity;

Activity getCurrentActivity() {
    if (mCurrentActivity == null) {
      return null;
    }
    return mCurrentActivity.get();
  }

原来mCurrentActivity是个WeakReference 弱引用,那么当系统垃圾回收的时候,就有可能为把它干掉了。

接下来开始思考,什么情况下,会出现调用Activity的onResume的时候,WeakReference会为空,下面全是经验之谈了,要造回收的场景,首选就是打开开发者模式的->不保留活动,不保留活动是意思是:用户离开后既销毁每个活动,

离开页面有三种情况:
1. 按back键
2. 按home
3. 切换到其他应用再切回来

测试了一下,第一种情况,会正常的处理back逻辑,没有崩溃,第二第三种场景都成功复现了这个bug

喜大普奔,复现了bug意味着bug解决了一半

解决问题

解决问题很简单,只要在fragmentManagerHelper使用前判空就可以,但是DialogModule是系统自带的,要想修复这个问题,还需要自己写个DialogModule,有点太重了,因为我们使用的RN版本很老了,我就想看看最新的版本也没有解决这个问题,升级RN 版本到0.33

@Override
  public void onHostResume() {
    mIsInForeground = true;
    // Check if a dialog has been created while the host was paused,so that we can show it now.
    FragmentManagerHelper fragmentManagerHelper = getFragmentManagerHelper();
    if (fragmentManagerHelper != null) {
      fragmentManagerHelper.showPendingAlert();
    } else {
      FLog.w(DialogModule.class,"onHostResume called but no FragmentManager found");
    }
  }

果然,已经修复了 ,那我们只要升级Rn版本就可以了。react-native 更新的非常快,很多bug都可以通过升级版本来解决,当然这是在碰运气,你去看看RN的issues已经超过了1000,你遇到的bug,能不能在下个版本修复,上天保佑吧。

通过这个bug的探索过程,我发现很多问题都是有迹可循的,只要我们耐心的分析,沉下心看源码。

希望这个bolg能帮助到遇到这个坑的同学。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom