当前位置:   article > 正文

Flutter与Android的混合跳转和通信_android 跳转到 flutter 界面

android 跳转到 flutter 界面

1、项目特点

项目是Flutter作为主工程,将Android module或SDK作为模块嵌入到flutter中,与通常所熟悉的Android(或iOS)工程将flutter 为module嵌入到工程中有所不同。

2、业务需求

任意界面间的跳转,不管是flutter页面Android页面亦或是SDK中的页面,需要实现相互间的任意跳转且按照顺序返回(例如进入时A-B-C-D-C-D-B,返回时是B-D-C-D-C-B-A,不考虑启动模式)

3、实现方式探索

我们都知道Android 要打开一个Flutter页面(简称为A页面吧),需要借助与FlutterEngine来开启一个新的任务栈。

  1. startActivity(
  2. FlutterActivity
  3. .withNewEngine()
  4. .initialRoute("/second")
  5. .build(ThreeActivity.this)
  6. );

这样可以开启在Flutter代码中注册路由为“/second”的A页面,但这个页面加载速度会有些慢,因为每次都需要创建新的FlutterEngine,因此我们可以优化代码为:

  1. public class MyApplication extends FlutterApplication {
  2. @Override
  3. public void onCreate() {
  4. super.onCreate();
  5. FlutterEngine flutterEngineInit = new FlutterEngine(this);
  6. //flutterEngineInit.
  7. // 开始执行Dart代码以预热FlutterEngine
  8. flutterEngineInit.getNavigationChannel().setInitialRoute("/second");
  9. flutterEngineInit.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
  10. // 缓存FlutterActivity要使用的FlutterEngine
  11. FlutterEngineCache.getInstance().put("/second", flutterEngineInit);
  12. }
  13. }

自定义Application并在AndroidManifest.xml中注册,在自定义的application的onCreate()方法中将页面提前绑定到创建好的FlutterEngine上,并放入到缓存中

  1. <application
  2. android:label="untitled"
  3. android:name=".MyApplication"
  4. android:icon="@mipmap/ic_launcher"
  5. android:usesCleartextTraffic="true"
  6. ></application>

这样在打开A页面时就可以,从缓存中获取,加载体验会明显优于使用.withNewEngine()创建并打开A页面

  1. jumpTv.setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View view) {
  4. startActivity(
  5. FlutterActivity
  6. .withCachedEngine("/second")
  7. .build(ThreeActivity.this));
  8. }
  9. });

注意:需要在AndroidManifest.xml中注册FlutterActivity

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

到这里你可以在Android中打开A页面了。

4、遇到问题

但有一个很大的问题,如果A页面需要与Android交互怎么办?每次打开A页面都是放在一个新的FlutterEngine中,FlutterEngine是相互隔离的,channel无法互通。而我们的项目作为一个flutter项目,APP启动时,已经创建了FlutterEngine,且channel全部建立在这个engine中,上述方法打开的页面是无法使用这些channel;并且flutter B页面中也可以通过navigation.push打卡A页面,那A页面所在的FlutterEngine,即为B页面所在的FlutterEngine。如此情况A页面的在调用channel时会报如下错误:

  1. 2023-11-15 15:56:51.504 1528-10332/com.example.untitled E/flutter: [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: MissingPluginException(No implementation found for method jumpToAndroidWebviewPage on channel samples.flutter.jumpto.android)
  2. #0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:165:7)
  3. <asynchronous suspension>

提示我在A页面中调用的channel,没有找到对应的实现。这是因为Android通过FlutterEngine开启的A页面,新的任务栈中没有对应的实现。如何解决这个问题呢?

MethodChannel一般会是这样写:

  1. public class MainActivity extends FlutterActivity implements MethodChannel.MethodCallHandler{
  2. @Override
  3. protected void onCreate(@Nullable Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. }
  6. @Override
  7. public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
  8. super.configureFlutterEngine(flutterEngine);
  9. MethodChannel commonMethodChannel = new MethodChannel(flutterEngine.getDartExecutor(), "samples.flutter.jumpto.android");
  10. commonMethodChannel.setMethodCallHandler(this);
  11. }
  12. @Override
  13. public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
  14. }
  15. }

我注意到在创建channel的时候,接收两个参数一个是messenger,一个是name。name好理解就是channel名称,自己定义。而messenger,我们是通过flutterEngine.getDartExecutor()来获取的一个BinaryMessenger的实现类对象。 也就是说我们只要能拿到每次打开A页面的FlutterEngine对象,那就好解决这个问题了。

我们创建一个channel的管理类,方便我们在每个engine中实现channel(代码有优化空间)

  1. public class ChannelManage implements MethodChannel.MethodCallHandler, EventChannel.StreamHandler{
  2. private FlutterEngine flutterEngine;
  3. private Context context;
  4. EventChannel.EventSink jumpevents;
  5. public ChannelManage(FlutterEngine flutterEngine, Context context){
  6. this.flutterEngine = flutterEngine;
  7. this.context = context;
  8. MethodChannel commonMethodChannel = new MethodChannel(flutterEngine.getDartExecutor(), "samples.flutter.jumpto.android");
  9. commonMethodChannel.setMethodCallHandler(this);
  10. }
  11. @Override
  12. public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
  13. if (call.method.equals("jumpToAndroidPage")) {
  14. Intent intent=new Intent(context, ContentActivity.class);
  15. context.startActivity(intent);
  16. result.success("跳转");
  17. }else if(call.method.equals("jumpToAndroidSDKPage")){
  18. Intent intent=new Intent(context, JumpActivity.class);
  19. context.startActivity(intent);
  20. result.success("跳转");
  21. }else if(call.method.equals("jumpToAndroidWebviewPage")){
  22. Intent intent=new Intent(context, ThreeActivity.class);
  23. context.startActivity(intent);
  24. result.success("跳转");
  25. } else {
  26. result.notImplemented();
  27. }
  28. }
  29. @Override
  30. public void onListen(Object arguments, EventChannel.EventSink events) {
  31. events.success("Android");
  32. jumpevents=events;
  33. }
  34. @Override
  35. public void onCancel(Object arguments) {
  36. }
  37. }

