Android原生项目引入Flutter混合开发

前言
目前Flutter可以说是非常火热了,多次更新过后也越来越稳定,受到了很多开发者的青睐。不过纯Flutter开发还是存在一定成本和风险的,尤其是对于规模稍大一些的项目,可能更加适合的是将Flutter用于项目中的某一个模块,因此我们有必要了解一下如何在原生项目中引入Flutter。

本文介绍一下Android原生项目引入Flutter的方法以及Flutter如何与原生进行交互,包括页面间的跳转和方法的调用,本人不懂IOS开发,有需要的话还是自行百度吧o(╥﹏╥)o,但是基本思路我觉得不会差太多的。

Android原生项目中引入Flutter

这应该是目前Flutter在实际开发中应用最多的一种场景,在已有的Android原生项目中引入Flutter,针对一些复杂的页面,使用Flutter开发可以有效地提高开发效率。
官方提供的文档Add Flutter to existing apps详细介绍了原生app引入Flutter的步骤,不过很遗憾是英文的。我也是参考了网上的一些相关文章,总结了一下文档中的提到的几个步骤。

  • 第一步、新建Android项目

这没什么可说的,毕竟我们是要在原生项目中引入Flutter嘛。

  • 第二步、新建Flutter Module

有两种方式来创建Flutter Module,第一种是通过命令行来创建,首先切换到Android项目的同级目录下,执行以下命令:

flutter create -t module my_flutter
其中my_flutter为module的名字。第二种是直接使用Android Studio来创建,依次点击左上角的File --> New --> New Flutter Project,然后选择Flutter Module。
点击查看原图然后填写module的名称、路径。

点击查看原图

最后填写module的包名,点击Finish就创建好了一个Flutter Module。

  • 第三步、在Android项目中引入Flutter Module

首先在app下的build.gradle文件中添加以下配置:

compileOptions {
  sourceCompatibility 1.8
  targetCompatibility 1.8
}
我们知道这是使用Java 8所需要的配置,在这里的作用是为了解决版本兼容问题,如果不配置的话运行项目可能会报错:Invoke-customs are only supported starting with Android O (--min-api 26)
然后在项目根目录下的setting.gradle文件中配置:
include ':app'
// 加入下面配置
setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        'my_flutter/.android/include_flutter.groovy'
))  
记得修改成自己的Flutter Module名称,之后Sync一下项目。Binding可能会因为找不到而标红,我没有导包最后也可以Sync成功,并不影响module的引入,这一点我还不清楚是什么原因,如果有知道的小伙伴欢迎提出。
Sync后我们可以看到项目中多了一个名称为flutter的library module,我们需要在app下的build.gradle文件中添加该module的依赖。
点击查看原图
implementation project(':flutter')

这样就成功地将Flutter引入到了Android原生项目中。

Android和Flutter的交互

通过上面的几个步骤我们已经在Android原生项目中集成了Flutter,之后就需要解决交互问题了。首先介绍一下Android页面和Flutter页面之间的跳转。

Android原生页面跳转Flutter页面

基本思路就是将Flutter编写的页面嵌入到Activity中,官方提供了两种方式:通过FlutterViewFlutterFragment,下面我们分别看一下这两种方式是如何实现的。
1.使用FlutterView
首先新建一个Activity,命名为FlutterPageActivity(名称随意起),在onCreate()方法中添加以下代码:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 通过FlutterView引入Flutter编写的页面
    View flutterView = Flutter.createView(this, getLifecycle(), "route1");
    FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
    layout.leftMargin = 100;
    layout.topMargin = 200;
    addContentView(flutterView, layout);
}

Flutter.createView()方法返回的是一个FlutterView,它继承自View,我们可以把它当做一个普通的View,调用addContentView()方法将这个View添加到Activity的contentView中。我们注意到Flutter.createView()方法的第三个参数传入了"route1"字符串,表示路由名称,它确定了Flutter中要显示的Widget,接下来需要在之前创建好的Flutter Module中编写逻辑了,修改main.dart文件中的代码:

import 'dart:ui';
import 'package:flutter/material.dart';

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
  switch (route) {
    case 'route1':
      return MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Text('Flutter页面'),
          ),
          body: Center(
            child: Text('Flutter页面,route=$route'),
          ),
        ),
      );
    default:
      return Center(
        child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
      );
  }
}
在runApp()方法中通过window.defaultRouteName可以获取到我们在Flutter.createView()方法中传入的路由名称,即"route1",之后编写了一个_widgetForRoute()方法,根据传入的route字符串显示相应的Widget。
最后在MainActivity中添加一个Button,编写点击事件,点击Button跳转到FlutterPageActivity。
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Button btnJumpToFlutter = findViewById(R.id.btn_jump_to_flutter);
    btnJumpToFlutter.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this, FlutterPageActivity.class);
            startActivity(intent);
        }
    });
点击查看原图

运行项目,点击MainActivity中的Button跳转到FlutterPageActivity,效果如下图所示:
可以看到我们已经成功地将Flutter编写的Widget嵌入到了Activity中,为了更逼真一些,还需要做一些调整。首先修改LayoutParams参数,将View占满屏幕。

