【Flutter 混合开发】添加 Flutter 到 Android Activity

Flutter 混合开发系列 包含如下:

  • 嵌入原生View-Android
  • 嵌入原生View-iOS
  • 与原生通信-MethodChannel
  • 与原生通信-BasicMessageChannel
  • 与原生通信-EventChannel
  • 添加 Flutter 到 Android Activity
  • 添加 Flutter 到 Android Fragment
  • 添加 Flutter 到 iOS

每个工作日分享一篇,欢迎关注、点赞及转发。

创建 Flutter Module

Flutter可以以源代码或AAR的方法嵌入到Android原生项目,集成流程可以使用 Android Studio 完成,也可以手动完成。强烈建议使用 Android Studio。

首先创建一个 Android 项目,创建一个空的 Activity:

Android 项目创建成功后,使用Android Studio 添加Flutter模块,在Android原生项目中点击“File > New > New Module...”,创建 Flutter Module

注意:Android Studio 的版本3.5及以上,Flutter IntelliJ plugin版本42及以上。

在弹出的选择Module类型的对话框中选中Flutter Module,然后点击Next,

设置Flutter module的Project name、Flutter SDK等,点击Next:

设置Flutter module的包名,点击Finish:

编译完成后将在当前App目录下生成Flutter模块的代码,目录结构如下:

启动页加载 Flutter

将 Flutter 页面加载到 MainActivity(默认启动页) 中,修改 MainActivity :

package com.flutter.androidflutter

import io.flutter.embedding.android.FlutterActivity

class MainActivity : FlutterActivity()

你没有看错,只需让 MainActivity 继承 FlutterActivity 即可。

注意:FlutterActivity的包名是io.flutter.embedding.android.FlutterActivity

跳转到 Flutter 页面

MainActivity(默认启动页)添加一个按钮,点击后跳转到新的页面,此页面加载 Flutter ,MainActivity代码如下:

package com.flutter.androidflutter

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            startActivity(Intent(this,SecondFlutterActivity::class.java))
        }
    }
}

SecondFlutterActivity 代码如下:

package com.flutter.androidflutter

import io.flutter.embedding.android.FlutterActivity

class SecondFlutterActivity:FlutterActivity() 

AndroidManifest.xml 中注册此 Activity:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.flutter.androidflutter">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        ...
        <activity android:name=".SecondFlutterActivity"/>
    </application>

</manifest>
flutter_android_1

SecondFlutterActivity 只是继承了 FlutterActivity,这种情况下,也可以直接使用 FlutterActivity

startActivity(Intent(this, FlutterActivity::class.java))

或者:

startActivity(FlutterActivity.createDefaultIntent(this))

AndroidManifest.xml 中注册 FlutterActivity:

<activity android:name="io.flutter.embedding.android.FlutterActivity"/>

效果与上面是一样的。

FlutterActivity 会加载 Flutter Module 中 lib/main.dart 中 main 方法,如果有多个Flutter页面,如何指定跳转,比如现在有 OnePage Flutter 页面,OnePage 代码如下:

class OnePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Text('这是 One 页面'),
      ),
    );
  }
}

FlutterActivity 指定加载页面需要使用命名路由,MyApp 修改如下:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(

        primarySwatch: Colors.blue,
      ),
      routes: {
        'one_page':(context){
          return OnePage();
        },
        'two_page':(context){
          return TwoPage();
        }
      },
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

MainActivity 页面点击到 Flutter 页面,加载 OnePage 页面:

class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            startActivity(
                FlutterActivity
                    .withNewEngine()
                    .initialRoute("one_page")
                    .build(this)
            )
        }
    }
}

引擎缓存

加载 FlutterActivity 页面时明显看到一段时间的黑屏,这段时间主要是启动 Flutter 引擎(FlutterEngine),Flutter 引擎启动的时间在不同手机上不同,性能越好的手机越短。同时每一个 FlutterActivity 页面都会启动一个引擎,所以强烈建议不要在一个项目中创建多个 FlutterActivity(或者启动多个 FlutterActivity 实例),否则内存会越来越大,下面是每隔3秒创建一个 FlutterActivity 实例内存变化图:

为了减少 FlutterActivity 页面的延迟时间和多个 FlutterActivity 实例内存一直增长问题,我们可以使用 Flutter 引擎(FlutterEngine)缓存,在启动 FlutterActivity 前先启动 Flutter 引擎,然后使用缓存的引擎加载页面,通常将其放在 Application 中:

class MyApplication : Application() {
    lateinit var flutterEngine: FlutterEngine

    override fun onCreate() {
        super.onCreate()
        flutterEngine = FlutterEngine(this)
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )
        FlutterEngineCache
            .getInstance()
            .put("engine_id", flutterEngine)
    }

}

使用缓存的引擎:

startActivity(
    FlutterActivity
        .withCachedEngine("engine_id")
        .build(this)
)

在同一台手机上效果非常明显,黑屏时间大大减少,不过还是有一个短暂的黑屏。

这里要注意,使用缓存引擎时,其生命周期不在是 FlutterActivity(或者 FlutterFragment)的生命周期,而是整个 App 的生命周期(在Application 中的创建和销毁)。当然也可以提前销毁:

flutterEngine.destroy()

另外项目的 debug 和 release 版本对性能的影响非常大,如果要测试其性能一定在要 release 下测试

上面使用新的引擎可以指定 FlutterActivity(或者 FlutterFragment)配置初始路由,但使用缓存引擎时无法在 FlutterActivity(或者 FlutterFragment)配置初始路由,因为缓存引擎已经启动并运行,不过可以在启动缓存引擎时指定其初始路由:

flutterEngine = FlutterEngine(this)

flutterEngine.navigationChannel.setInitialRoute("one_page")

flutterEngine.dartExecutor.executeDartEntrypoint(
    DartExecutor.DartEntrypoint.createDefault()
)
FlutterEngineCache
    .getInstance()
    .put("engine_id", flutterEngine)

如果使用缓存引擎在FlutterActivity(或 FlutterFragment)指定不同路由,如何处理?这时需要创建一个 method channel,flutter 接收具体消息从而切换不同的路由。

交流

老孟Flutter博客(330个控件用法+实战入门系列文章):http://laomengit.com

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354