Flutter页面关闭时Crash

Flutter页面关闭时Crash

问题描述

项目中使用了Flutter的WebView组件来加载H5,关闭Activity时,如果FLutter页面中包含 WebView,就会导致Crash;页面中如果没有WebView,则正常关闭。
FlutterMainActivity 继承FlutterActivity,在关闭FlutterMainActivity时,FLutter页面中包含 WebView,则发生Crash
报错:Cannot execute operation because FlutterJNI is not attached to native.

解决方法:

在关闭 FlutterMainActivity之前,先将Flutter路由栈中的包含WebView的路由出栈

原因分析:

在源码中看到跑出异常的代码:

// FlutterJNI.java 225
private void ensureAttachedToNative() {
  if (nativePlatformViewId == null) {
    throw new RuntimeException("Cannot execute operation because FlutterJNI is not attached to native.");  }
}

ensureAttachedToNative从字面意思来看就是确保FLutter已经绑定到了Native,如果nativePlatformViewId为null,就抛出异常。
nativePlatformViewId 是什么?

    @Nullable
    private Long nativePlatformViewId;
  /**
   * Attaches this {@code FlutterJNI} instance to Flutter's native engine, which allows
   * for communication between Android code and Flutter's platform agnostic engine.
   * <p>
   * This method must not be invoked if {@code FlutterJNI} is already attached to native.
   */
  @UiThread
  public void attachToNative(boolean isBackgroundView) {
    ensureRunningOnMainThread();
    ensureNotAttachedToNative();
    nativePlatformViewId = nativeAttach(this, isBackgroundView);
  }
  /**
   * Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes
   * any further communication between Android code and Flutter's platform agnostic engine.
   * <p>
   * This method must not be invoked if {@code FlutterJNI} is not already attached to native.
   * <p>
   * Invoking this method will result in the release of all native-side resources that were
   * setup during {@link #attachToNative(boolean)}, or accumulated thereafter.
   * <p>
   * It is permissable to re-attach this instance to native after detaching it from native.
   */
  @UiThread
  public void detachFromNativeAndReleaseResources() {
    ensureRunningOnMainThread();
    ensureAttachedToNative();
    nativeDestroy(nativePlatformViewId);
    nativePlatformViewId = null;
  }

从上面的源码可以看出 nativePlatformViewId 在 attachToNative 时被赋值,在detachFromNativeAndReleaseResources 时,被置为 null。
nativePlatformViewId 可以理解为用来显示FLutter的Activity或者Fragment(这样描述并不准确,暂时可以这么理解),Activity或者Fragment被销毁了,nativePlatformViewId 就为 null。
attachToNative 执行的时机不再解释,detachFromNativeAndReleaseResources 什么时候执行呢?
源码中看到:

// FlutterActivity.java 585
// delegate.onDetach(); // 1
  @Override
  protected void onDestroy() {
    super.onDestroy();
    delegate.onDestroyView();
    delegate.onDetach();    // 1
    lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
  }
 
// FlutterActivityAndFragmentDelegate.java  439
// flutterEngine.destroy();     // 2
  void onDetach() {
    Log.v(TAG, "onDetach()");
    ensureAlive();
    // Give the host an opportunity to cleanup any references that were created in
    // configureFlutterEngine().
    host.cleanUpFlutterEngine(flutterEngine);
    if (host.shouldAttachEngineToActivity()) {
      // Notify plugins that they are no longer attached to an Activity.
      Log.d(TAG, "Detaching FlutterEngine from the Activity that owns this Fragment.");
      if (host.getActivity().isChangingConfigurations()) {
        flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges();
      } else {
        flutterEngine.getActivityControlSurface().detachFromActivity();
      }
    }
    // Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment,
    // and this Fragment's Activity.
    if (platformPlugin != null) {
      platformPlugin.destroy();
      platformPlugin = null;
    }
    flutterEngine.getLifecycleChannel().appIsDetached();
    // Destroy our FlutterEngine if we're not set to retain it.
    if (host.shouldDestroyEngineWithHost()) {
      flutterEngine.destroy();      // 2
      if (host.getCachedEngineId() != null) {
        FlutterEngineCache.getInstance().remove(host.getCachedEngineId());
      }
      flutterEngine = null;
    }
  }
// FlutterEngine.java 264
// flutterJNI.detachFromNativeAndReleaseResources(); // 3
  public void destroy() {
    Log.d(TAG, "Destroying.");
    // The order that these things are destroyed is important.
    pluginRegistry.destroy();
    dartExecutor.onDetachedFromJNI();
    flutterJNI.removeEngineLifecycleListener(engineLifecycleListener);
    flutterJNI.detachFromNativeAndReleaseResources(); // 3 
  }

也就是说在 FlutterActivity 关闭时,会将 nativePlatformViewId 置为 null。
那么问题来了,为什么 ensureAttachedToNative 会在 FlutterActivity 关闭之后继续执行?
一番Debug发现每次在Flutter中打开普通的路由时,ensureAttachedToNative 会在打开页面时被执行多次,之后不再执行,而当在Flutter中打开包含WebView的路由时,ensureAttachedToNative 会被不断执行,直到该路由出栈。(这里有大问题,原因不明,应该是官方的BUG)
所以,当一个包含FLutterWebView的路由入栈时,ensureAttachedToNative 会不断执行,如果此时Activity关闭了,nativePlatformViewId 被置为 null,ensureAttachedToNative 还在执行,于是 原地爆炸。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值