Android混入Flutter以及Android与Flutter通信

从零开始

首先,创建一个新的项目文件夹用来存放Android项目和flutter的项目.
原生的Android项目我给它命名为AndroidWithFlutterProject.

然后创建一个flutter 的 module项目,我命名为flutter_with_android1 :

在这里插入图片描述
看提示也知道,这个Module就是为了混合进原生的.
创建好之后,我们的项目文件夹就变成了这个样子:

在这里插入图片描述
二者在同一目录,此时将flutter的项目引入到Android中:

在Android 的app上面右键,依次点击new , Module , :
在这里插入图片描述
然后选择最后的import flutter module
在这里插入图片描述

选择我们创建好的flutter项目就可以.

然后用Android Studio打开flutter项目编译一下, 就可以在Android项目看到它了:
在这里插入图片描述

在Android工程中创建Flutter的View

Flutter提供了两种方式让Android工程来引用组件,一种是View,一种是Fragment,这里选用View来进行讲解,Fragment同理。

这里我们用两种方式来引入FLutter,本质上是作为一个view引入布局还是将FlutterView作为Activity的根View。

以单个view引入布局

        View flutterView = Flutter.createView(this, getLifecycle(), "route1");

通过上面很简单的一个方法,我们就能通过Flutter创建出一个view,这个方法提供三个参数,第一个是Activity,第二个参数是一个Lifecycle对象,我们取Activity的lifecycle即可,第三个参数为route,这个参数Flutter端可以通过window.defaultRouteName获取,利用它flutter可知道要创建哪个widget.。

创建出这个FlutterView之后就可以按常规的操作来将它加入到任何你想要的布局中去了。
同理 Flutter.createFragment(String route)可生成FlutterFragment

以根view作为Activity

创建一个空的Activity,用Flutter创建一个View作为页面的根View:

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        View flutterView = Flutter.createView(this, getLifecycle(), "route1");
        ViewGroup.LayoutParams layout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
        addContentView(flutterView, layout);
    }
}

这里我们并没有使用setContentView而是是用了addContentView这个方法,原因是这样的:
虽然FLutter的加载速度非常快,但是这个过程依然存在,在创建FLutterView之前我们先给ContentView设置了一个R.layout.activity_flutter布局,这个布局可以作为FlutterView加载完成之前展示给用户的界面,当然大部分情况下用户根本感知不到这个界面Flutter已经加载完成了,但我们仍需要它,因为debug模式下造成Flutter的加载速度并不是非常快,这个界面可以给开发人员看,还有就是如果没有这个界面的话在Activity的加载过程会出现一个黑色的闪屏,而这个情况对用户来说并不友好。

在Flutter工程中根据不同的route创建不同的组件

flutter 只有一个入口, 就是main.dart中的main方法 ,

void main() => runApp(new MyApp());

我们这要根据原生传来的route显示不同的组件,所以不执行runApp,而是通过window的全局变量中获取到当前的routeName来返回不同的组件,
window.defaultRouteName正是Android中createView方法中的第三个参数route.

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

Widget _widgetForRoute(String defaultRouteName) {
  switch (defaultRouteName) {
    case 'route1':
      return Container(
        color: Colors.green,
        child: Column(
          children: <Widget>[
            Icon(
              Icons.accessible_forward,
              size: 100,
              textDirection: TextDirection.ltr,
            ),
            Text('defaultRouteName:$defaultRouteName',
                textDirection: TextDirection.ltr)
          ],
        ),
      );
      break;
    default:
      return Column(
        children: <Widget>[
          Icon(
            Icons.accessible_forward,
            size: 100,
            textDirection: TextDirection.ltr,
          ),
          Center(
              child: Text('default,defaultRouteName:$defaultRouteName',
                  textDirection: TextDirection.ltr)),
        ],
      );
      break;
  }
}

然后我们在mainActivity中加入一个跳转到第二个activity的方法:
记得在xml中加入这个按钮

public class MainActivity extends AppCompatActivity {
    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, SecondActivity.class));
            }
        });
    }
}

让Flutter模块支持热加载

首先在Flutter目录下启动监听服务,在你的项目根目录/flutter下执行

flutter attach

