Android 集成 Flutter | 与交互

前言

使用 Flutter 已经有一段时间了,开发体验还是非常好的,但是一般我们在正式使用 Flutter 的时候很少会去创建一个纯 Flutter 项目,而是需要在之前的项目中已集成的方式来编写 Flutter。这篇文章将以如何在 Android 项目中集成 Flutter 和 如何在两者之间进行交互为主要内容。

在 Android 项目中集成 Flutter 项目

首先我们需要找一个 android 项目,以这个为基础来集成 Fluuter。下面来看一下具体的步骤

  1. 创建 flutter 模块

    在 AndroidStudio 的 Terminal 中使用如下命令

    flutter create -t module flutter_module
    

    其中 my_flutter 为模块名称。该命令完成后将会在项目目录中产生一个新的文件夹 flutter_module

    或者直接使用 AS 创建一个 Flutter Module也行。

  2. 将 两个项目放在一个文件夹下面

    这一步主要是为了方便管理,并且可以分开上传到 git,方便开发等。

    不在一个目录下也行。

  3. 执行 flutter build aar

    打开 Flutter 模块,执行 flutter build aar 命令。执行完后显示如下:

    image-20220113183440084
  4. 完成上面截图中的四项

    上面截图中的四个项目都需要在 android 代码中完成

    repositories {
       //...	
        maven { url 'D:\\android\\project\\example\\flutter_module\\build\\host\\outputs\\repo' }
        maven { url "https://storage.googleapis.com/download.flutter.io" }
    }
    

    新项目的 repositories 都需要配置在 setting.gradle 中。

    上面中的 url 就是 fluuter_modlue 的路径了。

    dependencies {
    	//.....	
        debugImplementation 'com.lv.example.flutter_module:flutter_debug:1.0'
        profileImplementation 'com.lv.example.flutter_module:flutter_profile:1.0'
        releaseImplementation 'com.lv.example.flutter_module:flutter_release:1.0'
    }
    
    buildTypes {
        profile {
            initWith debug
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    
  5. 同步项目

    同步一下项目,看有没有报错,如果有排查一下问题

  6. 添加 FlutterActivity

    AdnroidManifest.xml 中添加 FlutterActivity

    <activity
        android:name="io.flutter.embedding.android.FlutterActivity" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
        android:hardwareAccelerated="true"
        android:windowSoftInputMode="adjustResize" />
    
  7. 跳转

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
    
            findViewById<View>(R.id.start).setOnClickListener {
                startActivity(
                    FlutterActivity.createDefaultIntent(this)
                )
            }
        }
    }
    
  8. 效果如下

    345

Flutter 和 Android 的交互

Android 调起 Flutter 页面

在上面的代码中已经有打开 flutter 页面的代码了,如下所示:

startActivity(FlutterActivity.createDefaultIntent(this))

不过你运行代码,就会发现这种方式启动会非常慢,下面来看一种预初始化 Flutter 的方式

class MainActivity : AppCompatActivity() {
    private val flutterEngine by lazy { initFlutterEngine() };

    override fun onCreate(savedInstanceState: Bundle?) {
 	   ...//
        findViewById<View>(R.id.start).setOnClickListener {
            startActivity(
                FlutterActivity.withCachedEngine("default_engine_id").build(this)
            )
        }
    }

    private fun initFlutterEngine(): FlutterEngine {
        //创建 Flutter 引擎
        val flutterEngine = FlutterEngine(this)
        //指定要跳转的flutter页面
        flutterEngine.navigationChannel.setInitialRoute("main")
        flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
        //这里做一个缓存,可以在适当的时候执行它,例如app里,在跳转前执行预加载
        val flutterEngineCache = FlutterEngineCache.getInstance();
        flutterEngineCache.put("default_engine_id", flutterEngine)
        //上面代码一般在跳转之前调用,这样可以使得跳转树的加快
        return flutterEngine
    }
    
    override fun onDestroy() {
       super.onDestroy()
       flutterEngine.destroy()
    }
}

