Flutter集成Android项目

在现有项目上集成Flutter

1.在andorid工程目录上级目录下执行命令:

flutter create -t module xxxx(想要创建的flutter项目名)

执行完后,一个和Android项目平级的flutter项目就已经创建好了。

2.添加flutter到当前Android项目:
在Android项目根目录下的settings.gradle文件中,添加如下代码:

setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        "flutter_hybrid/.android/include_flutter.groovy"
))

然后,在项目的app目录下,build.gradle文件中,添加如下dependency:

implementation project(':flutter')

如上,添加完之后,准备工作就完成了。

集成Flutter页面跳转

通过继承FlutterActivity跳到默认页面

1.Application:初始化Flutter

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        FlutterMain.startInitialization(this);
    }
}

2.Activity:继承FlutterActivity

/**
 * debug模式原生跳转到flutter界面会出现白屏,release包就不会出现白屏了
 */
public class MainFlutterActivity extends FlutterActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
  }

这样以后,我们就可以跳转这个MainFlutterActivity,实现在Android工程里面进入Flutter工程的默认页面了。

通过FlutterView跳到指定页面

首先通过Flutter.createFlutterView方法来创建一个FlutterView类型的View。它有三个构造参数:

public static FlutterView createView(Activity activity, Lifecycle lifecycle, String initialRoute)

三个参数分别是:
1.Activity,即当前附着的Activity

2.LifeCycle,且是不能为空(NonNull)的,这也就要求我们必须用AppCompatActivity来承载FlutterView了,如果我们的Activity继承自android.app.Activity是没有getLifeCycle()这个方法的。

public class FlutterViewActivity extends AppCompatActivity 复制代码

3.initialRoute这是一个String类型的变量,根据不同的标识跳转不同的Flutter页面:

Widget _widgetForRoute(String route) {
  switch (route) {
    case 'route1':
      return  MyHomePage(title: 'Flutter Demo Home Page1');
    case 'route2':
      return  MyHomePage(title: 'Flutter Demo Home Page2');
    default:
        return  MyHomePage(title: 'Flutter Demo Home Page2');
  }
}

然后在当前Activity的OnCreate方法中,执行:

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

//或者:

setContentView(R.layout.activity_flutter);
rlContainer = findViewById(R.id.rl_container);
rlContainer.addView(flutterView)

集成到现有项目遇到的坑

Check failed: vm. Must be able to initialize the VM:

E/flutter: [ERROR:flutter/runtime/dart_vm_data.cc(19)] VM snapshot invalid and could not be inferred from settings.
    [ERROR:flutter/runtime/dart_vm.cc(241)] Could not setup VM data to bootstrap the VM from.
    [ERROR:flutter/runtime/dart_vm_lifecycle.cc(89)] Could not create Dart VM instance.
 A/flutter: [FATAL:flutter/shell/common/shell.cc(218)] Check failed: vm. Must be able to initialize the VM.

SIGABRT
    0x146b
    #00    pc 0002210c    /system/lib/libc.so (tgkill+12) [armeabi-v7a::ac712d92afdda143205c7d7c8befc336]
    #01    pc 00013165    /system/lib/libc.so (pthread_kill+48) [armeabi-v7a::ac712d92afdda143205c7d7c8befc336]
    #02    pc 00013379    /system/lib/libc.so (raise+10) [armeabi-v7a::ac712d92afdda143205c7d7c8befc336]
    #03    pc 000120a3    /system/lib/libc.so [armeabi-v7a::ac712d92afdda143205c7d7c8befc336]
    #04    pc 000219c0    /system/lib/libc.so (abort+4) [armeabi-v7a::ac712d92afdda143205c7d7c8befc336]
    #05    pc 00af85ab    /data/app-lib/xxx.debug-1/libflutter.so [armeabi-v7a::a12434e0b53806a35730000001000000]

经github上#24679及其他issue参考,得出以下解决方案:

Import flutter module to your HOST app(set binding, implementation, and instantiate view/fragment)
When you clean/rebuild from HOST app, flutter module does not generate properly
You need to open your flutter module in a separate AS
flutter clean
cd .android
./gradlew clean
./gradlew assembleDebug
Return to host app and run app(do not sync or rebuild) :)