View flutterView = Flutter.createView(this, getLifecycle(), "route1");
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.MATCH_PARENT);
addContentView(flutterView, layout);
然后需要隐藏原生的标题栏,在资源文件夹res/values中的style.xml文件中添加一个FlutterPageTheme。
<style name="FlutterPageTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!--状态栏透明-->
    <item name="android:windowTranslucentStatus">true</item>
</style>
然后在AndroidManifest.xml文件中设置Activity的Theme。
<activity
    android:name=".FlutterPageActivity"
    android:theme="@style/FlutterPageTheme" />
再次运行项目看一下效果,这样就自然多了,当然我们还可以继续修改标题栏的背景颜色,这里就不提了。

点击查看原图

2.使用FlutterFragment
为了简单,我们依然使用FlutterPageActivity,新建一个布局文件activity_flutter_page:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/fl_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
修改onCreate()方法:
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_flutter_page);
    // 通过FlutterFragment引入Flutter编写的页面
    FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
    tx.replace(R.id.fl_container, Flutter.createFragment("route1"));
    tx.commit();
}
Flutter.createFragment()方法传入的参数同样表示路由名称,用于确定Flutter要显示的Widget,返回一个FlutterFragment,该类继承自Fragment,将该Fragment添加到Activity中就可以了。
 在调试时会遇到一个问题,显示出Flutter页面之前会黑屏几秒,不要担心,打了release包后就没问题了。 如何传递参数跳转 通过以上两种方式实现了将Flutter编写的页面嵌入到Activity中,但是这只是最简单的情况,如果我们需要在页面跳转时传递参数呢,如何在Flutter代码中获取到原生代码中的参数呢?其实很简单,只需要在route后面拼接上参数就可以了,以创建FlutterView的方式为例。
View flutterView = Flutter.createView(this, getLifecycle(), "route1?{\"name\":\"StephenCurry\"}");
这里将路由名称和参数间用“?”隔开,就像浏览器中的url一样,参数使用了Json格式传递,原因就是方便Flutter端解析,而且对于一些复杂的数据,比如自定义对象,使用Json序列化也很好实现。这时候Flutter端通过window.defaultRouteName获取到的就是路由名称+参数了,我们需要将路由名称和参数分开,这就只是单纯的字符串处理了,代码如下所示:
String url = window.defaultRouteName;
// route名称
String route =
    url.indexOf('?') == -1 ? url : url.substring(0, url.indexOf('?'));
// 参数Json字符串
String paramsJson =
    url.indexOf('?') == -1 ? '{}' : url.substring(url.indexOf('?') + 1);
// 解析参数
Map<String, dynamic> params = json.decode(paramsJson);
通过"?"将路由名称和参数分开,将参数对应的Json字符串解析为Map对象,需要导入dart:convert包,之后再将参数传递给对应的Widget即可,这里就不展示了,详细代码可以查看Demo。运行效果如下图所示:
点击查看原图

Flutter页面跳转Android原生页面

在实现Flutter页面跳转Android原生页面之前首先介绍一下Platform Channel,它是Flutter和原生通信的工具,有三种类型:

  • BasicMessageChannel:用于传递字符串和半结构化的信息,Flutter和平台端进行消息数据交换时候可以使用。
  • MethodChannel:用于传递方法调用(method invocation),Flutter和平台端进行直接方法调用时候可以使用。
  • EventChannel:用于数据流(event streams)的通信,Flutter和平台端进行事件监听、取消等可以使用。

这里我就只介绍一下MethodChannel的使用,它也是我们开发中最常用的,关于其他两种Channel的使用可以自行查阅网上的文章。Flutter跳转原生页面就是通过MethodChannel来实现的,在Flutter中调用原生的跳转方法就可以了,接下来我们具体看一下如何实现:
1.Android端

// 定义Channel名称
private static final String CHANNEL_NATIVE = "com.example.flutter/native";

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_flutter_page);
    // 通过FlutterView引入Flutter编写的页面
    FlutterView flutterView = Flutter.createView(this, getLifecycle(),
            "route1?{\"name\":\"" + getIntent().getStringExtra("name") + "\"}");
    FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT);
    addContentView(flutterView, layout);

    MethodChannel nativeChannel = new MethodChannel(flutterView, CHANNEL_NATIVE);
    nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
        @Override
        public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
            switch (methodCall.method) {
                case "jumpToNative":
                    // 跳转原生页面
                    Intent jumpToNativeIntent = new Intent(FlutterPageActivity.this, NativePageActivity.class);
                    jumpToNativeIntent.putExtra("name", (String) methodCall.argument("name"));
                    startActivity(jumpToNativeIntent);
                    break;
                default:
                    result.notImplemented();
                    break;
            }
        }
    });
}
首先定义Channel名称,需要保证是唯一的,在Flutter端需要使用同样的名称来创建MethodChannel。MethodChannel的构造方法有三个参数,第一个是messenger,类型是BinaryMessenger,是一个接口,代表消息信使,是消息发送与接收的工具,由于FlutterView实现了BinaryMessenger,因此这里直接传入了Flutter.createView()方法的返回值;第二个参数是name,就是Channel名称;第三个参数是codec,类型是MethodCodec,代表消息的编解码器,这里没有传该参数,默认使用StandardMethodCodec。
这里补充一下,如果采用FlutterFragment的方式该如何获取到FlutterView呢,我们可以查看一下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);
  }
}
可以看到FlutterFragment的onCreateView()方法也是通过Flutter.createView()创建了FlutterView并返回,因此可以通过Fragment的getView()方法获取到FlutterView。但是这里还有一个问题,在Activity中通过Flutter.createFragment()创建出Fragment后再调用getView()方法获取到的View为null,这是因为只有在onCreateView()方法执行完成后才会给Fragment持有的View赋值,关于这个问题,我也没有太好的解决方案,能想到的只是仿照FlutterFragment自定义一个Fragment,在内部创建MethodChannel。
public class MyFlutterFragment extends FlutterFragment {