Flutter 代码如下:

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

Widget getRouter(String name) {
  switch (name) {
    case "main":
      return const MyApp();
    default:
      return MaterialApp(
        title: "Flutter Demo",
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Container(
          alignment: Alignment.center,
          child: Text("not font page $name}"),
        ),
      );
  }
}		

效果如下所示:

345

​ 可以发现,跳转的速度明显加快了许多。

需要注意的是,并不是修改了 fluuter_model 中的代码后重新运行 android 后页面就会发生改变,在 android 项目中,flutter 的代码是一个 aar 包的形式存在的,所以 flutter 代码更新后,需要重新执行 flutter build aar 命令重新打一个aar 包才可以。

当然这并不是说每次都要这样操作,在正常开发过程中,直接运行 flutter_module 即可。等到需要合起来的时候执行该命令即可。

当使用缓存的 FlutterEngine 时,FlutterEngine 比任何显示它的 FlutterActivity 或 FlutterFragment 的寿命都要长。请记住,Dart 代码在您预热 FlutterEngine 后立即开始执行,并在您的 FlutterActivity/FlutterFragment 销毁后继续执行。要停止执行并清除资源,请从 FlutterEngineCache 中获取 FlutterEngine 并使用 FlutterEngine.destroy() 销毁 FlutterEngine。

最后就是,如果要测试性能,请使用 release 版本

携参跳转 Flutter

如果在跳转的时候需要携带参数,只需要在 route 后面拼接上参数即可,如下所示:

flutterEngine.navigationChannel.setInitialRoute("main?{\"name\":\"345\"}")

这里将路由和参数使用 ? 隔开,参数使用 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);

通过上面代码即可拿到跳转的参数

以透明的方式启动 FlutterActivity
startActivity(
    FlutterActivity.withCachedEngine("default_engine_id")
        .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
        .build(this)
)
以半透明的方式启动 FlutterActivity

1,需要一个主题属性,用于呈现半透明效果

<style name="MyTheme" parent="@style/MyParentTheme">
  <item name="android:windowIsTranslucent">true</item>
</style>

2,将主题应用到 FlutterActivity 中

<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/MyTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />

这样 FlutterActivity 即可支持半透明

Android 嵌入 FlutterFragment

在 Android 页面中显示一个 FlutterFragment,基础操作如下:

class MainActivity : AppCompatActivity() {
    //定义一个标记字符串来表示其中的FlutterFragment 活动的FragmentManager。这个值可以是你想要的任何值。
    private val tagFlutterFragment = "flutter_fragment"
    private var flutterFragment: FlutterFragment? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val flutterEngine = initFlutterEngine()
        findViewById<View>(R.id.start).setOnClickListener {
            //尝试找到现有的FlutterFragment,以防这不是第一次运行onCreate()
            flutterFragment =
                supportFragmentManager.findFragmentByTag(tagFlutterFragment) as 			FlutterFragment?
            //创建 FlutterFragment
            if (flutterFragment == null) flutterFragment =
                FlutterFragment
                    .withCachedEngine("default_engine_id")
                    .build()
            //加载 FlutterFragment
            supportFragmentManager
                .beginTransaction()
                .add(R.id.layout, flutterFragment!!, tagFlutterFragment)
                .commit()
        }
    }

    private fun initFlutterEngine(): FlutterEngine {
        //创建 Flutter 引擎
        val flutterEngine = FlutterEngine(this)
        //指定要跳转的flutter页面
        flutterEngine.navigationChannel.setInitialRoute("main")
        flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
        //这里做一个缓存,可以在适当的时候执行它,例如app里,在跳转前执行预加载
        val flutterEngineCache = FlutterEngineCache.getInstance();
        flutterEngineCache.put("default_engine_id", flutterEngine)
        //上面代码一般在跳转之前调用,这样可以使得跳转树的加快
        return flutterEngine
    }

    override fun onPostResume() {
        super.onPostResume()
        flutterFragment?.onPostResume()
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        flutterFragment?.onNewIntent(intent)
    }

    override fun onBackPressed() {
        super.onBackPressed()
        flutterFragment?.onBackPressed()
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        flutterFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }

    override fun onUserLeaveHint() {
        super.onUserLeaveHint()
        flutterFragment?.onUserLeaveHint()
    }

    override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
        flutterFragment?.onTrimMemory(level)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        flutterEngine.destroy()
    }
}

