Android集成flutter之MethodChannel异常
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的异常了
还是要多看代码呀!