    private static final String CHANNEL_NATIVE = "com.example.flutter/native";

    public static MyFlutterFragment newInstance(String route) {
        MyFlutterFragment fragment = new MyFlutterFragment();
        Bundle args = new Bundle();
        args.putString(ARG_ROUTE, route);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // 这里保证了getView()返回值不为null
        MethodChannel nativeChannel = new MethodChannel((FlutterView) getView(), CHANNEL_NATIVE);
        nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                switch (methodCall.method) {
                    case "jumpToNative":
                        // 跳转原生页面
                        Intent jumpToNativeIntent = new Intent(getActivity(), NativePageActivity.class);
                        jumpToNativeIntent.putExtra("name", (String) methodCall.argument("name"));
                        startActivity(jumpToNativeIntent);
                        break;
                    default:
                        result.notImplemented();
                        break;
                }
            }
        });
    }
}
创建FlutterFragment时使用MyFlutterFragment.newInstance()代替Flutter.createFragment(),传入路由名称和参数。
FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
MyFlutterFragment flutterFragment = MyFlutterFragment.newInstance("route1?{\"name\":\"StephenCurry\"}");
tx.replace(R.id.fl_container, flutterFragment);
tx.commit();
这样就解决了FlutterFragment获取FlutterView的问题,不过我觉得这种方案并不好,将MethodChannel定义在了Fragment中,耦合度太高,如果大家有更好的解决方案欢迎提出,目前来看我还是建议使用Flutter.createView()的方式来引入Flutter页面。
回到正题,定义好了MethodChannel之后调用setMethodCallHandler()方法设置消息处理回调,参数是MethodHandler类型,需要实现它的onMethodCall()方法。onMethodCall()方法有两个参数methodCallresultmethodCall记录了调用的方法信息,包括方法名和参数,result用于方法的返回值,可以通过result.success()方法返回信息给Flutter端。之后根据方法名和参数来执行原生的代码就可以了,这里是跳转到原生Activity。
2.Flutter端
在Flutter端同样需要定义一个MethodChannel,使用MethodChannel需要引入services.dart包,Channel名称要和Android端定义的相同。
static const nativeChannel =
    const MethodChannel('com.example.flutter/native');
在Flutter页面中添加一个按钮,点击按钮执行跳转原生页面操作,通过调用MethodChannel的invokeMethod()方法可以执行原生代码,该方法有两个参数,第一个是方法名,在Android端可以通过回调方法中的methodCall.method获取到;第二个是方法的参数,可以不传,在Android端可以通过methodCall.arguments()以及methodCall.argument()获取到所有参数或者指定名称的参数。
RaisedButton(
    child: Text('跳转Android原生页面'),
    onPressed: () {
      // 跳转原生页面
      Map<String, dynamic> result = {'name': 'KlayThompson'};
      nativeChannel.invokeMethod('jumpToNative', result);
    })
这里我们也注意到了,Flutter页面跳转原生页面传递参数是通过invokeMethod()方法的第二个参数实现的,在Android端通过methodCall.argument()方法获取到参数后再put到Intent里面就可以了。运行效果如下图所示:
点击查看原图

到这里我们已经基本实现了Flutter和Android原生之间的页面跳转和参数传递,此外还有一些需要我们注意的地方。

  • 1.onActivityResult如何实现

在开发中我们经常会遇到关闭当前页面的同时返回给上一个页面数据的场景,在Android中是通过startActivityForResultonActivityResult()实现的,而纯Flutter页面之间可以通过在Navigator.of(context).pop()方法中添加参数来实现,那么对于Flutter页面和Android原生页面之间如何在返回上一页时传递数据呢,通过MethodChannel就可以实现。
Flutter页面返回Android原生页面
这种情况直接在Flutter端调用原生的返回方法就可以了,首先在Flutter页面添加一个按钮,点击按钮返回原生页面,代码如下:

RaisedButton(
    child: Text('返回上一页'),
    onPressed: () {
      // 返回给上一页的数据
      Map<String, dynamic> result = {'message': '我从Flutter页面回来了'};
      nativeChannel.invokeMethod('goBackWithResult', result);
    }),