上面代码直接是已初始化引擎的方式打开 FlutterFragmetn 的,这样的好处是加载更加块。

需要注意的是,如果要实现 Flutter 所有预期的行为,必须将这些信号转发到 FlutterFragment 中,这也就是上面为什么重新这么多方法的原因了。

从指定的入口点运行 FlutterFragment

与不同的初始路由类似,不同的flutterfragment可能希望执行不同的Dart入口点。在一个典型的Flutter应用程序中,只有一个Dart入口点:main(),但你可以定义其他入口点。

FlutterFragment 支持为给定的Flutter体验执行所需Dart入口点的规格。要指定一个入口点,请构建FlutterFragment,如下所示:

FlutterFragment.withNewEngine()
    .dartEntrypoint("newMain")
    .build()

FlutterFragment 会启动一个名字为 newMian 的入口点。

flutter 端的配置如下所示:

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

void newMain() => runApp(NewMainApp());

需要注意的是,必须配置在 main.dart 文件中。

当 FlutterFragment 使用缓存时, Dart 入口点属性无效,所以指定入口后无法使用缓存。

控制 FlutterFragment 的渲染模式

Flutter 可以使用 SufaceView 来渲染他的内容,也可以使用 TextureView

FlutterFragment 默认使用 SurfaceView。它的新能明显高于 TextureView,但是 SufaceView 不能再 Android View 层次结构中交叉,SurfaceView 必须是最下面的视图,或者是最上面的视图。

此外,在 Android N 之前的版本中,SurfaceView 不能使用动画,因为他们的布局渲染和 View 的层次结构的其他部分不同。

那么您需要使用 TextureView 而不是 SurfaceView。通过使用 RenderMode 构建 FlutterFragment 来选择 TextureView,如下所示:

val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .renderMode(FlutterView.RenderMode.texture)
    .build()
具有透明度的 FlutterFragment

默认情况下,FlutterFragment 使用 SurfaceView 呈现不透明背景。 对于任何不是由 Flutter 绘制的像素,该背景都是黑色的。出于性能原因,使用不透明背景渲染是首选渲染模式。在 Android 上具有透明度的 Flutter 渲染会对性能产生负面影响。但是,有许多设计需要在 Flutter 体验中显示透明像素,这些像素会显示到底层 Android UI。因此,Flutter 在 FlutterFragment 中支持半透明

SurfaceView 和 TextureView 都支持透明度。但是,当 SurfaceView 被指示以透明方式呈现时,它会将自己定位在比所有其他 Android 视图更高的 z-index 上,这意味着它会出现在所有其他视图之上。这是 SurfaceView 的限制。如果在所有其他内容之上渲染您的 Flutter 体验是可以接受的,那么 FlutterFragment 的表面默认 RenderMode 就是您应该使用的 RenderMode。但是,如果您需要在 Flutter 体验的上方和下方显示 Android 视图,则必须指定的 RenderMode.texture。有

使用方式如下:

FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    .transparencyMode(FlutterView.TransparencyMode.transparent)
    .build();
FlutterFragment及其Activity之间的关系

有些应用选择使用Fragments作为整个Android屏幕。在这些应用中,用Fragment来控制系统chrome是合理的,比如Android的状态栏、导航栏和方向。

在其他应用程序中,片段仅用于表示 UI 的一部分。 FlutterFragment 可用于实现抽屉、视频播放器或单张卡片的内部。在这些情况下,FlutterFragment 影响 Android 的系统 chrome 是不合适的,因为在同一个 Window 中还有其他 UI 片段。