步骤3中的代码改下:

我们在application中已经缓存了A页面和绑定的FlutterEngine,从缓存那取出来,获取对应的messenger设置给ChannelManage,这样A页面的Channel也可以使用啦。

  1. jumpTv.setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View view) {
  4. new ChannelManage(FlutterEngineCache.getInstance().get("/second"),ThreeActivity.this);
  5. startActivity(
  6. FlutterActivity
  7. .withCachedEngine("/second")
  8. .build(ThreeActivity.this));
  9. }
  10. });

但是不要高兴太早,还记得我们要的需求不?任意界面间的跳转,不管是flutter页面Android页面亦或是SDK中的页面。

按照上面的方式假如有一个Android activityB 通过上述方法打开了flutter A页面,A页面又通过Channel新开了activityB页面,activityB又打开了A页面,(这里例子比较极端,现实中A页面和activityB中间可能有其他页面,但不妨碍会出现循环打开的场景)那会是什么样子呢?

童年Andy 2023-11-11 16.19.28

我发现在返回到A页面倒数第二次打开的地方,屏幕上没有任何界面,且不能响应系统的手势放回。这肯定是不行啊,没有按原路返回啊,更大的问题是APP不能用了啊。

这是怎么回事呢?

其实是因为用了FlutterEngineCache,我们每次取到的都是一个FlutterEngine,那么当多次使用时,FlutterEngine就被放在了最后一次出现的位置,原位置将是一个空的engine,那这怎么玩。

再回过头来看下,如果每次都新建engine开启A页面呢?

回顾下上面的代码(中间写的废话太多了)

  1. startActivity(
  2. FlutterActivity
  3. .withNewEngine()
  4. .initialRoute("/second")
  5. .build(ThreeActivity.this)
  6. );

这肯定没问题了吧,试下。靠,Channel又报错了.......。

那就想法搞到每次的FlutterEngine不就行了吗?

看API....,哎呀,这玩意没有get FlutterEngine的API啊。这怎么玩。

但是如果我写一个MFlutterActivity extends FlutterActivity,然后重写configureFlutterEngine方法不就可以了吗?(MainActivity不就是这样搞的吗)

  1. startActivity(
  2. MFlutterActivity
  3. .withNewEngine()
  4. .initialRoute("/second")
  5. .build(ThreeActivity.this)
  6. );
  1. public class MFlutterActivity extends FlutterActivity {
  2. @Override
  3. protected void onCreate(@Nullable Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. }
  6. @Override
  7. public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
  8. new ChannelManage(flutterEngine,this);
  9. }
  10. }

注意:需要在AndroidManifest.xml中注册MFlutterActivity

来一把试试,结果不行。这里的configureFlutterEngine不会回调,不应该啊,MainActivity为什么就可以呢?

想不通啊想不通。翻源代码。

发现如下注释(走了很多弯路,不赘述了,也尝试过反射获取engine对象,我没有成功,小伙伴们可以试试)

  1. /**
  2. * Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of
  3. * {@code FlutterActivity}.
  4. *
  5. * <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link
  6. * #withNewEngine()}, which returns an instance of {@code NewEngineIntentBuilder} constructed
  7. * with a {@code Class} reference to the {@code FlutterActivity} subclass, e.g.:
  8. *
  9. * <p>{@code return new NewEngineIntentBuilder(MyFlutterActivity.class); }
  10. */
  11. public NewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
  12. this.activityClass = activityClass;
  13. }

啥意思?这意思是不是要自己重写withNewEngine方法?干吧,直接把系统的代码粘贴复制一下试试。

  1. /**
  2. * Creates an {@link NewEngineIntentBuilder}, which can be used to configure an {@link Intent} to
  3. * launch a {@code FlutterActivity} that internally creates a new {@link
  4. * io.flutter.embedding.engine.FlutterEngine} using the desired Dart entrypoint, initial route,
  5. * etc.
  6. *
  7. * @return The engine intent builder.
  8. */
  9. @NonNull
  10. public static NewEngineIntentBuilder withNewEngine() {
  11. return new NewEngineIntentBuilder(FlutterActivity.class);
  12. }

APP起来,起来吧!!!

童年Andy 2023-11-11 14.30.39

5、总结:

实现flutter和Activity间的任意跳转一定要实现对Channel的统一管理和自定义MFlutterActivity继承FlutterActivity,用MFlutterActivity去执行加载flutter页面的代码开启新的engine,且一定要重写withNewEngine方法,否则Activity的生命周期以及configureFlutterEngine是不会执行的,也就没办法拿到engine,以便为统一管理的Channel设置messenger。

6、原代码

最后上demo代码,自取。

flutter_android_jump: 实现flutter工程下嵌入SDK、Android module后,flutter与Android界面间的任意跳转和通信

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/810254
推荐阅读
相关标签
  

闽ICP备14008679号