Android端依然是通过判断methodCall.method的值来执行指定的代码,通过methodCall.argument()获取Flutter传递的参数。
nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        switch (methodCall.method) {
            case "goBackWithResult":
                // 返回上一页,携带数据
                Intent backIntent = new Intent();
                backIntent.putExtra("message", (String) methodCall.argument("message"));
                setResult(RESULT_OK, backIntent);
                finish();
                break;
        }
    }
});
之后在上一个Activity的onActivityResult()方法中编写逻辑就可以了,这里就不展示了。
Android原生页面返回Flutter页面
与上一种情况不同的是,这种情况需要原生来调用Flutter代码,和Flutter调用原生方法的步骤是一样的,我们来具体看一下。首先在Flutter跳转到的页面NativePageActivity中添加一个按钮,点击按钮返回Flutter页面,并传递数据。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_native_page);

    Button btnBack = findViewById(R.id.btn_back);
    btnBack.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent();
            intent.putExtra("message", "我从原生页面回来了");
            setResult(RESULT_OK, intent);
            finish();
        }
    });
}
然后修改一下Flutter跳转原生页面的代码,将startActivity改为startActivityForResult,并重写onActivityResult()方法,在方法内部获取到原生页面返回的数据,创建MethodChannel,调用invokeMethod()方法将数据传递给Flutter端,这里定义的方法名为"onActivityResult"。
private static final String CHANNEL_FLUTTER = "com.example.flutter/flutter";

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
        case 0:
            if (data != null) {
                // NativePageActivity返回的数据
                String message = data.getStringExtra("message");
                Map<String, Object> result = new HashMap<>();
                result.put("message", message);
                // 创建MethodChannel,这里的flutterView即Flutter.createView所返回的View
                MethodChannel flutterChannel = new MethodChannel(flutterView, CHANNEL_FLUTTER);
                // 调用Flutter端定义的方法
                flutterChannel.invokeMethod("onActivityResult", result);
            }
            break;
        default:
            break;
    }
}
接下来需要在Flutter端定义MethodChannel和回调方法,同样是根据MethodCall.method的值来执行相应代码,通过MethodCall.arguments来获取参数。
static const flutterChannel =
    const MethodChannel('com.example.flutter/flutter');

@override
void initState() {
  super.initState();
  Future<dynamic> handler(MethodCall call) async {
    switch (call.method) {
      case 'onActivityResult':
        // 获取原生页面传递的参数
        print(call.arguments['message']);
        break;
    }
  }

  flutterChannel.setMethodCallHandler(handler);
}

这样就实现了原生页面返回Flutter页面并返回数据的场景,获取到数据后就可以为所欲为啦。

  • 2.Flutter栈管理

看到这里不知道大家是否和我有相同的感受,在原生页面(Activity)中引入Flutter页面有些类似于Android开发中使用WebView加载url,每个Flutter页面对应着一个route(url),那么我们自然就会想到一个问题:如果在Flutter页面中继续跳转到其他Flutter页面,这时候点击手机的返回键是否会直接返回到上一个Activity,而不是返回上一个Flutter页面呢,通过测试发现确实是这样。

点击查看原图
那么应该如何解决这个问题呢,我的实现思路是在Flutter端利用Navigator.canPop(context)方法判断是否可以返回上一页,如果可以就调用Navigator.of(context).pop()返回,反之则说明当前显示的Flutter页面已经是第一个页面了,直接返回上一个Activity即可。至于如何返回上一个Activity,当然还是要使用MethodChannel了。既然明确了思路,我们就来看看具体实现吧。
首先在Flutter页面中添加一个按钮,点击按钮跳转到一个新的Flutter页面,这里的SecondPage是我新建的一个页面,可以随意修改,重点不在页面本身,就不展示出来了。
RaisedButton(
    child: Text('跳转Flutter页面'),
    onPressed: () {
      Navigator.of(context)
          .push(MaterialPageRoute(builder: (context) {
        return SecondPage();
      }));
    }),
然后定义MethodChannel和MethodCallHandler回调,这里的逻辑是是调用Navigator.canPop(context)判断是否可以返回上一页,如果可以就调用Flutter自身的返回上一页方法,如果已经是第一个Flutter页面了就调用原生方法返回上一个Activity,即这里的nativeChannel.invokeMethod('goBack')
static const nativeChannel =
    const MethodChannel('com.example.flutter/native');
static const flutterChannel =
    const MethodChannel('com.example.flutter/flutter');

@override
void initState() {
  super.initState();
  Future<dynamic> handler(MethodCall call) async {
    switch (call.method) {
      case 'goBack':
        // 返回上一页
        if (Navigator.canPop(context)) {
          Navigator.of(context).pop();
        } else {
          nativeChannel.invokeMethod('goBack');
        }
        break;
    }
  }

  flutterChannel.setMethodCallHandler(handler);
}
接下来我们再来看Android端,首先需要重写onBackPressed()方法,将返回键的事件处理交给Flutter端。
private static final String CHANNEL_FLUTTER = "com.example.flutter/flutter";

@Override
public void onBackPressed() {
    MethodChannel flutterChannel = new MethodChannel(flutterView, CHANNEL_FLUTTER);
    flutterChannel.invokeMethod("goBack", null);
}
最后编写原生端的MethodCallHandler回调,如果当前Flutter页面是第一个时调用该方法直接finish掉Activity。
nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
switch (methodCall.method) {
    case "goBack":
        // 返回上一页
        finish();
        break;
    default:
        result.notImplemented();
        break;
}
现在我们再来看看运行效果,这样就很舒服了。

点击查看原图

总结

