文章

Flutter Module

Flutter Module

Flutter Module

命令创建 Flutter module

命令:flutter create -t module flutter_module
yjwlm

  • flutter module 也是可以跑到手机上的
  • flutter 中的 .android.ios 是自动生成的,app 的是没有 .

add to android app(添加到已有Android App)

集成 flutter module 到已有的 Android App 有两种方式:

  1. Android AAR 依赖
  2. 源码依赖

AAR方式引入

  1. 执行命令创建 flutter module:flutter create -t module flutter_module
  2. 进入到 flutter_module 目录
  3. 执行命令:flutter build aar

p9gby

  1. 构建后的产物
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
build/host/outputs/repo
└── com
    └── example
        └── flutter_module
            ├── flutter_release
               ├── 1.0
                  ├── flutter_release-1.0.aar
                  ├── flutter_release-1.0.aar.md5
                  ├── flutter_release-1.0.aar.sha1
                  ├── flutter_release-1.0.pom
                  ├── flutter_release-1.0.pom.md5
                  └── flutter_release-1.0.pom.sha1
               ├── maven-metadata.xml
               ├── maven-metadata.xml.md5
               └── maven-metadata.xml.sha1
            ├── flutter_profile
               ├── ...
            └── flutter_debug
                └── ...
  1. 集成到 App

flutter_module 是以 submodule 的方式集成到 App 中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// root build.gradle
allprojects {
	repositories {
        // Flutter maven
        String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
        maven {
            url "$rootDir/flutter/KingOfFlutter/flutter_module/build/host/outputs/repo"
        }
        maven {
            url "$storageUrl/download.flutter.io"
        }
    }
}

// app build.gradle
android {
    // ...
    buildTypes {
        profile {
        	initWith debug
        }
    }
}
dependencies {
  // ...
  debugImplementation 'com.example.flutter_module:flutter_debug:1.0'
  profileImplementation 'com.example.flutter_module:flutter_profile:1.0'
  releaseImplementation 'com.example.flutter_module:flutter_release:1.0'
}

注意:Important: If you’re located in China, use a mirror site rather than the storage.googleapis.com domain. To learn more about mirror sites, check out Using Flutter in China page.

  • Android Studio using the Build > Flutter > Build AAR menu.

56vwv

依赖模块源码引入

确定 flutter 模块的位置

首先需要确认 Flutter-Module 依赖库文件夹的位置,简单来说,这里有两种方式:

  • 创建在项目的根目录下(内部);
  • 创建和项目文件夹的同一层级(外部),这也是官方推荐的方式。

引入步骤

  1. 命令:flutter create -t module flutter_module

AS 创建 Flutter module, flutter_module,会自动添加依赖

  1. 在 settings.gradle 中添加
1
2
3
4
5
6
7
8
9
10
11
12
13
// Flutter集成到Android项目中
// 如果flutter_module模块是创建在项目内部
setBinding(new Binding([gradle: this]))
evaluate(new File(settingsDir.path, 'flutter_module/.android/include_flutter.groovy'))
// 如果是项目内部引入flutter模块,还需要引入flutter_module进来
include ':flutter_module'

// 如果flutter_module模块在项目外部,和项目平级
//setBinding(new Binding([gradle: this]))
//evaluate(new File(
//        settingsDir.parentFile,
//        'module_flutter/.android/include_flutter.groovy'
//))
  1. 如果 Android 项目的主 module 名称不是 app,而是改成了其他的名称:,则还需要在 project 级别的 gradle.properties 中添加如下代码:

flutter.hostAppProjectName = app_module_name

  1. 在对应的 Android module 依赖 flutter 模块

在 app 主模块的 build.gradle 的 dependencies 中加入依赖库

implementation project(‘:flutter’)

集成代码

add a single Flutter screen

Add a normal Flutter screen

Flutter 提供了 FlutterActivity 用来展示 Flutter 的界面

  1. Add FlutterActivity to AndroidManifest.xml
1
2
3
4
5
6
7
<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
/>

@style/LaunchTheme 可替换成你想要的

  1. Launch FlutterActivity

假定 Dart entrypoint 是 main(),initial route 初始化路由是 /

1
2
3
4
5
myButton.setOnClickListener {
  startActivity(
    FlutterActivity.createDefaultIntent(this)
  )
}

如果需要渲染自定义的 initial route 初始化路由:

1
2
3
4
5
6
7
8
myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withNewEngine()
      .initialRoute("/my_route")
      .build(this)
  )
}

Flutter main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void main() => runApp(const MyApp());

Map<String, WidgetBuilder> routes = {
  "/": (context) => const MyHomePage(title: "Flutter Demo Home Page by /."),
  "/my_route": (context) => const MyHomePage(title: "my_route Flutter Demo Home Page by my_route."),
};

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // home: const MyHomePage(title: 'Flutter Demo Home Page'),
      routes: routes,
      initialRoute: "/", //名为"/"的路由作为应用的home(首页)
    );
  }
}

