Android集成flutter之MethodChannel异常

2020-07-20  本文已影响0人  RookieRun

1.项目中Flutter与Native项目通信时,用到了MethodChannel,根据官方文档:
native

package com.example.shared;
import android.content.Intent;
import android.os.Bundle;
import java.nio.ByteBuffer;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.ActivityLifecycleListener;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MyFlutterActivity extends FlutterActivity {

  private String sharedText;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
    Intent intent = getIntent();
    String action = intent.getAction();
    String type = intent.getType();

    if (Intent.ACTION_SEND.equals(action) && type != null) {
      if ("text/plain".equals(type)) {
        handleSendText(intent); // Handle text being sent
      }
    }
//这里的channel必须与flutter一致
    new MethodChannel(getFlutterView(), "app.channel.shared.data").setMethodCallHandler(
      new MethodCallHandler() {
        @Override
        public void onMethodCall(MethodCall call, MethodChannel.Result result) {
          if (call.method.contentEquals("getSharedText")) {
            result.success(sharedText);
            sharedText = null;
          }
        }
      });
  }

  void handleSendText(Intent intent) {
    sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
  }
}

manifest

<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />

flutter

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample Shared App Handler',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
//这里的channel必须与native一致
  static const platform = const MethodChannel('app.channel.shared.data');
  String dataShared = "No data";

  @override
  void initState() {
    super.initState();
    getSharedText();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: Center(child: Text(dataShared)));
  }

  getSharedText() async {
    var sharedData = await platform.invokeMethod("getSharedText");
    if (sharedData != null) {
      setState(() {
        dataShared = sharedData;
      });
    }
  }
}

而打开FlutterActivity在不引入第三方框架的情况下,有4种方式
1.传统的打开方式

   startActivity(new Intent(this, MyFlutterActivity.class));

2.默认打开flutter的main.dart

startActivity(
      MyFlutterActivity.createDefaultIntent(currentActivity)
    );

3.支持自定义route(有坑)

  MyFlutterActivity
        .withNewEngine()
        .initialRoute("/my_route")
        .build(currentActivity)
      );

4.由于engine的初始化开销较大,此种方法支持engine cache

public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    // Instantiate a FlutterEngine.
    flutterEngine = new FlutterEngine(this);

    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.getDartExecutor().executeDartEntrypoint(
      DartEntrypoint.createDefault()
    );

    // Cache the FlutterEngine to be used by FlutterActivity.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine);
  }
}
startActivity(
      MyFlutterActivity
        .withCachedEngine("my_engine_id")
        .build(currentActivity)
      );

好,以上背景介绍完毕,下面介绍坑
在上述的方法2,方法3,以及方法4打开MyFlutterActivity时,直接在flutter调用getSharedText()时,程序会抛出如下异常:

