问题描述
创建的Flutter module按照撰写双端平台代码添加到host App中,并且通过MethodChannel调用host App 原生方法失败,抛MissingPluginException异常:
No implementation found for method testMethod on channel flutter.bridge.call_platform
问题出现的环境背景及自己尝试过哪些方法
-
为了排查问题,我特别使用现有flutter版本(1.14.6)新建了hello world的flutter module,也新建了一个helloworld Android app,再按add-to-app步骤添加依赖也一样存在此问题。因此可能不是1.12之后的plugin兼容问题(Supporting the new Android plugins APIs)。Flutter版本信息:
$ flutter --version Flutter 1.14.6 • channel beta • https://github.com/flutter/flutter.git Framework • revision fabeb2a16f (10 weeks ago) • 2020-01-28 07:56:51 -0800 Engine • revision c4229bfbba Tools • Dart 2.8.0 (build 2.8.0-dev.5.0 fc3af737c7)
- 在full-flutter测试项目中同样的写法可正常调起
- 在创建的Flutter module中,.android下写平台原生方法也是可正常调起
- 但按"将 Flutter module 集成到 Android 项目",无论是使用aar,还是直接源码依赖,均会有这个问题。
-
大致调用顺序:host App的点击事件,使用以下方式打开flutter模块,然后,在Flutter触发调用回host app的方法:
startActivity(FlutterActivity.createDefaultIntent(this));
相关代码
- host App的点击事件中打开flutter模块
package com.peter.myapplication;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.tv_test).setOnClickListener((view)->
startActivity(FlutterActivity.createDefaultIntent(this)));
}
}
- 在Flutter模块中点击回调中添加host app方法调用
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
///通过平台通道调用平台原生方法的通道名
static const bridgeNameToNative = 'flutter.bridge.call_platform';
static const MethodChannel _toPlatform = MethodChannel(bridgeNameToNative);
String _callRst = '点击按钮触发获取';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Fluuter:测试页面'),
),
body: Center(
child: Container(
padding: EdgeInsets.all(16),
child: Column(
children: <Widget>[
RaisedButton(
child: Text('call platform'),
onPressed: () {
////////////////////////////
//点击回调中添加触发调用平台原生方法
////////////////////////////
_toPlatform.invokeMethod('testMethod').then((value) {
print('---> testMethod = $value');
setState(() => _callRst = value);
}).catchError((e) {
print('---> ' + e.message);
setState(() => _callRst = e.message);
});
},
),
Text('call platform rst:\n' + _callRst)
],
),
),
),
);
}
}
- 在host App中处理此请求的原生代码如下:
package com.peter.myapplication;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
public class BridgeActivity extends FlutterActivity {
private static final String CHANNEL_NAME = "flutter.bridge.call_platform";
@Override
public void configureFlutterEngine(FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL_NAME)
.setMethodCallHandler((call, result) -> {
switch (call.method){
case "testMethod":
result.success("从平台原生方法返回的值");
break;
default:
result.notImplemented();
break;
}
});
}
}
- host App中AndroidManifest.xml的相关配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.peter.myapplication">
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".BridgeActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize" />
</application>
</manifest>
你期待的结果是什么?实际看到的错误信息又是什么?
理论上,应该在host app中onClick事件跳转到Flutter模块时,能打印出由Flutter模块发起、从host app返回的文本信息"从平台原生方法返回的值",但实际上却如开头所述,此时抛MissingPluginException异常。
是不是我使用的方式不符合最佳实践?为什么这个问题似乎没有人遇到?add-to-app按理应该还是有很广的应用场景的……
后来看了下FlutterActivity内部实现逻辑,算是搞清楚为什么了,应该是Flutter sdk的bug:
需要在
创建出来的
intent
中重新指向到子类BridgeActivity
,具体的整理了篇分析文章,感兴趣的伙伴欢迎查阅讨论:Add-to-app的MethodCallHandler不生效抛MissingPluginException解决