翻译过来就是:

本项目执行清理命令。./gradlew clean
进入 flutter module 项目执行清理命令。flutter packages get;flutter clean
进入 flutter module 的 .android 项目执行清理命令和打包操作。./gradlew clean;./gradlew assemble
回到本项目执行打包命令。./gradlew assemble

直接执行下面命令即可:

$ ./gradlew clean;cd ../flutter_module;flutter packages get;flutter clean;cd .android/;./gradlew clean;./gradlew assemble;cd /d/workspace/app_proj/;./gradlew installDebug

后续假设你 flutter module 没有更新过,那么以后修改本地项目之后,就直接执行./gradlew installDebug。 注意不要用studio工具运行、rebuild或clean。

flutter_assets

assets下缺少flutter_assets也有可能造成此问题。具体可以参考:https://www.jianshu.com/p/2d0bba84e57e

混淆

按照上述方式,debug版本没问题,release却报错。需加以下混淆:

-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
-keep class myapp.flutter.** { *; }

交互

Flutter 调用Android

java

new MethodChannel(flutterView, FlutterToAndroidCHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {

        //接收来自flutter的指令withoutParams
        if (methodCall.method.equals("withoutParams")) {

            //跳转到指定Activity
            Intent intent = new Intent(NativeActivity.this, NativeActivity.class);
            startActivity(intent);

            //返回给flutter的参数
            result.success("success");
        }
        //接收来自flutter的指令withParams
        else if (methodCall.method.equals("withParams")) {

            //解析参数
            String text = methodCall.argument("flutter");

            //带参数跳转到指定Activity
            Intent intent = new Intent(NativeActivity.this, NativeActivity.class);
            intent.putExtra("test", text);
            startActivity(intent);

            //返回给flutter的参数
            result.success("success");
        } else {
            result.notImplemented();
        }
    }
});

dart

Future<Null> _jumpToNative() async {
  String result = await toAndroidPlugin.invokeMethod('withoutParams');

  print(result);
}


Future<Null> _jumpToNativeWithParams() async {

  Map<String, String> map = { "flutter": "这是一条来自flutter的参数" };

  String result = await toAndroidPlugin.invokeMethod('withParams', map);

  print(result);
}

Android 向 Flutter 传参

java

new EventChannel(flutterView, AndroidToFlutterCHANNEL)
            .setStreamHandler(new EventChannel.StreamHandler() {
                @Override
                public void onListen(Object o, EventChannel.EventSink eventSink) {
                    String androidParmas = "来自android原生的参数";
                    eventSink.success(androidParmas);
                }

                @Override
                public void onCancel(Object o) {

                }
            });

dart

 void _startfromAndroiPlugin(){
   if(_fromAndroiSub == null){
     _fromAndroiSub =  fromAndroiPlugin.receiveBroadcastStream()
     .listen(_onfromAndroiEvent,onError: _onfromAndroiError);
   }
 }

void _onfromAndroiEvent(Object event) {
   setState(() {
     _nativeParams = event;
   });
 }

 void _onfromAndroiError(Object error) {
   setState(() {
     _nativeParams = "error";
     print(error);
   });
 }

有几处 要注意一一对应:

public static final String FlutterToAndroidCHANNEL = "com.litngzhe.toandroid/plugin"; 
public static final String AndroidToFlutterCHANNEL= "com.litngzhe.toflutter/plugin";

new MethodChannel(flutterView, FlutterToAndroidCHANNEL)
new EventChannel(flutterView, AndroidToFlutterCHANNEL) 


//dart中 获取到插件与原生的交互通道 MethodChannel 中 涉及到的方法名要要统一
static const toAndroidPlugin = const MethodChannel('com.litngzhe.toandroid/plugin');
static const fromAndroiPlugin = const EventChannel('com.litngzhe.toflutter/plugin'); 

参考资料

FlutterDemo
https://juejin.im/post/5c74dbeee51d45708f2a20c6
https://github.com/flutter/flutter/issues/24679
https://github.com/flutter/flutter/issues/25147
https://mp.weixin.qq.com/s/OGbH3G3wHVTUt-0EJit8RA