E/flutter: [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: MissingPluginException(No implementation found for method openNative on channel samples.flutter.dev/battery)
    #0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:154:7)
    <asynchronous suspension>
    #1      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:329:12)
    #2      AboutPage.build.<anonymous closure> (package:hencoderflutter/about.dart:14:25)
    #3      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
    #4      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:504:11)
    #5      BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:282:5)
    #6      BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:217:7)
    #7      PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:475:9)
    #8      PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:76:12)
    #9      PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:122:9)
    #10     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:379:8)
    #11     PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:120:18)
    #12     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:106:7)
    #13     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:218:19)
    #14     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:198:22)
    #15     GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:156:7)
    #16     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:102:7)
    #17     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:86:7)
    #18     _rootRunUnary (dart:async/zone.dart:1196:13)
    #19     _CustomZone.runUnary (dart:async/zone.dart:1085:19)
    #20     _CustomZone.runUnaryGuarded (dart:async/zone.dart:987:7)
    #21     _invoke1 (dart:ui/hooks.dart:275:10)
    #22     _dispatchPointerDataPacket (dart:ui/hooks.dart:184:5)

可能你会有疑问,为什么方法1就没事呢,这个问题也困扰了我很久,直到我通过log打印MyFlutterActivity的生命周期,连onCreate都没走的时候,我才觉察到问题的所在

MethodChannel是在configureFlutterEngine中初始化的,而方法1中,没有问题,很明显说明configureFlutterEngine的方法被回调了,进而MethodChannel的相关也就init了,带着这个疑问,我跟进了withNewEngine.build()源码,果然,问题出现在这儿
io.flutter.embedding.android.FlutterActivity

/**
   * Creates an {@link Intent} that launches a {@code FlutterActivity}, which creates a {@link
   * FlutterEngine} that executes a {@code main()} Dart entrypoint, and displays the "/" route as
   * Flutter's initial route.
   *
   * <p>Consider using the {@link #withCachedEngine(String)} {@link Intent} builder to control when
   * the {@link FlutterEngine} should be created in your application.
   */
  @NonNull
  public static Intent createDefaultIntent(@NonNull Context launchContext) {
    return withNewEngine().build(launchContext);
  }

  /**
   * Creates an {@link NewEngineIntentBuilder}, which can be used to configure an {@link Intent} to
   * launch a {@code FlutterActivity} that internally creates a new {@link FlutterEngine} using the
   * desired Dart entrypoint, initial route, etc.
   */
  @NonNull
  public static NewEngineIntentBuilder withNewEngine() {
//请注意这里1
    return new NewEngineIntentBuilder(FlutterActivity.class);
  }

 public static class NewEngineIntentBuilder {
    private final Class<? extends FlutterActivity> activityClass;
    private String initialRoute = DEFAULT_INITIAL_ROUTE;
    private String backgroundMode = DEFAULT_BACKGROUND_MODE;
 /**
     * Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of
     * {@code FlutterActivity}.
     *
     * <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link
     * #withNewEngine()}, which returns an instance of {@code NewEngineIntentBuilder} constructed
     * with a {@code Class} reference to the {@code FlutterActivity} subclass, e.g.:
     *
     * <p>{@code return new NewEngineIntentBuilder(MyFlutterActivity.class); }
     */
//请注意这里2
    protected NewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
      this.activityClass = activityClass;
    }

    /**
     * The initial route that a Flutter app will render in this {@link FlutterFragment}, defaults to
     * "/".
     */
    @NonNull
    public NewEngineIntentBuilder initialRoute(@NonNull String initialRoute) {
      this.initialRoute = initialRoute;
      return this;
    }

    /**
     * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
     * {@link BackgroundMode#transparent}.
     *
     * <p>The default background mode is {@link BackgroundMode#opaque}.
     *
     * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
     * {@link FlutterView} of this {@code FlutterActivity} to be configured with a {@link
     * FlutterTextureView} to support transparency. This choice has a non-trivial performance
     * impact. A transparent background should only be used if it is necessary for the app design
     * being implemented.
     *
     * <p>A {@code FlutterActivity} that is configured with a background mode of {@link
     * BackgroundMode#transparent} must have a theme applied to it that includes the following
     * property: {@code <item name="android:windowIsTranslucent">true</item>}.
     */
    @NonNull
    public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
      this.backgroundMode = backgroundMode.name();
      return this;
    }

    /**
     * Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with the
     * desired configuration.
     */
    @NonNull
    public Intent build(@NonNull Context context) {
      return new Intent(context, activityClass)
          .putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
          .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
          .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
    }
  }

代码中,标注了2处注意点,
1.构造中传入的时FlutterActivity.class,所以,我们使用方法2,方法3,以及方法4,MyFlutterActivity的生命周期根本就没有走,这是Flutter给我们偷梁换柱了。。。

找到问题所在了,那解决办法也就有了,既然,FlutterActivity的源码有问题,那么我们就override 那个createDefaultIntent方法,提供我们自己的MyFlutterActivityClass就行了呗,
天真。。。。
看下标明的注意2,那个NewEngineIntentBuilder,是protected,我们根本引用不到,那咋办呢?

解决办法

方法1.在打开Activity时,重设目标class

        final Intent defaultIntent = MyFlutterActivity.createDefaultIntent(this);
      defaultIntent.setClass(this,MyFlutterActivity.class);
        startActivity(defaultIntent);

方法2.重写createDefaultIntent方法,并且自定义MyEngineBuilder继承自NewEngineIntentBuilder,

  @NonNull
    public static Intent createDefaultIntent(@NonNull Context launchContext) {
        return withNewEngine().build(launchContext);
    }

    /**
     * Creates an {@link NewEngineIntentBuilder}, which can be used to configure an {@link Intent} to
     * launch a {@code FlutterActivity} that internally creates a new {@link FlutterEngine} using the
     * desired Dart entrypoint, initial route, etc.
     */
    @NonNull
    public static NewEngineIntentBuilder withNewEngine() {
        return new MyEngineBuilder(MyFlutterActivity.class);
    }


    public static class MyEngineBuilder extends NewEngineIntentBuilder {

        /**
         * Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of
         * {@code FlutterActivity}.
         *
         * <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link
         * #withNewEngine()}, which returns an instance of {@code NewEngineIntentBuilder} constructed
         * with a {@code Class} reference to the {@code FlutterActivity} subclass, e.g.:
         *
         * <p>{@code return new NewEngineIntentBuilder(MyFlutterActivity.class); }
         *
         * @param activityClass
         */
        protected MyEngineBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
            super(activityClass);
        }
    }

通过以上2种方法,就可以解决这个MissingPluginException的unsupported的异常了
还是要多看代码呀!

上一篇下一篇

猜你喜欢

热点阅读