【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="//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個控制項用法+實戰入門系列文章)://laomengit.com

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

Tags: