Flutter學習中遇到的問題:已有專案加入Flutter模組
本文主要嘗試解決如下幾個問題:
- 如何在在已經專案加入Flutter
- 混合跳轉
- 混合棧問題
- 混合棧資料問題
跳轉黑屏是因為debug的緣故,打release包則沒有。

af.gif
1. 如何在在已經專案加入Flutter
直接參考這篇文章 ofollow,noindex">Add Flutter to existing apps
2. 混合跳轉
首先:Android跳轉到Flutter
建立一個FlutterActivity
直接繼承FlutterActivity或者自定義一個CustomFlutterActivity,我用的自定義,是做了一些封裝和flutter外掛註冊
public abstract class CustomFlutterActivity extends AppCompatActivity implements FlutterView.Provider, PluginRegistry, FlutterActivityDelegate.ViewFactory { private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this); private final FlutterActivityEvents eventDelegate; private final FlutterView.Provider viewProvider; private final PluginRegistry pluginRegistry; public CustomFlutterActivity() { this.eventDelegate = this.delegate; this.viewProvider = this.delegate; this.pluginRegistry = this.delegate; } @Override protected void onCreate(Bundle savedInstanceState) { if (injectRouter()) ARouter.getInstance().inject(this); super.onCreate(savedInstanceState); this.eventDelegate.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); registerCustomPlugin(this); } protected boolean injectRouter() { return false; } //省略部分程式碼 ......... ......... private static void registerCustomPlugin(PluginRegistry registrar) { FlutterPluginJumpToAct.registerWith(registrar.registrarFor(FlutterPluginJumpToAct.CHANNEL)); } }
做一個Flutter用的容器,通過容器統一管理需要跳轉的Flutter介面
@Route(path = RouterPath.MainPath.MAIN_FLUTTER_CONTAINER, name = "FlutterContainerActivity") public class FlutterContainerActivity extends CustomFlutterActivity { private static String CHANNEL = "com.jzhu.msg/plugin"; private static int TIME_ONE_SECOND = 1000; @Autowired(name = "path") String path; @Override protected boolean injectRouter() { return true; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initBasicMessageChannel(); } @Override public FlutterView createFlutterView(Context context) { WindowManager.LayoutParams matchParent = new WindowManager.LayoutParams(-1, -1); FlutterNativeView nativeView = this.createFlutterNativeView(); FlutterView flutterView = new FlutterView(FlutterContainerActivity.this, (AttributeSet) null, nativeView); flutterView.setInitialRoute(path); flutterView.setLayoutParams(matchParent); this.setContentView(flutterView); return flutterView; } private void initBasicMessageChannel() { switch (path) { case RouterPath.ModuleFlutterPath.FLUTTER_HOME: initHomeBasicMessage(); break; case RouterPath.ModuleFlutterPath.FLUTTER_TEST: initTestBasicMessage(); break; } } private void initTestBasicMessage() { BasicMessageChannel channel = new BasicMessageChannel<String>( getFlutterView(), CHANNEL, StringCodec.INSTANCE); channel.setMessageHandler((o, reply) -> { ToastUtils.show((String)o,3000); reply.reply("FlutterContainerActivity:回條訊息給你"); }); new Handler().postDelayed(() -> channel.send("FlutterContainerActivity:發條訊息給你"), TIME_ONE_SECOND); } private void initHomeBasicMessage() { //todo } }
Flutter提供的頁面
void main() { runApp(new MaterialApp( routes: <String, WidgetBuilder>{ '/homepage': (BuildContext context) => new MyHomePage(), '/testpage': (BuildContext context) => new TestPage(), }, home: new MyHomePage())); }
路由
public interface RouterPath { interfaceMainPath{ String MAIN_FLUTTER_CONTAINER = "/main/FlutterContainerActivity"; String MAIN_TEST= "/main/TestActivity"; } interfaceModuleFlutterPath{ String FLUTTER_HOME= "/homepage"; String FLUTTER_TEST= "/testpage"; } }
點選跳轉
ARouter.getInstance() .build(RouterPath.MainPath.MAIN_FLUTTER_CONTAINER) .withString("path", RouterPath.ModuleFlutterPath.FLUTTER_HOME) .navigation();
最後:Flutter跳轉到Android
通過外掛實現,用法參考我之前寫一篇 Flutter知識點: Flutter與原生(Android)的互動
3. 混合棧問題
- 如果是Android的頁面跳轉到Android頁面,所以就是普通的Activity棧。
- 如果是Android的頁面跳轉到Flutter頁面,那麼都使用了我們的容器FlutterContainerActivity,所以就是普通的Activity棧,這裡面遇到個坑,下面會提出並嘗試解決。
- 如果是Flutter的頁面跳轉到Flutter頁面,那麼由Flutter自己內部的棧管理。
- 如果是Flutter的頁面跳轉到Android頁面,那麼自己管自己就好啦。
混合跳轉註意:
- Android某些頁面的啟動模式Standard,SingleTop,SingleTask,SingleInstance
- Flutter頁面也存在一些特殊的跳轉,popAndPushNamed,pushNamedAndRemoveUntil,popUntil,pushReplacementNamed
以上需要根據實際情況處理,存在特殊跳轉,銷燬等邏輯
開發中需要特別需要注意的一個問題:
跳轉順序:Android頁面 -> Flutter頁面(使用了FlutterContainerActivity)-> Flutter頁面(原始)
期望結果:Flutter頁面(原始)-> Flutter頁面(使用了FlutterContainerActivity)-> Android頁面
真實結果:Flutter頁面(原始)-> Flutter頁面(使用了FlutterContainerActivity)-> Flutter頁面(homepage) -> Android頁面
得到疑問:我們並沒有啟動homepage,為什麼多了一個homepage?
程式碼猜想:Flutter棧裡初始化了一個homepage, 其他Flutter的頁面都在這個棧之上。
void main() { runApp(new MaterialApp( routes: <String, WidgetBuilder>{ ............ }, home: new MyHomePage())); }
如何解決:使用SystemNavigator.pop(),
Tells the operating system to close the application, or the closest equivalent.
onWillPop參考這篇 Flutter學習中的問題記錄: 如何監聽實體/虛擬返回鍵和AppBar返回鍵
class _MyHomePageState extends State<MyHomePage> { static const jumpPlugin = const MethodChannel('com.jzhu.jump/plugin'); Future<Null> _jumpToNative() async { Map<String, String> map = {"path": "/main/TestActivity"}; String result = await jumpPlugin.invokeMethod('jump2act', map); print(result); } Future<bool> _requestPop() { SystemNavigator.pop(); return new Future.value(false); } @override Widget build(BuildContext context) { return new WillPopScope( child: new Scaffold( appBar: new AppBar( title: new Text("Home Page"), ), body: new Center( child: new RaisedButton( child: new Text("跳到TestActivity"), onPressed: _jumpToNative), ), // This trailing comma makes auto-formatting nicer for build methods. ), onWillPop: _requestPop); } }
4. 混合棧資料問題
MethodChannel,EventChanneld的資料傳輸,用法參考我之前寫一篇 Flutter知識點: Flutter與原生(Android)的互動
主要舉例BasicMessageChannel和BinaryMessages

1_Sd6s3EDGkU8TBS9xLc4Zvw.png
FlutterContainerActivity中初始化,監聽,傳送
為什麼要延遲傳送?
因為FlutterView可能還沒初始化,這時候無法接收訊息
private static String CHANNEL = "com.jzhu.msg/plugin"; private static String CHANNEL_BINARY = "com.jzhu.msg.binary/plugin"; private static int TIME_ONE_SECOND = 1000; private void initTestBasicMessage() { BasicMessageChannel channel = new BasicMessageChannel<String>( getFlutterView(), CHANNEL, StringCodec.INSTANCE); channel.setMessageHandler((o, reply) -> { ToastUtils.show((String)o,3000); reply.reply("FlutterContainerActivity:回條訊息給你"); }); new Handler().postDelayed(() -> channel.send("FlutterContainerActivity:發條訊息給你"), TIME_ONE_SECOND); ByteBuffer message = ByteBuffer.allocateDirect(256); message.putDouble(3.14); message.putInt(123456789); new Handler().postDelayed(() -> getFlutterView().send(CHANNEL_BINARY,message), TIME_ONE_SECOND); getFlutterView().setMessageHandler(CHANNEL_BINARY, (byteBuffer, binaryReply) -> { byteBuffer.order(ByteOrder.nativeOrder()); double x = byteBuffer.getDouble(); int n = byteBuffer.getInt(); Log.i("zj", "Received: "+x+ " and "+ n); binaryReply.reply(message); }); }
Flutter中監聽,傳送
static const channel = const BasicMessageChannel<String>('com.jzhu.msg/plugin', StringCodec()); static const String channelBinary = 'com.jzhu.msg.binary/plugin'; void _sendMsg2Android() async { _replyMsg = await channel.send('TestPage:發條訊息給你'); setState(() {}); final WriteBuffer buffer = WriteBuffer() ..putFloat64(3.14) ..putInt32(123456789); final ByteData message = buffer.done(); _replyBinaryMsg = await BinaryMessages.send(channelBinary, message); _decodeData(message); } void _initMessageHandler() { channel.setMessageHandler((String message) async { _receivedMsg= message; setState(() {}); }); BinaryMessages.setMessageHandler(channelBinary, (ByteData message) async { _decodeData(message); }); } void _decodeData(ByteData message){ final ReadBuffer readBuffer = ReadBuffer(message); final double x = readBuffer.getFloat64(); final int n = readBuffer.getInt32(); print('Received $x and $n'); }