FlutterFragment 带有一个概念,可以帮助区分 FlutterFragment 应该能够控制其宿主 Activity 的情况,以及 FlutterFragment 应该只影响其自身行为的情况。为了防止 FlutterFragment 将其 Activity 暴露给 Flutter 插件,并防止 Flutter 控制 Activity 的系统 UI,请使用 FlutterFragment 的 Builder 中的 shouldAttachEngineToActivity() 方法,如下所示:

FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
    //此FlutterFragment是否应自动附加其Activity作为其FlutterEngine的控制面。
    .shouldAttachEngineToActivity(false)
    .build();
Flutter 和 Android 通信

在进行通信之前先介绍一下 Platform Channel ,他是 Flutter 和原生通信的工具,有三种类型:

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

在日常开发中最常用的也就是 MethodChannel 了,关于其他的两种可自行查阅网上的文章

Android 调用 Flutter 方法
val methodChannel =
    MethodChannel(flutterEngine.dartExecutor, "com.example.AndroidWithFlutter/native")

上面代码中定义了一个 MtthodChannel ,第一个参数是一个接口,是与 Flutter 进行通信的工具,第二个参数是 name,就是 channel 的名称(这个名称需要和 Flutter 中定义的一致)。

//调用 Flutter 方法
methodChannel.invokeMethod("flutterMethod","调用 Flutter 参数",object : MethodChannel.Result {
	override fun success(result: Any?) {
		Log.e("---345--->", "$result");
	}

	override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
		Log.e("---345--->", "调用Flutter失败");
	}

	override fun notImplemented() {}
	})
}

上面代码中调用了 Flutter 中名字为 flutterMethod 的方法,其中第一个参数为方法名字,第二个是参数,回调中是调用结果和是否调用成功。下面我们看一下 Flutter 中如何定义:

final _channel = const MethodChannel("com.example.AndroidWithFlutter/native");
@override
void initState() {
  super.initState();
  ///监听android端的调用
  _channel.setMethodCallHandler((call) async {
    switch (call.method) {
      case "flutterMethod":
        print("参数:${call.arguments}");
        break;
    }
    return "我是 Flutter 返回值";
  });
}

上面代码中监听 android 端的调用,接着根据方法名字判断是哪个方法即可。

需要注意的是,在调用 Flutter 的时候,即使没有打开页面,也能调用其方法,这是应为已经缓存过 flutterEngine 了,flutterEngine 中会直接执行 dart 代码,所以可以直接调用。但是如果在页面跳转的时候没有使用缓存。这个时候虽然显示调用成功了,但是跳转过去是拿不到对应的参数的,因为没有使用缓存,不是同一个对象,所以不行,这里需要注意一下。

Flutter 调用 Android 方法

flutter端代码:

void _incrementCounter() {
  //调用 Android 的 AndroidMethod 方法
  var result = _channel.invokeMapMethod("AndroidMethod", "调用 Android 参数");
  result.then((value) => print('Android 返回值 :$value'));
}

android 端代码:

methodChannel.setMethodCallHandler { call, result ->
    when (call.method) {
        "AndroidMethod" -> {
            result.success(mapOf("Android 返回值" to "\"我是Android\""))
        }
        else -> {
            result.success("我是Android,没招到对应方法")
        }
    }
}

这里需要注意的就是 flutter 调用 android 的时候限制了返回值必须为 map,这点需要注意一下;

Flutter 跳转 Android 页面

flutter 跳转 android 页面实际上使用的是 MethodChannel ,需要跳转的时候,flutter 调用一下 android,在 android 端执行跳转的逻辑即可,如下所示:

flutter 端代码:

void _incrementCounter() { 
  //打开原生页面
  _channel.invokeMapMethod("jumpToNative");
}

android 端代码:

//监听flutter调用 android
methodChannel.setMethodCallHandler { call, result ->
    when (call.method) {
        "AndroidMethod" -> {
            result.success(mapOf("Android 返回值" to "\"我是Android\""))
        }
        "jumpToNative" -> {
            //跳转登录页面
            startActivity(Intent(this, LoginActivity::class.java))
        }
        else -> {
            result.success("我是Android,没招到对应方法")
        }
    }
}

