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
还在执行,于是 原地爆炸。