Flutter混编(Android)

  • 重0开始新建一个module
  • 利用已有的module
  • 在原生页面中展示Flutter页面
  • flutter和原生通信
  • 使用FlutterBoost实现各种路由需求

其实Flutter的混编方案并非只有一种,但是本文所介绍的是Flutter团队官方给出的方案。它的优缺点我暂不讨论,接下来我们一步一步的实现一个Android项目的混编。

新建module

使用命令新建一个flutter的module,或者也可以使用AndroidStudio直接新建一个flutter module。

flutter create -t module mymodule

然后在app下的build.gradle文件中添加下面两个部分的内容

在android中添加

compileOptions {
  sourceCompatibility 1.8
  targetCompatibility 1.8
}

在dependencies添加flutter项目的依赖

implementation project(':flutter')

在项目的中的setting.gradle文件中添加如下代码

setBinding(new Binding([gradle: this]))
evaluate(new File(
        './mymodule/.android/include_flutter.groovy'))

注意:如果你的最低支持的sdk版本是低于16的话是会报错的,因为flutter最低支持api 16版本。ok如果不出什么其他意外的话集成的步骤已经完成了,可以开始原生和Flutter的混编之旅了。

Flutter页面在Activity中展示

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FlutterView flutterView = Flutter.createView(
                MainActivity.this,
                getLifecycle(),
                "route1"
        );
        FrameLayout layout = findViewById(R.id.content);
        layout.addView(flutterView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    }
}

Flutter页面在Fragment中展示


public class DemoFragmentActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);
        FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
        tx.replace(R.id.someContainer, Flutter.createFragment("route1"));
        tx.commit();
    }
}

或者是这样写

public class DemoFlutterFragment extends Fragment {
   @Override
    public FlutterView onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return Flutter.createView(getActivity(), getLifecycle(), "route1");
    }
}

这两种方式其实并没有什么区别,只是前者的createFragment方式是帮我们创建了一个FlutterFragment对象,这个FlutterFragment类的路径如下图所示:

《Flutter混编(Android)》 image.png

FlutterFragment的源码是这个样子的

public class FlutterFragment extends Fragment {
  public static final String ARG_ROUTE = "route";
  private String mRoute = "/“;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
      mRoute = getArguments().getString(ARG_ROUTE);
    }
  }
  @Override
  public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {
    super.onInflate(context, attrs, savedInstanceState);
  }
  @Override
  public FlutterView onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return Flutter.createView(getActivity(), getLifecycle(), mRoute);
  }
}

如何展示指定的Flutter页面

修改main.dart代码如下,根据传过来的路由名称来展示不同的flutter页面,默认情况是route1

import 'dart:ui';
import 'package:flutter/material.dart';
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
  switch (route) {
    case 'route1':
      return SomeWidget(...);
    case 'route2':
      return SomeOtherWidget(...);
    default:
      return Center(
        child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
      );
  }
}

如何解决flutter页面第一次加载黑屏的问题

public class DemoActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
        FlutterView flutterView = Flutter.createView(
                DemoActivity.this,
                getLifecycle(),
                "route1"
        );
        FrameLayout layout = findViewById(R.id.content);
        layout.addView(flutterView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        final FlutterView.FirstFrameListener[] listeners = new FlutterView.FirstFrameListener[1];
        listeners[0] = new FlutterView.FirstFrameListener() {
            @Override
            public void onFirstFrame() {
                layout.setVisibility(View.VISIBLE);
            }
        };
        flutterView.addFirstFrameListener(listeners[0]);
   }
}

使用消息通道和原生通信

这里以一个实际例子来说明问题,比如说flutter中打印日志只能使用print,并没有像Android里面的日志分级调试起来很不方便,这个时候就可以使用消息通道来调用原生的Log。

flutter代码

class LogUtils {

  static const perform = const MethodChannel("android_log");

  static void v(String tag, String message) {

    perform.invokeMethod('logV', {'tag': tag, 'msg': message});
  }
  static void d(String tag, String message) {
    perform.invokeMethod('logD', {'tag': tag, 'msg': message});
  }
  static void i(String tag, String message) {
    perform.invokeMethod('logI', {'tag': tag, 'msg': message});
  }
  static void w(String tag, String message) {
    perform.invokeMethod('logW', {'tag': tag, 'msg': message});
  }
  static void e(String tag, String message) {
    perform.invokeMethod('logE', {'tag': tag, 'msg': message});
  }
}

原生代码,这里使用了kotlin

MethodChannel(flutterView,"android_log").setMethodCallHandler { call, result ->
    logPrint(call)
}
private fun logPrint(call: MethodCall) {
    var tag: String = call.argument("tag")!!
    var message: String = call.argument("msg")!!
    when (call.method) {
        "logV" -> Log.v(tag, message)
        "logD" -> Log.d(tag, message)
        "logI" -> Log.i(tag, message)
        "logW" -> Log.w(tag, message)
        "logE" -> Log.e(tag, message)
    }
}

如何使用呢?在dart代码中调用如下:

LogUtils.v("tag", "v------");
LogUtils.d("tag", "d------");
LogUtils.i("tag", "i------");
LogUtils.w("tag", "w------");
LogUtils.e("tag", "e------");

更多关于消息通道的问题请参考https://flutter.dev/docs/development/platform-integration/platform-channels

怎么使用FlutterBoost做路由

flutter混编之后的路由跳转情况

  • 原生跳原生

  • 原生跳flutter

  • flutter跳原生

  • flutter跳flutter

原生跳原生这个不用说了之前怎么样就是怎么样,原生跳flutter、flutter跳原生、flutter跳flutter这三种情况我们想想要怎么处理呢?原生跳flutter页面我们可以通过原生页面A跳转到原生页面B然后通过原生页面B展示flutterB页面,flutter跳原生可以让flutterA通过消息通道通知原生页面A跳转到原生页面B,那么flutter跳flutter怎么办呢?首先要说的一点是在混编方案中Flutter本身的那套路由跳转(Navigator.push)是不起作用的,这点我们也是在开发新闻开发者这个demo的时候才发现的,那么怎么办呢?最笨的办法是flutterA通知原生页面A跳转原生页面B然后使用原生页面B展示flutterB,但是这样做不仅麻烦而且还消耗内存。原因是原生页面加载一个flutter页面的时候都要初始化一个flutter引擎,而不同页面的flutter引擎资源是不共享的,而且引擎本身也是非常重的。于是FlutterBoost进入我们的视野,FlutterBoost采用的方案是共享引擎模式,换句话说我们不同原生页面可以使用同一个flutter引擎,这样是不是就解决了我们的苦恼?

如何使用?
在Flutter的pubspec.yaml文件中添加依赖

dependencies:
  flutter:
    sdk: flutter
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  flutter_boost: ^0.0.400//添加flutterboost依赖

或者这样

flutter_boost:
        git:
            url: 'https://github.com/alibaba/flutter_boost.git'
            ref: '0.0.408'

然后在对应的flutter module路径先执行 flutter paskages get命令,然后重新build。

在Flutter端注册FlutterBoost

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new MyAppState();
  }
}
class MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    ///register page widget builders,the key is pageName
    FlutterBoost.singleton.registerPageBuilders({
      'MyHomePage': (pageName, params, _) => MyHomePage(),
      'FlutterPage': (pageName, params, _) => FlutterPage(),
      'FlutterFragmentPage': (pageName, params, _) => FlutterFragmentPage()
    });
    ///query current top page and load it
    FlutterBoost.handleOnStartPage();
  }
  @override
  Widget build(BuildContext context) => MaterialApp(
      title: 'Flutter Boost example',
      builder: FlutterBoost.init(),//注册FlutterBoost
      ///init container manager
      home: Container());
}

原生方面在app的build.gradle添加flutterboost依赖

implementation project(path: ':flutter_boost')

在Application中注册

FlutterBoostPlugin.init(new IPlatform() {
    @Override
    public Application getApplication() {
        return App.this;
    }
    @Override
    public Activity getMainActivity() {
        return null;//返回MainActivity对象
    }
    @Override
    public boolean isDebug() {
        //表示是否是debug模式
        return false;
    }
    @Override
    public boolean startActivity(Context context, String url, int requestCode) {
        //用于做路由跳转,url是在main.dart中定义好的页面的名称,返回true表示跳转成功,返回false表示失败
        //return false;
        return PageRouter.openPageByUrl(context, url);
    }
    @Override
    public Map getSettings() {
        return null;
    }
});

在Activity中展示Flutter页面

public class FlutterPageActivity extends BoostFlutterActivity {
    @Override
    public String getContainerName() {
        return "FlutterPage";
    }
    @Override
    public Map getContainerParams() {
        //传参数的时候用
        return null;
    }
    @Override
    public void onRegisterPlugins(PluginRegistry registry) {
        GeneratedPluginRegistrant.registerWith(registry);
    }
}

在Fragment中展示Flutter页面

public class DemoFlutterFragment extends BoostFlutterFragment {
    @Override
    public void destroyContainer() {
    }
    @Override
    public String getContainerName() {
        return "FlutterFragmentPage";
    }
    @Override
    public Map getContainerParams() {
        return null;
    }
    @Override
    public void onRegisterPlugins(PluginRegistry registry) {
        GeneratedPluginRegistrant.registerWith(registry)
    }
}

注意:getContainerName()方法返回的是当前Flutter页面的名称,并且这个Flutter页面必须已经被注册这样才能正常展示。

使用FlutterBoost发起路由跳转

FlutterBoost.singleton.openPage(
    "flutter:FlutterPage", {}, animated: true);

自定义协议

注册Flutter页面

FlutterBoost.singleton.registerPageBuilders({
      'xxxPage': (pageName, params, _) => xxxPage(),
    });

发起路由,跳转名字为xxxPage的Flutter页面

FlutterBoost.singleton.openPage(
    "flutter:xxxPage", {}, animated: true);

跳转Native页面

FlutterBoost.singleton.openPage(
    "native:xxxActivity", {}, animated: true);

PageRouter

public class PageRouter {
    public static boolean openPageByUrl(Context context, String url) {
        return openPageByUrl(context, url, 0);
    }
    public static boolean openPageByUrl(Context context, String url, int requestCode) {
        if (url.startsWith("flutter")) {//跳转Flutter
            Intent intent = new Intent(context, FlutterPageActivity.class);
            String[] strings = url.split(":");
            intent.putExtra("url", strings[1]);
            intent.putExtra("requestCode", requestCode);
            try {
                context.startActivity(intent);
                return true;
            } catch (Throwable t) {
                return false;
            }
        } else {
            //跳转native,根据:后面打的字符串来跳转不同的Activity
        }
        return false;
    }
}

FlutterPageActivity代码也做修改如下:

public class FlutterPageActivity extends BoostFlutterActivity {
    String pageRoute;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        pageRoute = getIntent().getStringExtra("url");
    }
    @Override
    public String getContainerName() {
        return pageRoute;
    }
    @Override
    public Map getContainerParams() {
        //传参数的时候用
        return null;
    }
    @Override
    public void onRegisterPlugins(PluginRegistry registry) {
        GeneratedPluginRegistrant.registerWith(registry);
    }
}

这样会使我们代码量减少,如在多个Fragment切换的场景下,遇到闪屏问题可以尝试在onRegisterPlugins方法中添加1如下代码

boostFlutterView.setZOrderOnTop(true)
boostFlutterView.holder.setFormat(PixelFormat.TRANSLUCENT)

参考

https://yq.aliyun.com/articles/693659?spm=a2c4e.11153959.0.0.7d75616br7x3bm

https://www.jianshu.com/p/1397936bbf9b

https://flutter.dev/docs/development/platform-integration/platform-channels

    原文作者:grr1314
    原文地址: https://www.jianshu.com/p/ff365c655f94
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