效果图如下所示:

345
页面返回传参的实现

实现方式和上面的差不多,也是借助 MethodChannel ,在页面返回的时候使用 channel 调用一下传入对应的参数即可。

内存使用情况

我们对项目使用 flutter 之后和未使用的时候做了一个内存观测,具体如下:

未引入 flutter module:

image-20220117141615819

引入 flutter module:

只启动一个缓存引擎:

image-20220117144933906

查看上面的图片,可以发现 未引入之前内存使用只有 55Mb 左右,而在初始化了 fluuter 引擎(Engine) 之后,内存瞬间到了 181Mb 。并且这还是初始化了单个的情况下。

下面看一下初始化多个会有什么影响:

    initFlutterEngine("init_one")
    initFlutterEngine("init_two")
    initFlutterEngine("init_three")

private fun initFlutterEngine(id: String): FlutterEngine {
    //创建 Flutter 引擎
    val flutterEngine = FlutterEngine(this)
    //指定要跳转的flutter页面
    flutterEngine.navigationChannel.setInitialRoute("main")
    flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
    //这里做一个缓存,可以在适当的时候执行它,例如app里,在跳转前执行预加载
    val flutterEngineCache = FlutterEngineCache.getInstance();
    flutterEngineCache.put(id, flutterEngine)
    //上面代码一般在跳转之前调用,这样可以使得跳转树的加快
    return flutterEngine
}

代码如上所示,下面看一下结果:

image-20220117145045833

可以看到,一共初始化了四个缓存,共使用了 355Mb。比之前使用一个多了 174Mb,平均每增加一个缓存就会增加 60Mb

通过上面的验证,可以得出,使用了 Flutter 之后,内存确实会增加很多,但是并不会造成内存压力。

通增加缓存引擎的对比,发现每次增加一个缓存引擎,就会增加 60Mb 左右。

总结一下:

一般情况下使用时没有问题的,但是需要注意的是初始化引擎的时候初始化一个即可。不能每次打开页面都重新进行初始化引擎。

推荐阅读

参考资料

https://docs.flutter.dev/development/add-to-app

如果本文有帮助到你的地方,不胜荣幸,如有文章中有错误和疑问,欢迎大家提出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Flutter前后台交互的实现方法有以下几种: 1. 使用http库进行网络请求:Flutter中有多个http库可供选择,如Dio、http、flutter_http等。开发者可以利用这些库发送HTTP请求到后台服务器,获取响应数据并进行处理。在前台,开发者可以使用这些库发送post、get等请求,后台收到请求后,进行处理并返回相应的结果。 2. 使用WebSocket进行实时通信:WebSocket是一种在单个TCP连接上进行全双工通信的协议,它可以在前后台之间建立持久连接,实现实时通信。开发者可以在Flutter中使用web_socket库建立WebSocket连接,前台可以通过WebSocket发送数据到后台服务器,并通过监听收到后台的实时响应和通知。 3. 使用平台通道进行原生方法调用:Flutter提供了Platform Channel机制,开发者可以通过与原生平台(如Android和iOS)进行通信,调用平台上的原生代码实现前后台交互。开发者可以在Flutter中通过MethodChannel、EventChannel等通道发送方法调用请求和接收平台传递过来的数据。 4. 使用第三方插件或SDK:有些后台服务提供商提供了与Flutter集成的插件或SDK,开发者可以直接使用这些插件或SDK与后台进行交互。比如Firebase提供了Firebase SDK,可以用于Flutter应用与Firebase后台进行数据交互、推送通知等。 5. 使用其他网络协议:根据具体需求,还可以使用其他网络协议进行前后台交互,比如使用TCP、UDP等协议。开发者可以根据项目的实际需求选择合适的网络协议进行前后台交互

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tʀᴜsᴛ³⁴⁵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值