美文网首页
Android集成flutter之MethodChannel异常

Android集成flutter之MethodChannel异常

作者: RookieRun | 来源:发表于2020-07-20 16:55 被阅读0次

    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的异常了
    还是要多看代码呀!

    相关文章

      网友评论

          本文标题:Android集成flutter之MethodChannel异常

          本文链接:https://www.haomeiwen.com/subject/mljykktx.html