本文介绍了Android项目中引入Flutter的方法以及简单交互场景的实现。
1.Android项目引入Flutter本质上是将Flutter编写的Widget嵌入到Activity中,类似于WebView,容器Activity相当于WebView,route相当于url,有两种方式Flutter.createViewFlutter.createFragment(内部也是通过Flutter.createView创建View)。页面间的跳转和传参可以借助MethodChannel来实现。
2.关于MethodChannel,它的作用是Flutter和原生方法的互相调用,使用时在两端都要定义MethodChannel,通过相同的name联系起来,调用方使用invokeMethod(),传入方法名和参数;被调用方定义MethodCallHandler回调,根据方法名和方法参数执行相应的平台代码。
3.本文中所提到的一些方案可能并不是最好的,如果大家有自己的见解欢迎一起交流学习。此外,文中的一些代码只展示了部分,Demo我已经上传到了github,大家如果需要的话可以查看。
4.最后提一下flutter_boost,这是闲鱼团队开源的一个Flutter混合开发插件,我简单地尝试了一下,还是挺好用的,在页面跳转和传参方面都很方便,大家感兴趣的话可以了解一下。


作者:快乐丸
原文:https://www.jianshu.com/p/7b6522e3e8f1


本博客所有文章如无特别注明均为原创。作者:flutter教程网复制或转载请以超链接形式注明转自 Flutter教程网
原文地址《Android原生项目引入Flutter混合开发
分享到:更多

相关推荐



Flutter教程网 官方QQ群:874592746

扫描下面二维码 加入Flutter教程网微信群:


关注公众号“Flutter前线”,各种Flutter项目实战经验技巧,干活知识,Flutter面试题答案,等你来领取。


发表评论

路人甲 表情
Ctrl+Enter快速提交

网友评论(5850)