执行后,监听服务会等待并监听debug应用中flutter的状态
然后在打开FlutterInAndroid项目的AS中以正常方式调试运行,在真机或模拟器中运行app后并不会立即出发flutter的监听服务,当flutter的view或Fragment激活时才会触发。
当flutter的监听服务和app建立连接后,终端会出现如下输出:

Waiting for a connection from Flutter on ONEPLUS A5000...
Done.
Syncing files to device ONEPLUS A5000...                         1,782ms

�  To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on ONEPLUS A5000 is available at: http://127.0.0.1:13379/
For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".

这时我们修改flutter工程中的dart代码文件,保存后在终端中点击r键即可进行热加载,R键进行热重启。

运行

此时启动Android项目 , 跳转到SecondActivity时 会看到有黑屏 但是release包就没有这种情况了.

更改flutter代码时, 在 flutter窗口的下面输入R即可实时刷新页面

在同一个页面显示android组件和flutter组件

如果想要实现这个需求.我们需要稍微修改一下代码.

还记得我们SecondActivity里的setContentView(R.layout.activity_second)吗?
可以在R.layout.activity_second布局文件中直接写一个组件:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondActivity">

    <RelativeLayout
        android:id="@+id/relative"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:ignore="MissingConstraints">

        <TextView
            android:id="@+id/hello_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </RelativeLayout>

</android.support.constraint.ConstraintLayout>

如上所示, 我加入了一个RelativeLayout和一个Textview,
然后改变一下我们加入flutter view的方式:


public class SecondActivity extends AppCompatActivity {

    TextView textView;
    RelativeLayout relativeLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        textView = findViewById(R.id.hello_text);
        relativeLayout = findViewById(R.id.relative);


        View flutterView = Flutter.createView(this, getLifecycle(), "route1");
        RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);

        layout.addRule(RelativeLayout.BELOW, R.id.hello_text);
        relativeLayout.addView(flutterView, layout);


    }
}

我们先获取到了textViewrelativeLayout ,
之后创建flutter view. 在布局参数那里, 我们设置成flutter view显示在TextView的下面, 然后再添加到布局中就好了.

解决黑屏问题

如果这样写,在跳转到含有flutter View的Activity中时, debug模式下会有明显的黑屏问题 .
release模式下几乎不可见,但低端手机上首次加载可能会有感知.
这是为什么呢?

主要是因为flutter view加载比较慢, 在它加载出来之前,它所处的区域会显示为黑色 . 那么我们怎么处理呢?

如上代码, 我将flutter添加到了relativeLayout中,我们可以在flutter view加载完成前隐藏这个layout, 在加载成功之后才显示出来, 看一下代码:

public class SecondActivity extends AppCompatActivity {

    TextView textView;
    RelativeLayout relativeLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        textView = findViewById(R.id.hello_text);
        relativeLayout = findViewById(R.id.relative);

        Intent i = getIntent();
        String r = i.getStringExtra("name");
        FlutterView flutterView = Flutter.createView(this, getLifecycle(), r);
        relativeLayout.addView(flutterView);

        final FlutterView.FirstFrameListener[] listeners = new FlutterView.FirstFrameListener[1];
        listeners[0] = () -> relativeLayout.setVisibility(View.VISIBLE);

        flutterView.addFirstFrameListener(listeners[0]);
    }


}

activity_second.xml:

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

    <TextView
        android:id="@+id/hello_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <RelativeLayout
        android:id="@+id/relative"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="invisible">


    </RelativeLayout>

</LinearLayout>

在RelativeLayout中, 添加了android:visibility="invisible" 默认隐藏 ,

        listeners[0] = () -> relativeLayout.setVisibility(View.VISIBLE);

可以写做:

        listeners[0] = new FlutterView.FirstFrameListener() {
            @Override
            public void onFirstFrame() {
                relativeLayout.setVisibility(View.VISIBLE);
            }
        };

意思是在渲染完第一帧之后才显示relativeLayout 这样就将黑屏的时间过渡掉了.

数据传递

涉及到EventChannel和MethodChannel.

  • EventChannel:是native发送给flutter的数据用的
  • MethodChannel: 是native接收flutter数据用的

在Android中添加:
写在调用flutter组件的activity的oncreate中:

//定义好通道的名字
private static final String BatteryLevelChannel = "samples.flutter.io/battery";
private static final String sendToFlutter = "samples.flutter.io/sendToFlutter";