通过 withNewEngine() 配置的 FlutterActivity 会创建自己的 FlutterEngine,这意味着启动一个标准的 FlutterActivity 会在界面可见时出现一短暂的延迟,可以选择使用预缓存的 FlutterEngine 来减小其延迟,实际上在内部会先检查是否存在预缓存的 FlutterEngine,如果存在则使用该 FlutterEngine,否则继续使用非预缓存的 FlutterEngine

Use a cached FlutterEngine

  • 每个 FlutterActivity 创建自己的 FlutterEngine,创建需要花费不少时间;每创建一个 FlutterActivity 都需要一定的时间才能看到
  • 可以预先加载 FlutterEngine,在 Application onCreate 中预热
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine

  override fun onCreate() {
    super.onCreate()

    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)

    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )

    // Cache the FlutterEngine to be used by FlutterActivity.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}

myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withCachedEngine("my_engine_id")
      .build(this)
  )
}

初始化路由带 cache engine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine
  override fun onCreate() {
    super.onCreate()
    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)
    // Configure an initial route.
    flutterEngine.navigationChannel.setInitialRoute("your/route/here");
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )
    // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}

Add a translucent Flutter screen

1
2
3
4
5
6
7
8
9
10
11
<style name="MyTheme" parent="@style/MyParentTheme">
  <item name="android:windowIsTranslucent">true</item>
</style>

<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"
/>

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Using a new FlutterEngine.
startActivity(
    FlutterActivity
        .withNewEngine()
        .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
        .build(this)
);

// Using a cached FlutterEngine.
startActivity(
    FlutterActivity
        .withCachedEngine("my_engine_id")
        .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
        .build(this)
);

add a Flutter Fragment

FlutterFragment

全新的 FlutterEngine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class FlutterFragmentDemoActivity : AppCompatActivity() {

    companion object {
        // Define a tag String to represent the FlutterFragment within this
        // Activity's FragmentManager. This value can be whatever you'd like.
        private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment"
    }

    // Declare a local variable to reference the FlutterFragment so that you
    // can forward calls to it later.
    private var flutterFragment: FlutterFragment? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Inflate a layout that has a container for your FlutterFragment. For
        // this example, assume that a FrameLayout exists with an ID of
        // R.id.fragment_container.
        setContentView(R.layout.fragment_flutter_demo)

        // Get a reference to the Activity's FragmentManager to add a new
        // FlutterFragment, or find an existing one.
        val fragmentManager: FragmentManager = supportFragmentManager

        // Attempt to find an existing FlutterFragment, in case this is not the
        // first time that onCreate() was run.
        flutterFragment = fragmentManager
            .findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?

        // Create and attach a FlutterFragment if one does not exist.
        if (flutterFragment == null) {
            val newFlutterFragment = FlutterFragment.createDefault()
            flutterFragment = newFlutterFragment
            fragmentManager
                .beginTransaction()
                .add(
                    R.id.flutter_fragment_container,
                    newFlutterFragment,
                    TAG_FLUTTER_FRAGMENT
                )
                .commit()
        }
    }

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

    override fun onNewIntent(@NonNull intent: Intent) {
        super.onNewIntent(intent)
        flutterFragment!!.onNewIntent(intent)
    }

    override fun onBackPressed() {
        flutterFragment!!.onBackPressed()
    }

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

    override fun onActivityResult(
        requestCode: Int,
        resultCode: Int,
        data: Intent?
    ) {
        super.onActivityResult(requestCode, resultCode, data)
        flutterFragment!!.onActivityResult(
            requestCode,
            resultCode,
            data
        )
    }

    override fun onUserLeaveHint() {
        flutterFragment!!.onUserLeaveHint()
    }

    override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
        flutterFragment!!.onTrimMemory(level)
    }
}

pre-warmed FlutterEngine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
val flutterEngine = FlutterEngine(context)

// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
    DartEntrypoint.createDefault()
)

// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
  .getInstance()
  .put("my_engine_id", flutterEngine)

FlutterFragment.withCachedEngine("my_engine_id").build()

Initial route with a cached engine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine
  override fun onCreate() {
    super.onCreate()
    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)
    // Configure an initial route.
    flutterEngine.navigationChannel.setInitialRoute("your/route/here");
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )
    // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}

Control FlutterFragment’s render mode

Display a FlutterFragment with transparency

add a Flutter View

use Flutter engine

Ref

https://mp.weixin.qq.com/s/ceoiYiICn1mCOd7yW1CT0g

三方

flutter_booster

本文由作者按照 CC BY 4.0 进行授权