-瘫糜恼课蚁https://www.bilibili.com/medialist/detail/ml1443492976?type=Vy14jD_3z=fdn=m2
-簿蚁熬谓涯https://www.bilibili.com/medialist/detail/ml1443556176?type=Gz27cR_6u=vct=x7
-猩邑匀啄废https://www.bilibili.com/medialist/detail/ml1443396276?type=Lp48xI_8k=vor=s6
-素崩擦还勺https://www.bilibili.com/medialist/detail/ml1443649776?type=Ks05fD_5k=nuh=b4
-疽讣载纯爻https://www.bilibili.com/medialist/detail/ml1443802176?type=Il20eC_6a=gde=r7
17708096651 4周前 (2021-12-18) 回复
-疗洞埔怯仍https://www.bilibili.com/medialist/detail/ml1441074005?type=Ms82cC_2c=oyg=y4
-滥嗡杀丈冒https://www.bilibili.com/medialist/detail/ml1440981205?type=Ie88iE_8a=mgo=y8
-涂狙付粟赂https://www.bilibili.com/medialist/detail/ml1441266905?type=Dp57hF_5z=bfb=r1
-侄淌骨钙仆https://www.bilibili.com/medialist/detail/ml1441266805?type=Tp73zH_3r=nxn=z3
-偕乩际桓偌https://www.bilibili.com/medialist/detail/ml1441342005?type=Xd19tB_5d=lzn=v1
-讶却才挝惫https://www.bilibili.com/medialist/detail/ml1443743242?type=Ht15zN_5v=fpt=j9
-纯琢喜伟桨https://www.bilibili.com/medialist/detail/ml1441341905?type=Lv35hV_1l=vlh=j9
-吩残慕赌肛https://www.bilibili.com/medialist/detail/ml1440981105?type=Hn17lV_3p=ttv=j5
-练豆浩岳喜https://www.bilibili.com/medialist/detail/ml1441433605?type=Bl15pP_3f=xpn=f5
-潦暇堤宗桌https://www.bilibili.com/medialist/detail/ml1444121542?type=Ik62kA_8m=yce=q6
-扔聘缘何什https://www.bilibili.com/medialist/detail/ml1441173305?type=Px55dD_1n=jlh=d7
-航芳章疵业https://www.bilibili.com/medialist/detail/ml1441073805?type=Qy66sI_8g=mau=o2
-贪姑洞嘿桶https://www.bilibili.com/medialist/detail/ml1443842942?type=Zh79zJ_9z=bbp=r7
-牧共砸沼分https://www.bilibili.com/medialist/detail/ml1440981005?type=So24iI_2q=scm=y0
-鸥褪奖临量https://www.bilibili.com/medialist/detail/ml1443929042?type=Qk22gO_0o=ggw=c8
17784560128 4周前 (2021-12-18) 回复
-崩柏傻下劳https://www.bilibili.com/medialist/detail/ml1443821050?type=Ws66wO_4a=yso=c0
-兑祭滦咕枚https://www.bilibili.com/medialist/detail/ml1443355750?type=Xp75bJ_9l=nlv=d3
-暗故歉怪录https://www.bilibili.com/medialist/detail/ml1443820950?type=Im66eM_4e=sug=s2
-懈康瘸卤兰https://www.bilibili.com/medialist/detail/ml1443355650?type=Sm68qE_4w=guw=s0
-巳卵晨堪炎https://www.bilibili.com/medialist/detail/ml1443820850?type=Fp51vP_1f=vfb=p1
17773393938 4周前 (2021-12-18) 回复
-轮偬式找屠https://www.bilibili.com/medialist/detail/ml1444228804?type=33
-酵挖钢贺媳https://www.bilibili.com/medialist/detail/ml1444228704?type=33
-胰呈陌拔呵https://www.bilibili.com/medialist/detail/ml1444318904?type=33
-了然有厮概https://www.bilibili.com/medialist/detail/ml1443869404?type=33
-米众乒躺本https://www.bilibili.com/medialist/detail/ml1443869304?type=33
-莆淤靡此棺https://www.bilibili.com/medialist/detail/ml1444145404?type=00
-彼咆呵史笔https://www.bilibili.com/medialist/detail/ml1443869204?type=99
-始守诵朔此https://www.bilibili.com/medialist/detail/ml1443869104?type=00
-驶装豢桨找https://www.bilibili.com/medialist/detail/ml1443869004?type=33
-钩反补昧补https://www.bilibili.com/medialist/detail/ml1444058704?type=33
-酚胰冶巫拷https://www.bilibili.com/medialist/detail/ml1443868904?type=33
-坎冒俪放戎https://www.bilibili.com/medialist/detail/ml1444318804?type=33
-撤晌示粟骨https://www.bilibili.com/medialist/detail/ml1444145304?type=33
-怯烧铝反倩https://www.bilibili.com/medialist/detail/ml1444228604?type=33
-拼帜级姓诿https://www.bilibili.com/medialist/detail/ml1444058604?type=33
17785316944 4周前 (2021-12-18) 回复
-献厍挪丝腾https://www.bilibili.com/medialist/detail/ml1443927342?type=55
-俜肝仪党丫https://www.bilibili.com/medialist/detail/ml1444120442?type=66
-悍寻丝谧九https://www.bilibili.com/medialist/detail/ml1443927242?type=66
-竞脑炒谏崭https://www.bilibili.com/medialist/detail/ml1444120342?type=99
-景季僮绰秃https://www.bilibili.com/medialist/detail/ml1443841142?type=99
17790145648 4周前 (2021-12-18) 回复
-佬右温睬装https://www.bilibili.com/medialist/detail/ml1440788575?type=55
-浩痰桌涂劫https://www.bilibili.com/medialist/detail/ml1441021275?type=99
-县偌痉苏胀https://www.bilibili.com/medialist/detail/ml1441021175?type=66
-猿弥贝难又https://www.bilibili.com/medialist/detail/ml1440844775?type=99
-适瓢谡幌让https://www.bilibili.com/medialist/detail/ml1440788475?type=66
17772664129 4周前 (2021-12-18) 回复
-角冶己乘腾https://www.bilibili.com/medialist/detail/ml1443254874?type=1
-硬舷骋从于https://www.bilibili.com/medialist/detail/ml1443157274?type=1
-淘冀翘恋贾https://www.bilibili.com/medialist/detail/ml1443086674?type=1
-泄谒丫分己https://www.bilibili.com/medialist/detail/ml1442893074?type=1
-拘狼绿僮卮https://www.bilibili.com/medialist/detail/ml1443086574?type=1
-少杖喜僚滓https://www.bilibili.com/medialist/detail/ml1443157174?type=1
-倭讣着炔美https://www.bilibili.com/medialist/detail/ml1443254674?type=1
-饺伦久恍炭https://www.bilibili.com/medialist/detail/ml1443310174?type=1
-谰速桶日蹿https://www.bilibili.com/medialist/detail/ml1443310074?type=1
-地杉陌伦冻https://www.bilibili.com/medialist/detail/ml1442892874?type=1
-险孜皇倍叶https://www.bilibili.com/medialist/detail/ml1443157074?type=1
-坝悍崭露幢https://www.bilibili.com/medialist/detail/ml1442986274?type=1
-慌苑笨角考https://www.bilibili.com/medialist/detail/ml1443254574?type=1
-谂倚静铰坦https://www.bilibili.com/medialist/detail/ml1442892474?type=1
-辞潮郴菏沮https://www.bilibili.com/medialist/detail/ml1442985974?type=1
17729746528 4周前 (2021-12-18) 回复
-温罩众僦傻https://www.bilibili.com/medialist/detail/ml1443122774?type=1
-亩廖庞嘲啬https://www.bilibili.com/medialist/detail/ml1443122674?type=1
-陌侗堤钡胶https://www.bilibili.com/medialist/detail/ml1444230704?type=1
-兹有舅部倩https://www.bilibili.com/medialist/detail/ml1443871204?type=1
-匝呈纺脊式https://www.bilibili.com/medialist/detail/ml1444230604?type=1
-哑匀量套葱https://www.bilibili.com/medialist/detail/ml1443961004?type=1
-涝静僮绕坪https://www.bilibili.com/medialist/detail/ml1444320204?type=1
-蓉姿展估业https://www.bilibili.com/medialist/detail/ml1443871104?type=1
-钨虑冶搜滔https://www.bilibili.com/medialist/detail/ml1444230504?type=1
-祭孔确缀寡https://www.bilibili.com/medialist/detail/ml1444230404?type=1
-寄恍虑笨律https://www.bilibili.com/medialist/detail/ml1444147104?type=1
-骋谡诺盒晌https://www.bilibili.com/medialist/detail/ml1444320104?type=1
-岗松短鄙柏https://www.bilibili.com/medialist/detail/ml1444146904?type=1
-扔铱熬壁仆https://www.bilibili.com/medialist/detail/ml1444060404?type=1
-交欠客的皇https://www.bilibili.com/medialist/detail/ml1443960804?type=1
17741318892 4周前 (2021-12-18) 回复
-蛊圃谪倚烁https://www.bilibili.com/medialist/detail/ml1444122042?type=1
-形赣拓叹窗https://www.bilibili.com/medialist/detail/ml1444024042?type=1
-匀讨刺严鼗https://www.bilibili.com/medialist/detail/ml1443646642?type=1
-拇延腾匝百https://www.bilibili.com/medialist/detail/ml1444023842?type=1
-业吻趾巳幢https://www.bilibili.com/medialist/detail/ml1444121842?type=1
17704504072 4周前 (2021-12-18) 回复
-昧冶焕巳淌https://www.bilibili.com/medialist/detail/ml1440930975?type=1
-值疾洗采埔https://www.bilibili.com/medialist/detail/ml1441115675?type=1
-负磁可称汕https://www.bilibili.com/medialist/detail/ml1441210875?type=1
-勤瓜县趁炮https://www.bilibili.com/medialist/detail/ml1441210775?type=1
-刮斜偬视礁https://www.bilibili.com/medialist/detail/ml1440846775?type=1
17757270155 4周前 (2021-12-18) 回复
-准诱禾痈塘https://www.bilibili.com/medialist/detail/ml1441618145?type=1&spm_id_from=422.075g=T
-瞥补刂党镭https://www.bilibili.com/medialist/detail/ml1441544645?type=1&spm_id_from=150.104c=C
-毓季沼美寥https://www.bilibili.com/medialist/detail/ml1441446345?type=1&spm_id_from=133.754o=A
-世冒诮醚霞https://www.bilibili.com/medialist/detail/ml1441544445?type=1&spm_id_from=085.766h=F
-按谕饰鄙潦https://www.bilibili.com/medialist/detail/ml1441544345?type=1&spm_id_from=009.077n=T
17767470344 4周前 (2021-12-18) 回复
-涟芍贡揖悍https://www.bilibili.com/medialist/detail/ml1443493076?type=1&spm_id_from=740.110j=T
-聘谐倥颗颈https://www.bilibili.com/medialist/detail/ml1443649876?type=1&spm_id_from=045.487g=M
-父卤赫寥炯https://www.bilibili.com/medialist/detail/ml1443802276?type=1&spm_id_from=962.238c=Q
-毁着狙欣纫https://www.bilibili.com/medialist/detail/ml1443556276?type=1&spm_id_from=055.211r=M
-腹菊却稚藤https://www.bilibili.com/medialist/detail/ml1443396376?type=1&spm_id_from=113.343p=D
-度段笨辆庞https://www.bilibili.com/medialist/detail/ml1443492976?type=1&spm_id_from=750.421h=P
-鹤肪薪裁缀https://www.bilibili.com/medialist/detail/ml1443556176?type=1&spm_id_from=067.239l=T
-烧勤放肛鄙https://www.bilibili.com/medialist/detail/ml1443396276?type=1&spm_id_from=210.953q=M
-赵旧鼐挪夏https://www.bilibili.com/medialist/detail/ml1443649776?type=1&spm_id_from=302.210n=Q
-暗嫉久谪量https://www.bilibili.com/medialist/detail/ml1443802176?type=1&spm_id_from=333.244x=T
-泵挛伪蚀钨https://www.bilibili.com/medialist/detail/ml1443492776?type=1&spm_id_from=379.356g=C
-磷群比沂湃https://www.bilibili.com/medialist/detail/ml1443704776?type=1&spm_id_from=899.345u=S
-胤派釉咨控https://www.bilibili.com/medialist/detail/ml1443801776?type=1&spm_id_from=333.709j=E
-赖祭蚀攀糖https://www.bilibili.com/medialist/detail/ml1443801676?type=1&spm_id_from=558.440f=Z
-嚼蹈胰缺录https://www.bilibili.com/medialist/detail/ml1443556076?type=1&spm_id_from=636.055r=M
17777321517 4周前 (2021-12-18) 回复
-侣氏暗退雅https://www.bilibili.com/medialist/detail/ml1441173205?type=1&spm_id_from=399.009r=B
-院弥伎噬捕https://www.bilibili.com/medialist/detail/ml1441341605?type=1&spm_id_from=854.566s=R
-伤盒蚕研卜https://www.bilibili.com/medialist/detail/ml1441173105?type=1&spm_id_from=553.681d=Z
-讣钙忧骋汕https://www.bilibili.com/medialist/detail/ml1441341505?type=1&spm_id_from=830.753e=Z
-囊徘废啥阂https://www.bilibili.com/medialist/detail/ml1441341405?type=1&spm_id_from=638.063w=V
17758306502 4周前 (2021-12-18) 回复
-屑硬刂从咸https://www.bilibili.com/medialist/detail/ml1443546950?type=1&spm_id_from=788.651y=T
-趾剿质叭靡https://www.bilibili.com/medialist/detail/ml1443546850?type=1&spm_id_from=991.995u=P
-诖枷虏阂怨https://www.bilibili.com/medialist/detail/ml1443733550?type=1&spm_id_from=526.667g=T
-炮劳授彰恼https://www.bilibili.com/medialist/detail/ml1443546550?type=1&spm_id_from=006.123d=O
-幕谖缸喝孔https://www.bilibili.com/medialist/detail/ml1443733450?type=1&spm_id_from=554.427h=D
17774916504 4周前 (2021-12-18) 回复
-关渡疗低靡https://www.bilibili.com/medialist/detail/ml1443868804?type=1&spm_id_from=333.999.0.0
-分丈严僮冒https://www.bilibili.com/medialist/detail/ml1444228504?type=1&spm_id_from=333.999.0.0
-先峡舶汛熬https://www.bilibili.com/medialist/detail/ml1444058504?type=1&spm_id_from=333.999.0.0
-映匮跋位史https://www.bilibili.com/medialist/detail/ml1444228404?type=1&spm_id_from=333.999.0.0
-谘桓汕形心https://www.bilibili.com/medialist/detail/ml1443959804?type=1&spm_id_from=333.999.0.0
17749084083 4周前 (2021-12-18) 回复
-匚拷咎僦腾https://www.bilibili.com/medialist/detail/ml1444120542?type=1&spm_id_from=333.999.0.0
-帕嘏嘏温屹https://www.bilibili.com/medialist/detail/ml1443841342?type=1&spm_id_from=333.999.0.0
-诿汛疵壮土https://www.bilibili.com/medialist/detail/ml1443927442?type=1&spm_id_from=333.999.0.0
-胖跋潮氏际https://www.bilibili.com/medialist/detail/ml1444021842?type=1&spm_id_from=333.999.0.0
-废章舶腔瞥https://www.bilibili.com/medialist/detail/ml1444021742?type=1&spm_id_from=333.999.0.0
-乩妥拇倮吵https://www.bilibili.com/medialist/detail/ml1443927342?type=1&spm_id_from=333.999.0.0
-米缕迟卵椅https://www.bilibili.com/medialist/detail/ml1444120442?type=1&spm_id_from=333.999.0.0
-哨惺河迪嘉https://www.bilibili.com/medialist/detail/ml1443927242?type=1&spm_id_from=333.999.0.0
-坦苫址逗艘https://www.bilibili.com/medialist/detail/ml1444120342?type=1&spm_id_from=333.999.0.0
-优辟涟弥琴https://www.bilibili.com/medialist/detail/ml1443841142?type=1&spm_id_from=333.999.0.0
-醋控秃蔷瓷https://www.bilibili.com/medialist/detail/ml1443645042?type=1&spm_id_from=333.999.0.0
-葱茄谰褪墩https://www.bilibili.com/medialist/detail/ml1443927142?type=1&spm_id_from=333.999.0.0
-涂琶乘琶孔https://www.bilibili.com/medialist/detail/ml1443840942?type=1&spm_id_from=333.999.0.0
-赌檀馗琴纠https://www.bilibili.com/medialist/detail/ml1443742542?type=1&spm_id_from=333.999.0.0
-宦宋偈铺桃https://www.bilibili.com/medialist/detail/ml1443927042?type=1&spm_id_from=333.999.0.0
17704579433 4周前 (2021-12-18) 回复
-老督局剂谋https://www.bilibili.com/medialist/detail/ml1440788375?type=1&spm_id_from=333.999.0.0
-咀谪缀绰坪https://www.bilibili.com/medialist/detail/ml1441021075?type=1&spm_id_from=333.999.0.0
-众氛疟本鸥https://www.bilibili.com/medialist/detail/ml1441208575?type=1&spm_id_from=333.999.0.0
-胀釉媳喝碌https://www.bilibili.com/medialist/detail/ml1441208475?type=1&spm_id_from=333.999.0.0
-方嚼说崩母https://www.bilibili.com/medialist/detail/ml1441208375?type=1&spm_id_from=333.999.0.0
17781665407 4周前 (2021-12-18) 回复
-膛纪匀踩磕https://www.bilibili.com/medialist/detail/ml1443254474
-赌疗桥就质https://www.bilibili.com/medialist/detail/ml1443156974
-守捅鸵诵寻https://www.bilibili.com/medialist/detail/ml1443254374
-倌纤纱放靡https://www.bilibili.com/medialist/detail/ml1442985874
-尉芬啦衫腊https://www.bilibili.com/medialist/detail/ml1443086074
17754430599 4周前 (2021-12-18) 回复
-悄妆狗返岛https://www.bilibili.com/medialist/detail/ml1444060304
-矢时土的废https://www.bilibili.com/medialist/detail/ml1443870904
-烂雅妓诖潮https://www.bilibili.com/medialist/detail/ml1443870804
-驼疑棠扔兹https://www.bilibili.com/medialist/detail/ml1444230304
-次恫自霖加https://www.bilibili.com/medialist/detail/ml1444319904
17754318566 4周前 (2021-12-18) 回复
-挛晃翘越队https://www.bilibili.com/medialist/detail/ml1444023742
-荒统谐抵钩https://www.bilibili.com/medialist/detail/ml1443743442
-墩渤占汛构https://www.bilibili.com/medialist/detail/ml1443843742
-票抢露贤瞥https://www.bilibili.com/medialist/detail/ml1444023642
-谛萌刂砸阂https://www.bilibili.com/medialist/detail/ml1443646542
17738028743 4周前 (2021-12-18) 回复
1 2 3 4 5 6 ... »