...
//native接收flutter数据
        new MethodChannel(flutterView, BatteryLevelChannel).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                // methodCall.method 对应 Flutter端invokeMethod方法的第一个参数
                if (methodCall.method.equals("123")) {
                    // 获取Flutter传递的参数
                    String msg = methodCall.argument("msg");
                    // 回传给Flutter
                    result.success(msg);
                }
            }
        });

        //native发送给flutter的数据
        new EventChannel(flutterView, sendToFlutter).setStreamHandler(new EventChannel.StreamHandler() {

            @Override
            public void onListen(Object o, EventChannel.EventSink eventSink) {
                eventSink.success("原生来的数据");
            }

            @Override
            public void onCancel(Object o) {
                // 做一些注销操作
            }
        });

Activity代码:

package com.example.androidwithflutterproject;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;

import io.flutter.facade.Flutter;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.view.FlutterView;


public class SecondActivity extends AppCompatActivity {

    TextView textView;
    RelativeLayout relativeLayout;
    private static final String BatteryLevelChannel = "samples.flutter.io/battery";
    private static final String sendToFlutter = "samples.flutter.io/sendToFlutter";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        textView = findViewById(R.id.hello_text);
        relativeLayout = findViewById(R.id.relative);


        Intent i = getIntent();
        String r = i.getStringExtra("name");
        FlutterView flutterView = Flutter.createView(this, getLifecycle(), r);
        relativeLayout.addView(flutterView);

        final FlutterView.FirstFrameListener[] listeners = new FlutterView.FirstFrameListener[1];
        listeners[0] = new FlutterView.FirstFrameListener() {
            @Override
            public void onFirstFrame() {
                relativeLayout.setVisibility(View.VISIBLE);
            }
        };

        flutterView.addFirstFrameListener(listeners[0]);

        //native接收flutter数据
        new MethodChannel(flutterView, BatteryLevelChannel).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                // methodCall.method 对应 Flutter端invokeMethod方法的第一个参数
                if (methodCall.method.equals("123")) {
                    // 获取Flutter传递的参数
                    String msg = methodCall.argument("msg");
                    // 回传给Flutter
                    result.success(msg);
                }
            }
        });

        //native发送给flutter的数据
        new EventChannel(flutterView, sendToFlutter).setStreamHandler(new EventChannel.StreamHandler() {

            @Override
            public void onListen(Object o, EventChannel.EventSink eventSink) {
                eventSink.success("原生来的数据");
            }

            @Override
            public void onCancel(Object o) {
                // 做一些注销操作
            }
        });
    }


}

flutter中的代码就简单了

import 'dart:async';
import 'dart:ui';

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

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

Widget _widgetForRoute(String defaultRouteName) {
  switch (defaultRouteName) {
    case 'route1':
      return MyApp();
      break;
    default:
      return Column(
        children: <Widget>[
          Icon(
            Icons.accessible_forward,
            size: 100,
            color: Colors.green,
            textDirection: TextDirection.ltr,
          ),
          Center(
              child: Text('default,defaultRouteName:$defaultRouteName',
                  style: TextStyle(color: Colors.blue),
                  textDirection: TextDirection.ltr)),
        ],
      );

      break;
  }
}

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> {
  int _counter = 0;
  static const platform = const MethodChannel('samples.flutter.io/battery');
  static const sendToFlutter =
      const EventChannel("samples.flutter.io/sendToFlutter");

  String _batteryLevel = 'Unknown battery level.';
  StreamSubscription _timerSubscription = null;

  Future sendToNative() async {
    String batteryLevel =
        await platform.invokeMethod('123', {'msg': '来自flutter的数据'});
    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

  void getDataFromNative() {
    _timerSubscription = sendToFlutter.receiveBroadcastStream().listen((msg) {
      setState(() {
        _batteryLevel = msg;
      });
    }); // 添加监听
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              onPressed: sendToNative,
              child: Text('发送给Native数据并接收回调参数'),
            ),
            Text(_batteryLevel),
            RaisedButton(
              onPressed: getDataFromNative,
              child: Text('接收Native的数据'),
            ),
          ],
        ),
      ),
     
    );
  }
}

猜你喜欢

转载自blog.csdn.net/u011272795/article/details/88839587