oh, 我亲爱的朋友,很高兴你来到了这里!既然来了,那么就让我们在这篇糟糕的烂文章中,一起来学习一下,如何在一个糟糕的 Flutter 混合应用中开发一个糟糕的 Android Native 烂插件吧!😑
首先,先考虑第一个问题:混合开发中如何将Flutter集成到现有的Android应用中呢?
主要步骤:
- 首先,创建Flutter module;
- 为已存在的Android应用添加Flutter module依赖;
- 在Koltin中调用Flutter module;
- 编写Dart代码;
- 运行项目;
- 热重启/重新加载;
- 调试Dart代码;
- 发布应用;
在 Android Studio 中创建 Flutter module
在做混合开发之前我们首先需要创建一个Flutter module。
假如你的Native项目是这样的:xxx/flutter_hybrid/Native项目
$ cd xxx/flutter_hybrid/
// 创建 flutter_module
$ flutter create -t module flutter_module
// 如果需要指定包名
$ flutter create -t module --org com.example.xxx flutter_module
上面代码会切换到你的 Android/iOS
项目的上一级目录,并创建一个 flutter_module
模块。
打开 flutter_module
,查看其中的文件结构,你会发现它里面包含.android
与.ios
,这两个文件夹是隐藏文件,也是这个 flutter_module
宿主工程:
.android
:flutter_module 的Android宿主工程;.ios
:flutter_module 的iOS宿主工程;lib
:flutter_module 的Dart部分的代码;pubspec.yaml
:flutter_module 的项目依赖配置文件;
因为宿主工程的存在,我们这个
flutter_module
在不加额外的配置的情况下是可以独立运行的,通过安装了Flutter与Dart插件的Android Studio打开这个flutter_module
项目,通过运行按钮是可以直接运 行它的。
为已存在的 Android 应用添加 Flutter module 依赖
接下来就需要将创建的Flutter module依赖到我们Android的主工程,有如下两种方式可以依赖
方式一:构建 flutter aar(非必须)
如果你需要的话,可以通过如下命令来构建 flutter aar:
$ cd .android/
$ ./gradlew flutter:assembleRelease
这会在 .android/Flutter/build/outputs/aar/
中生成一个 flutter-release.aar
归档文件。
使用这种方式的好处是我们可以把自己生成的flutter aar上传到自己公司的Maven仓库中给别人使用,这样开发Flutter的人和开发Android原生代码的人就可以分开独立工作,各干各的,不用在同一个工程里面折腾。(但是假如你的公司中的app开发只有你一个人的话,那我只能 deeply sorry for that)
方式二:在settings.gradle添加依赖
打开我们Android项目的 settings.gradle
添如下代码:
include ':app' // 已存在
//for flutter
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'flutter_module/.android/include_flutter.groovy' // new
))
//可选,主要作用是可以在当前AS的Project下显示flutter_module以方便查看和编写Dart代码
include ':flutter_module'
project(':flutter_module').projectDir = new File('../flutter_module')
setBinding
与 evaluate
允许Flutter模块包括它自己在内的任何Flutter插件,在 settings.gradle
中以类似::flutter
、:video_player
的方式存在。
此时再同步一下项目。
宿主模块添加 :Flutter 依赖
在app module下的build.gradle
中添加:
dependencies {
implementation project(':flutter')
...
}
如果工程中很多地方都需要用到它,可以将其放到common module中添加。
添加 Java 8 编译选项
因为Flutter的Android engine使用了Java 8 的特性,所以在引入Flutter时需要配置你的项目的 Java 8 编译选 项:
在你的app的 build.gradle
文件的 android {}
节点下添加:
android {
// ...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
配置CPU架构
在app module下的build.gradle
中添加:
android {
// ...
defaultConfig {
// 配置Flutter支持的架构
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
}
}
}
注意:
- 从
Flutter v1.17
版本开始,Flutter module仅仅支持AndroidX
的应用。- 在
release
模式下Flutter仅支持以下架构:x86_64,armeabi-v7a,arm64-v8a
,不支持mips和x86;所以引入Flutter前需要选取Flutter支持的架构。
在 Koltin 中调用 Flutter module
至此,我们已经为我们的Android项目添加了Flutter所必须的依赖,接下来我们来看如何在项目上以Kotlin的方式在Fragment中调用Flutter模块。
在 Android 中调用 Flutter 模块的有两种方式:
- 1.使用 Flutter.createView API 方式创建 (作为页面的一部分)Flutter.createView() 已经被官方弃用 Flutter 1.12版本废弃了io.flutter.facade包导致的
- 2.使用 FlutterFragment.createDefault() 来创建
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.test).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//没有指定路由 传值;只能调到 默认路由(开始界面)
// FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
// fragmentTransaction.add(R.id.someContainer, FlutterFragment.createDefault());
// //fragmentTransaction.replace(R.id.someContainer, FlutterFragment.createDefault());
// fragmentTransaction.commit();
//指定路由并且传值
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.initialRoute("{name:'devio',dataList:['aa','bb','bb']}")
.build();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.someContainer, flutterFragment)
.commit();
}
});
}
}
原生传值可以直接接收到参数:
import 'dart:ui' as ui;
final String initParams = ui.window.defaultRouteName;
编辑的时候:
- 编辑原生原生代码只能修改原生的代码;
- 编辑dart 代码: Flutter_module lib 文件夹里面的代码;
直接运行 Flutter_module:只能运行 Flutter_module里面的工程;
也可以不用Flutter系统为我们准备的FlutterFragment
,自己新建一个Fragment处理:
abstract class HiFlutterFragment : HiBaseFragment() {
protected lateinit var flutterEngine: FlutterEngine
override fun onAttach(context: Context) {
super.onAttach(context)
flutterEngine = FlutterEngine(context)
//让引擎执行Dart代码
flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
}
}
在Fragment的布局中添加Flutter View:
其中有一点需要稍加解释一下,就是图中注释的部分,渲染FlutterView的两种方式,除了FlutterTextureView
之外,还有一个FlutterSurfaceView
:
但是这里不采用它的原因是由于当app切后台FlutterSurfaceView
是会有复用的问题,比如:
此时将app切到后台,然后再切到前台时,推荐页面的FlutterView会被复用到收藏页面的FlutterView上的,很显然是不对的,所以这一点需要明确。
为了能让Flutter感知到自己创建的Fragment的各个生命周期,所以需要重写一系列的生命周期方法,如下:
接下来在宿主项目中使用一下上面自定义的可以承载Flutter的Fragment,在宿主中找一个页面替换成Flutter view,例如将首页的推荐页面替换成Flutter页面:
其中有个title文本资源:<string name="title_recommend">精选推荐</string>
一切就绪后,运行看一下效果:
等于就是把官方的demo给嵌入到了咱们的推荐页面上了,至此,混编的第一步已经搭建好了。
热重启 /重启加载
在混合开发中 Android 项目集成 Flutter 项目的时候,如果发现重启/重新加载不起作用了,那在混合开发中怎么启用重启/重新加载呢:
- 手机连接我们的 电脑
- 关闭我们的App应用;然后运行
flutter attach
; (在对应的 flutter_module 项目根路径)
注意:如果你同时有多个模拟器或连接的设备,运行flutter attach
会提示你选择一个设备,接下来我们需要flutter attach -d 设备ID
来指定一个设备:如flutter attach -d emulator-5554
- 当出现 “Waiting for a connection from Flutter on PACM00…” 的时候打开我们原生App;并且进入我们的 Flutter 界面
然后会提示同步信息和 命令信息
D:\MineGit\flutter_trip\flutter_module_john>flutter attach
Multiple devices found:
SM G9650 (mobile) • 21a9f15c1d037ece • android-arm64 • Android 10 (API 29)
PACM00 (mobile) • JZU8PB9DQOG68D6D • android-arm64 • Android 10 (API 29)
[1]: SM G9650 (21a9f15c1d037ece)
[2]: PACM00 (JZU8PB9DQOG68D6D)
Please choose one (To quit, press "q/Q"): 2
Waiting for a connection from Flutter on PACM00...
Syncing files to device PACM00... 7.4s
Flutter run key commands.
r Hot reload.
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
Running with sound null safety
现在你只要 修改完毕 dart 代码保存;然后在 按 r 键就能立马看到效果了。
调试 dart 代码
在混合开发模式下, 如何更好的调试我们的代码:
- 关闭App(这一步很关键)
- 点击 Android Studio 的 Flutter Attch 按钮(前提是 安装过flutter 与 dart 插件)
- 打上断点,启动App,就能进入对应的断点 了
接下来就可以像调试普通Flutter项目一样来调试混合开发模式下的Dart代码了。
除了以上步骤不同之外,还有一点需要注意:在运行Android工程时,一定要在Android模式下的AndroidStuio中运行,因为Flutter模式下的AndroidStudio运行的是Flutter module下的.android中的Android工程。
复杂场景下的Flutter混合架构设计
通常Flutter混合设计是这样的形态:
也就是将要打开的某一个页面的整个页面使用Flutter View来实现。而复杂场景就是像下面这种:
也就是一个页面中既有原生View 又有 Flutter View。
为啥复杂呢?这是因为Flutter可以理解是一个单页面应用, 所以并不支持像这种一个页面中既有native又有flutter的场景。
优化:秒开Flutter模块
目前我们初步在推荐模块中集成的Flutter运行起来会比较慢,因为我们目前是在Fragment中每次都来初始化Flutter引擎,如下:
要实现秒开的效果,则需要使用预加载,但是预加载很显然会影响到首页加载的性能,所以如何让预加载不损失"首页"性能成了我们需要解决的问题,下面一个个来。
1、预加载逻辑实现:
新建一个单例类HiFlutterCacheManager
,并在其中初始化FlutterEngine
:
import android.content.Context
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.view.FlutterMain
/**
* Flutter优化提升加载速度,实现秒开Flutter模块
* 0.如何让预加载不损失"首页"性能
* 1.如何实例化多个Flutter引擎并分别加载不同的dart 入口文件
*/
class HiFlutterCacheManager private constructor() {
// 初始化FlutterEngine
private fun initFlutterEngine(
context: Context,
moduleName: String
): FlutterEngine {
// Instantiate a FlutterEngine.
val flutterEngine = FlutterEngine(context)
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint(
FlutterMain.findAppBundlePath(),
moduleName
)
)
// Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
FlutterEngineCache.getInstance().put(moduleName, flutterEngine)
return flutterEngine
}
companion object {
@JvmStatic
@get:Synchronized
var instance: HiFlutterCacheManager? = null
get() {
if (field == null) {
field = HiFlutterCacheManager()
}
return field
}
private set
}
}
其中可以看到,缓存的key
是moduleName
,刚好供之后具体模块的使用。
【预加载FlutterEngine
的核心逻辑】:
接下来就来提供一个预加载的方法,其中有一个小技巧值得学习:
/**
* 预加载FlutterEngine
*/
fun preLoad(
context: Context
) {
//在线程空闲时执行预加载任务,这样就不会和主线程进行争抢了,只有在主线程空闲时才会执行预加载
Looper.myQueue().addIdleHandler {
initFlutterEngine(context, MODULE_NAME_FAVORITE)
initFlutterEngine(context, MODULE_NAME_RECOMMEND)
false
}
}
获取预加载的FlutterEngine
:
/**
* 获取预加载的FlutterEngine
*/
fun getCachedFlutterEngine(moduleName: String, context: Context?): FlutterEngine? {
var engine = FlutterEngineCache.getInstance()[moduleName]
if (engine == null && context != null) {
engine = initFlutterEngine(context, moduleName)
}
return engine!!
}
2、调用HiFlutterCacheManager开启预加载:
在宿主的Application
当中调用HiFlutterCacheManager
的preLoad
方法预加载Flutter的Engine
:
import android.app.Application
import com.google.gson.Gson
import org.devio.`as`.proj.common.flutter.HiFlutterCacheManager
import org.devio.hi.library.log.HiConsolePrinter
import org.devio.hi.library.log.HiFilePrinter
import org.devio.hi.library.log.HiLogConfig
import org.devio.hi.library.log.HiLogConfig.JsonParser
import org.devio.hi.library.log.HiLogManager
open class HiBaseApplication : Application() {
override fun onCreate() {
super.onCreate()
initLog()
//Flutter 引擎预加载,让其Flutter页面可以秒开
HiFlutterCacheManager.instance?.preLoad(this)
}
private fun initLog() {
HiLogManager.init(object : HiLogConfig() {
override fun injectJsonParser(): JsonParser {
return JsonParser { src: Any? ->
Gson().toJson(src)
}
}
override fun includeThread(): Boolean {
return true
}
}, HiConsolePrinter(), HiFilePrinter.getInstance(cacheDir.absolutePath, 0))
}
}
3、在HiFlutterFragment中使用HiFlutterCacheManager:
4、修改RecommendFragment:
由于基类调整了,子类相应也得进行修改,如下:
5、改造FavoriteFragment:
为了看到效果,我们对收藏页面也进行相应的代码集成:
这样对于native的代码就已经改造完了,接下来则则可以来修改Flutter代码了。
6、修改flutter代码:
先找到flutter_module,对于flutter代码的编写可以切到project视图,找到它:
注意,它的出现,前提是一定要在这里进行配置这句话:
这样就省得在Android和Flutter之间的环境进行切换了,全在一个工程中都可以搞定了,还是非常有用的技巧。
在 flutter_module/lib
下面新建page
目录,在其中新建收藏和推荐两个页面的dart
文件:
修改main.dart
【重点】:
如何实例化多个Flutter引擎并分别加载不同的dart 入口文件呢?此时就需要回到main.dart文件中来添加支持了,原本Flutter只支持一个main.dart
入口的, 此时咱们要加载多个模块的dart入口,怎么办呢?此时就需要进行改造了:此时将MyApp
改为home
参数是可以动态更改的。
而我们在宿主中注册Flutter引擎时会提供指定的页面名称:
这里默认的值main
字符串就对应了main.dart
中的main()
方法,因此接下来就需要在main.dart
中再创建一个推荐页面的入口了,依葫芦画瓢:
但是!!!这样只是创建了一个recommend入口Flutter是不会加载它的, 需要向Flutter注册一下,具体方法如下:
主要是通过 @pragma('vm:entry-point')
这个注解指定多个入口。
Flutter与Native的通信机制
Flutter和Native的通信是通过Channel来完成的。
消息使用Channel(平台通道)在客户端(UI)和主机(平台)之间传递,如下图所示:
-
应用中的 Flutter 部分通过Platform Channel向其宿主 (非 Dart 部分) 发送消息。
-
宿主监听Platform Channel并接收消息。然后,它使用原生编程语言来调用任意数量的相关平台 API,并将响应发送回 Flutter 。
消息和响应以异步的形式进行传递,以确保用户界面能够保持响应。
Flutter 是通过 Dart 异步发送消息的。即便如此,当你调用一个平台方法时,也需要在主线程上做调用。
Flutter端在调用方法的时候 MethodChannel 会负责响应,从平台一侧来讲,Android 系统上使用 MethodChannelAndroid
、 iOS 系统使用 MethodChanneliOS
来接收和返回来自 MethodChannel
的方法调用。在开发平台插件的时候,可以减少样板代码。
Platform Channel支持的数据类型
标准平台通道使用标准消息编解码器,它支持简单的类似 JSON 值的高效二进制序列化,例如布尔值、数字、字符串、字节缓冲区及这些类型的列表和映射(详情请参阅 StandardMessageCodec)。当你发送和接收值时,它会自动对这些值进行序列化和反序列化。
以下是Platform Channel支持的kotlin
和Dart
的对应数据类型:
其他语言请参考 table from the Flutter documentation
Platform Channel 的三种分类
Flutter定义了三种不同类型的Channel来与原生通信:
Channel 类型 | 用途 | 交互方向 | 一个示例 |
---|---|---|---|
BasicMessageChannel | 低级消息,传递字符串和半结构化信息 | 双向 | 自定义编解码器 |
MethodChannel | 请求-响应(类似 RPC)类型的方法调用,传递方法调用 | 双向 | 调用本地代码 |
EventChannel | 事件驱动流,传递数据流信息 | 双向 | 订阅原生事件 |
这三种方式,无论是传递方法还是事件,本质上都是传递的数据。
具体使用哪种通信方式,要看使用场景,同一种功能可以通过不同的方式来实现。比如获取手机电量,网络变化等可以通过 EventChannel
,也能用 MethodChannel
。比如Flutter调用Native拍照可以用MethodChannel
。 如果要通过Flutter控制一个原生的视频播放组件,则最好通过MethodChannel
;如果要视频播放期间获取播放进度改变、当前网速变化等信息,则最好通过EventChannel
。
Flutter 与 Android 原生通信示例
以 Flutter 获取显示 Android 手机的电池电量为例,使用 MethodChannel
来实现。
1、Flutter 端的代码配置
设置 Flutter 端的通道比较简单,一共需要两步
- 第一步:生成一个 Flutter 和 Android 原生通信的通道
MethodChannel
; - 第二步:通过
MethodChannel
对象发起一次方法的调用invokeMethod
。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('samples.flutter.dev/battery');
// Get battery level.
MethodChannel
构造时需要传递一个name
名称,一个应用中所使用的所有通道名称必须是唯一的;因此官方建议为通道名称添加唯一的前缀,比如:samples.flutter.dev/battery
。
接下来,在MethodChannel
上通过invokeMethod
调用方法(通过指定 String
类型的 getBatteryLevel
字符串调用具体方法)。调用可能会失败,比如,如果平台不支持此平台 API(比如在模拟器中运行),所以需要将 invokeMethod
调用包裹在 try-catch
语句中。
// Get battery level.
String _batteryLevel = 'Unknown battery level.';
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
// 调用 setState 使用返回的 batteryLevel 来更新 _batteryLevel 的界面状态。
setState(() {
_batteryLevel = batteryLevel;
});
}
最后,将模板中的 build 方法替换为包含以字符串形式显示电池状态、并包含一个用于刷新该值的按钮的小型用户界面。
build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: _getBatteryLevel, // 点击按钮时调用上面的方法请求Android端
child: const Text('Get Battery Level'),
),
Text(_batteryLevel), // 显示电池状态
],
),
),
);
}
Widget
2、Android 端的代码配置
找到 MainActivity.kt
文件,在 MainActivity
中添加以下代码:
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery" // 确保与 Flutter 客户端使用的一致
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
// This method is invoked on the main thread.
// TODO
}
}
}
这里主要是在 configureFlutterEngine()
方法中创建一个 MethodChannel
并调用 setMethodCallHandler()
。确保使用的Channel名称与 Flutter 客户端使用的一致。
当使用特定的 Android Activity 实例初始化 Flutter Engine时会调用configureFlutterEngine
方法,因此 Flutter 推荐使用它来注册方法通道处理程序。
接下来添加使用 Android battery API 来检索电池电量的 Android Kotlin 代码。该代码与你在原生 Android 应用中编写的代码完全相同。
首先在文件头部添加所需的依赖:
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
然后在 MainActivity
类中的 configureFlutterEngine()
方法下方添加以下新方法:
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
最后,完成前面添加的 onMethodCall()
方法。你需要处理单个平台方法 getBatteryLevel()
,所以在参数 call
中对其进行验证。该平台方法的实现是调用上一步编写的 Android 代码,并使用 result
参数来返回成功和错误情况下的响应。如果调用了未知方法,则报告该方法。
删除以下代码:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
// This method is invoked on the main thread.
// TODO
}
并替换成以下内容:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
这里通过call.method
判断Dart发来的方法名称,你可以写多个分支,处理多个来自Dart的方法调用。
注意,这里处理未知的方法名最好方式是返回 result.notImplemented()
:
// ---
else {
result.notImplemented()
}
现在你应该可以在 Android 中运行该应用。如果使用了 Android 模拟器,请在扩展控件面板中设置电池电量,可从工具栏中的 … 按钮访问。
将数据从 Kotlin 返回到 Dart
通过前面的示例,你已经知道该如何做:result.success(arg)
,开发者无需做其他工作,只要传递的参数类型是Platform Channel所支持的数据类型(参考前文)。
将参数从 Dart 传递到 Kotlin
类似的,在 Dart 中MethodChannel
对象的invokeMethod
方法也可以传递额外的参数给平台。
以下是一个简单示例,通过MethodChannel
向Android端调用方法请求返回一个随机字符串:
// 该方法向Native平台调用getRandomString请求返回一个随机数字符串
Future<void> _generateRandomString() async {
String random = '';
try {
var arguments = {
'len': 3, // 随机字符串长度
'prefix': 'fl_', // 随机字符串的前缀
};
random = await platform.invokeMethod('getRandomString', arguments);
} on PlatformException catch (e) {
random = '';
}
setState(() {
_counter = random;
});
}
在 Kotlin 代码中获取Dart传来的参数:
if(call.method == "getRandomString") {
val limit = call.argument("len") ?: 4
val prefix = call.argument("prefix") ?: ""
val rand = ('a'..'z')
.shuffled()
.take(limit)
.joinToString(prefix = prefix, separator = "")
result.success(rand)
}
这里使用的主要是 call.argument("argName")
来获取参数值,注意可能获取失败,会返回null
, 因此提供了默认值。
如果你只需要传递一个方法参数,那么可以直接传,而不需要像上面代码那样搞一个动态Map:
// Dart:
random = await platform.invokeMethod('getRandomString', 3);
// Kotlin
val limit = call.arguments() ?: 4
val rand = ('a'..'z')
.shuffled()
.take(limit)
.joinToString("")
result.success(rand)
通过 Pigeon 获得类型安全的通道
在之前的样例中,我们使用 MethodChannel 在 host 和 client 之间进行通信,然而这并不是类型安全的。为了正确通信,调用/接收消息取决于 host 和 client 声明相同的参数和数据类型。 Pigeon 包可以用作 MethodChannel 的替代品,它将生成以结构化类型安全方式发送消息的代码。
在 Pigeon 中,消息接口在 Dart 中进行定义,然后它将生成对应的 Android 以及 iOS 的代码。更复杂的例子以及更多信息请查看 pigeon。
使用 Pigeon 消除了在主机和客户端之间匹配字符串的需要消息的名称和数据类型。它支持:嵌套类,消息转换为 API,生成异步包装代码并发送消息。生成的代码具有相当的可读性并保证在不同版本的多个客户端之间没有冲突。支持 Objective-C,Java,Kotlin 和 Swift(通过 Objective-C 互操作)语言。
Pigeon 样例:
import 'package:pigeon/pigeon.dart';
class SearchRequest {
final String query;
SearchRequest({required this.query});
}
class SearchReply {
final String result;
SearchReply({required this.result});
}
()
abstract class Api {
@async
SearchReply search(SearchRequest request);
}
Dart 用法:
import 'generated_pigeon.dart';
Future<void> onClick() async {
SearchRequest request = SearchRequest(query: 'test');
Api api = SomeApi();
SearchReply reply = await api.search(request);
print('reply: ${reply.result}');
}
错误处理策略
编程中有两种主要的错误处理策略:基于错误代码和基于异常。一些程序员通过混合使用两者来使用混合错误处理策略。
MethodChannel
内置支持为 Kotlin 端的错误流处理来自 Dart 的异常。此外,它还提供了一种方法来区分本机错误类型和异常实例中的错误代码。换句话说,MethodChannel
为 Flutter 开发人员提供了一种混合错误处理策略。
在前面的示例中,我们使用result.success
方法返回一个值并处理未知的方法调用,这将在 Dart 中抛出result.notImplementedMissingPluginException
。
如果我们需要从 Kotlin 端创建 Dart 异常怎么办?result.error
方法可帮助您从 Kotlin 中抛出一个 Dart 的 PlatformException
实例。假设如果我们在前面请求随机字符串的示例中,当请求的随机字符串长度为负值时,需要抛出异常,那么可以这样修改:
// kotlin
if(call.method == "getRandomString") {
val limit = call.arguments() ?: 4
if(limit < 0) {
result.error("INVALIDARGS", "String length should not be a negative integer", null)
}
else {
val rand = ('a'..'z')
.shuffled()
.take(limit)
.joinToString("")
result.success(rand)
}
}
接下来,在 Dart 端捕获异常并使用它,如下所示:
Future<void> _generateRandomString() async {
String random = '';
try {
random = await platform.invokeMethod('getRandomString', -5);
} on PlatformException catch (e) {
random = '';
print('PlatformException: ${e.code} ${e.message}');
}
setState(() {
_counter = random;
});
}
当您运行应用程序并按下操作按钮时,您将在终端上看到异常代码和消息,因为我们从 Dart 端传递了 -5
作为字符串长度:
正如我们在上面的示例中看到的,您可以在 Dart 中进行捕获PlatformException,并且可以在异常实例中查看错误代码以处理方法通道错误。另一种更抽象的方法是根据 Kotlin 错误代码创建您自己的异常实例。请参考 Flutter 的 camera plugin 中的 CameraException 实现。
使用 EventChannel
MethodChannel
与传统的 RESTful API 一样,该类提供基于请求-响应的通信解决方案。如果我们在使用 Web 应用程序时需要从服务器调用客户端怎么办?那么,我们倾向于选择像WebSockets这样的事件驱动的通信机制。EventChannel
类提供了一个异步事件流,用于在本机主机应用程序和 Flutter 之间构建事件驱动的通信线路。EventChannel
类主要用于将本机事件发送到 Dart 端。
例如,我们可以将系统主题更改事件从 Kotlin 调度到 Dart。此外,我们可以用EventChannel
来广播来自设备传感器的频繁事件。
下面是示例中,我们将通过一个EventChannel
实例来自动检测当前的系统颜色主题。
首先,将以下代码添加到MainActivity.kt
中:
package com.example.flutter_platform_channels_demo
import kotlin.random.Random
import androidx.annotation.NonNull
import android.os.Bundle
import android.content.res.Configuration
import android.content.pm.ActivityInfo
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
import io.flutter.plugin.common.EventChannel.StreamHandler
class MainActivity: FlutterActivity() {
var events: EventSink? = null
var oldConfig: Configuration? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
oldConfig = Configuration(getContext().getResources().getConfiguration())
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
EventChannel(flutterEngine.dartExecutor.binaryMessenger, "example.com/channel").setStreamHandler(
object: StreamHandler {
override fun onListen(arguments: Any?, es: EventSink) {
events = es
events?.success(isDarkMode(oldConfig))
}
override fun onCancel(arguments: Any?) {
}
}
);
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if(isDarkModeConfigUpdated(newConfig)) {
events?.success(isDarkMode(newConfig))
}
oldConfig = Configuration(newConfig)
}
fun isDarkModeConfigUpdated(config: Configuration): Boolean {
return (config.diff(oldConfig) and ActivityInfo.CONFIG_UI_MODE) != 0
&& isDarkMode(config) != isDarkMode(oldConfig);
}
fun isDarkMode(config: Configuration?): Boolean {
return config!!.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}
}
我们使用EventChannel
类来创建事件驱动的通信流。一旦EventChannel Handler
附加之后,我们就可以使用onListen
方法中回传的EventSink
实例将事件发送到 Dart 端。在以下情况下会发生事件:
- 当 Flutter 应用初始化时,事件通道将收到一个具有当前主题状态的新事件
- 当用户从系统设置页面更改系统主题后返回应用时,事件通道将收到一个具有当前主题状态的新事件
请注意,这里我们使用一个boolean
值作为事件参数来标识暗模式是否被激活。
现在,将以下代码添加到main.dart
文件:
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.system,
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _theme = '';
static const events = EventChannel('example.com/channel');
void initState() {
super.initState();
events.receiveBroadcastStream().listen(_onEvent);
}
void _onEvent(Object? event) {
setState(() {
_theme = event == true ? 'dark' : 'light';
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'System color theme:',
),
Text(
_theme,
style: Theme.of(context).textTheme.headline4,
),
],
),
),
);
}
}
运行应用并激活/停用深色模式。您应该会在应用屏幕上看到主题名称,如以下预览所示:
你可能注意到应用配色方案已根据当前主题更改。发生此行为是因为我们之前已经使用了ThemeMode.system
来配置themeMode
使Flutter 应用可以响应当前系统主题,并且此行为并不是使用EventChannel
API 导致的结果。
发送/接收复杂对象
Flutter 平台通道 API 会自动转换内置的复杂类型,例如maps和lists。但是,在某些情况下,我们需要传递具有许多数据记录的更复杂的对象。您可以考虑以下发送/接收此类复杂对象的策略:
- 将数据对象转换成基本数据类型的
Map
进行传输。您可以编写一个辅助方法将您的复杂对象转换为Map
- 将对象序列化为与平台无关的格式,如 JSON,并在使用前反序列化
- 编写用于序列化/反序列化的自定义编解码器。可以参考FirestoreMessageCodec的实现。
Channel和平台线程
目标平台向 Flutter 发起 channel 调用的时候,需要在对应平台的主线程执行。同样的,在 Flutter 向目标平台发起 channel 调用的时候,需要在根 Isolate
中执行。对应平台侧的 handler 既可以在平台的主线程执行,也可以通过事件循环在后台执行。对应平台侧 handler 的返回值可以在任意线程异步执行。
在后台Isolate中使用插件和Channel
任何Isolate
都可以使用插件和通道,但该Isolate
必须是根Isolate
(由Flutter创建的Isolate
)或注册为 根Isolate
的后台Isolate
。
以下示例显示了如何注册后台Isolate
以便从后台Isolate
中使用插件。
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
void _isolateMain(RootIsolateToken rootIsolateToken) async {
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
print(sharedPreferences.getBool('isDebug'));
}
void main() {
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
Isolate.spawn(_isolateMain, rootIsolateToken);
}
在后台线程中执行 channel 的 handlers
要在 channel 对应的平台侧的后台中执行 handler,需要使用 Task Queue
API。当前该功能仅支持在 iOS 和 Android。
对应的 Java 代码:
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
BinaryMessenger messenger = binding.getBinaryMessenger();
BinaryMessenger.TaskQueue taskQueue =
messenger.makeBackgroundTaskQueue();
channel =
new MethodChannel(
messenger,
"com.example.foo",
StandardMethodCodec.INSTANCE,
taskQueue);
channel.setMethodCallHandler(this);
}
Kotlin 版本:
override fun onAttachedToEngine( flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val taskQueue =
flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue()
channel = MethodChannel(flutterPluginBinding.binaryMessenger,
"com.example.foo",
StandardMethodCodec.INSTANCE,
taskQueue)
channel.setMethodCallHandler(this)
}
跳转到 Android 中的 UI 线程
为了符合通道跳转到 Android UI 线程的要求,你可能需要从后台线程跳转到 Android 的 UI 线程以执行通道的方法。在 Android 中的实现方式是:在 Android UI 线程里(使用主线程Looper的Handler) post()
一个 Runnable
。这能使得 Runnable
在下一次机会时在主线程上执行。
Java 代码:
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Call the desired channel message here.
}
});
Kotlin 代码:
Handler(Looper.getMainLooper()).post {
// Call the desired channel message here.
}
打包和发布Native代码
Flutter SDK 提供了一个功能齐全的插件系统来创建、发布和集成可共享的插件。
这部分可直接查看官方文档:Flutter Packages 的开发和提交
当你的 Packag 被发布到 pub.dev 上之后,其他开发者便可以搜索并轻松的使用它。
在 Flutter 中嵌入原生 Android View
Platform view 允许将原生视图嵌入到 Flutter 应用中,所以您可以通过 Dart 将变换、裁剪和不透明度等效果应用到原生视图。
Flutter 支持两种集成模式:虚拟显示模式 (Virtual displays) 和混合集成模式 (Hybrid composition) 。
我们应根据具体情况来决定使用哪种模式:
-
混合集成模式 会将原生的
android.view.View
附加到视图层次结构中。因此,键盘处理和无障碍功能是开箱即用的。在Android 10
之前,此模式可能会大大降低 Flutter UI 的帧吞吐量 (FPS)。minSdkVersion
最低为19
-
虚拟显示模式 会将
android.view.View
实例渲染为纹理,因此它不会嵌入到 AndroidActivity
的视图层次结构中。某些平台交互(例如键盘处理和辅助功能)可能无法正常工作。minSdkVersion
最低为20
。
在 Android 上创建 Platform view 需要如下的步骤:
准备 Native View
继承 io.flutter.plugin.platform.PlatformView
以提供对 android.view.View
的引用,如 NativeView.kt
所示:
package dev.flutter.example
import android.content.Context
import android.graphics.Color
import android.view.View
import android.widget.TextView
import io.flutter.plugin.platform.PlatformView
internal class NativeView(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
private val textView: TextView
override fun getView(): View {
return textView
}
override fun dispose() {}
init {
textView = TextView(context)
textView.textSize = 72f
textView.setBackgroundColor(Color.rgb(255, 255, 255))
textView.text = "Rendered on a native Android view (id: $id)"
}
}
创建 NativeViewFactory 工厂类
创建一个用来创建 NativeView
的实例的工厂类,参考 NativeViewFactory.kt
:
package dev.flutter.example
import android.content.Context
import android.view.View
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
class NativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
val creationParams = args as Map<String?, Any?>?
return NativeView(context, viewId, creationParams)
}
}
注册 NativeView
最后,注册这个平台视图。这一步可以在Activity中注册,也可以在插件中注册。
- 如果要在Activity中进行注册,修改应用的主 Activity (例如:
MainActivity.kt
):
package dev.flutter.example
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
flutterEngine
.platformViewsController
.registry
.registerViewFactory("<platform-view-type>", NativeViewFactory())
}
}
- 如果要在插件中进行注册,修改您插件的主类 (例如:
PlatformViewPlugin.kt
):
package dev.flutter.plugin.example
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
class PlatformViewPlugin : FlutterPlugin {
override fun onAttachedToEngine(binding: FlutterPluginBinding) {
binding
.platformViewRegistry
.registerViewFactory("<platform-view-type>", NativeViewFactory())
}
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}
}
修改 sdk 版本
最后,修改您的 build.gradle
文件来满足 Android SDK 最低版本的要求:
android {
defaultConfig {
minSdkVersion 19 // if using hybrid composition
minSdkVersion 20 // if using virtual display.
}
}
在 Dart 中进行处理
在 Dart 端,创建一个 Widget
然后添加如下的实现,具体如下:
混合集成模式
在 Dart 文件中,例如 native_view_example.dart
,请执行下列操作:
- 添加下面的导入代码:
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
- 实现
build()
方法:
Widget build(BuildContext context) {
// This is used in the platform side to register the view.
const String viewType = '<platform-view-type>';
// Pass parameters to the platform side.
const Map<String, dynamic> creationParams = <String, dynamic>{};
return PlatformViewLink(
viewType: viewType,
surfaceFactory:
(context, controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (params) {
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
onFocus: () {
params.onFocusChanged(true);
},
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
);
}
虚拟显示模式 (Virtual Display)
在 Dart 文件中,例如 native_view_example.dart
,请执行下列操作:
- 添加下面的导入代码:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
- 实现
build()
方法:
Widget build(BuildContext context) {
// This is used in the platform side to register the view.
const String viewType = '<platform-view-type>';
// Pass parameters to the platform side.
final Map<String, dynamic> creationParams = <String, dynamic>{};
return AndroidView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
以上代码中的 viewType
需要在 Dart 和 kotlin 代码中保持一致,且保证唯一性。
您的插件或应用必须使用 Android embedding v2 以确保平台视图可用。如果您还没有更新您的插件,查看插件迁移指南。
实例:WebViewPlugin 的实现
下面示例将展示如何将 Android 和 iOS 本地 WebView
公开为 Flutter Widget。
下图是 webview_plugin 工作原理的概述
Flutter端的实现
首先在 ./lib/web_view.dart
中创建使用 WebView
的 Widget
代码:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
typedef FlutterWebViewCreatedCallback = void Function(
WebViewController controller);
class WebView extends StatelessWidget {
final FlutterWebViewCreatedCallback onMapViewCreated;
const WebView({Key? key, required this.onMapViewCreated}) : super(key: key);
Widget build(BuildContext context) {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return AndroidView(
viewType: 'plugins.codingwithtashi/flutter_web_view',
onPlatformViewCreated: _onPlatformViewCreated,
);
case TargetPlatform.iOS:
return UiKitView(
viewType: 'plugins.codingwithtashi/flutter_web_view',
onPlatformViewCreated: _onPlatformViewCreated,
);
default:
return Text(
'$defaultTargetPlatform is not yet supported by the web_view plugin');
}
}
// Callback method when platform view is created
void _onPlatformViewCreated(int id) =>
onMapViewCreated(WebViewController._(id));
}
// WebView Controller class to set url etc
class WebViewController {
WebViewController._(int id)
: _channel =
MethodChannel('plugins.codingwithtashi/flutter_web_view_$id');
final MethodChannel _channel;
Future<void> setUrl({required String url}) async {
return _channel.invokeMethod('setUrl', url);
}
}
在这段代码中,我们返回基于平台的AndroidView
和UiKitView
。(有关它们的完整内容请查看AndroidView和UiKitView文档)
这里需要注意的一件重要事情是,当我们在第 15 行和第 20 行创建 AndroidView 或 UiKitView 时,我们需要提供一个viewType
我们将在稍后的时间点使用的。
此外,我们还提供了onPlatformCompleted
方法,以便我们可以为 WebView
Widget 提供一个WebViewController
,然后我们可以使用它调用用setUrl
方法来更新其URL
。
Android Native端的实现
创建 WebViewPlugin.kt
,内容如下:
package com.codingwithtashi.web_view
import io.flutter.embedding.engine.plugins.FlutterPlugin
class WebViewPlugin: FlutterPlugin {
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
binding.platformViewRegistry.registerViewFactory(
"plugins.codingwithtashi/flutter_web_view", WebViewFactory(binding.binaryMessenger))
}
/*
* onDetachedFromEngine: should release all resources in this method
* https://api.flutter.dev/javadoc/io/flutter/embedding/engine/plugins/FlutterPlugin.html
* */
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
/*
* Eg: .setMethodCallHandler(null), setStreamHandler(null) etc
* */
// TODO("Not yet implemented")
}
}
我们在这里所做的只是添加 WebViewPlugin
继承 FlutterPlugin
并覆写两个方法。
在onAttAchedToEngine
方法中,我们为registerViewFactory
提供了 viewType
以及 WebViewFactory
。其中 viewType
就是我们之前在whichwebview.dart
中定义过的, 而WebViewFactory
会将我们 Native 的 WebView
作为PlatformView
来创建。
接下来就是创建这个缺失的WebViewFactory
类,新建 WebViewFactory.kt
:
package com.codingwithtashi.web_view
import android.content.Context
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
class WebViewFactory(private val messenger: BinaryMessenger) :
PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, id: Int, o: Any?): PlatformView {
return FlutterWebView(context, messenger, id)
}
}
我们为 WebViewFactory
实现了create
方法返回一个PlatformView
(在我们的例子中它将返回一个FlutterWebView
)。
接下来就是创建这里缺失的 FlutterWebView
,新建 FlutterWebView.kt
:
package com.codingwithtashi.web_view
import android.content.Context
import android.view.View
import android.webkit.WebView
import android.webkit.WebViewClient
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.platform.PlatformView
class FlutterWebView internal constructor(
context: Context,
messenger: BinaryMessenger,
id: Int
) : PlatformView, MethodCallHandler {
private val webView: WebView
private val methodChannel: MethodChannel
override fun getView(): View {
return webView
}
init {
// Init WebView
webView = WebView(context)
// Set client so that you can interact within WebView
webView.webViewClient = WebViewClient()
methodChannel = MethodChannel(messenger, "plugins.codingwithtashi/flutter_web_view_$id")
// Init methodCall Listener
methodChannel.setMethodCallHandler(this)
}
override fun onMethodCall(methodCall: MethodCall, result: MethodChannel.Result) {
when (methodCall.method) {
"setUrl" -> setUrl(methodCall, result)
else -> result.notImplemented()
}
}
// set and load new Url
private fun setUrl(methodCall: MethodCall, result: MethodChannel.Result ) {
val url = methodCall.arguments as String
webView.loadUrl(url)
result.success(null)
}
// Destroy WebView when PlatformView is destroyed
override fun dispose() {
webView.destroy()
}
}
这里我们让 FlutterWebView
继承了 PlatformView
和 MethodCallHandler
,FlutterWebView
的主要作用就是可以生产一个Native的WebView
实例,并设置一个MethodChannel
,以便WebView
可以从 dart
代码接收数据并进行更新(在本例中是更新 URL
)。
-
为了实现
PlatformView
,我们覆写了它的getView
方法返回在init
方法中所创建的WebView
新实例对象,同时,还覆写了dispose()
方法以便当PlatformView
被销毁时,它能够销毁WebView
。 -
为了实现
MethodCallHandler
,我们需要重写onMethodCall
,并根据method
调用我们的内部setUrl
方法(以更新 WebView URL)或 返回result.notImplemented()
(因为我们目前不支持任何其他方法)。
这样我们就可以将原生视图绘制成一个 Flutter 的 Widget
并且同时能够接收来自Dart的数据。
现在是时候测试我们的新 WebView Widget了!
打开 flutter 项目,转到./lib/main.dart
并替换为以下代码:
import 'package:flutter/material.dart';
import 'package:web_view/web_view.dart';
void main() => runApp(const MaterialApp(home: WebViewExample()));
class WebViewExample extends StatefulWidget {
const WebViewExample({Key? key}) : super(key: key);
State<WebViewExample> createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
late final TextEditingController _urlController;
late final WebViewController _webViewController;
void initState() {
_urlController = TextEditingController(text: 'https://flutter.dev/');
super.initState();
}
void dispose() {
_urlController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter WebView example')),
body: Column(
children: [
TextFormField(
controller: _urlController,
),
ElevatedButton(
onPressed: () =>
_webViewController.setUrl(url: _urlController.text),
child: const Text('Load Url'),
),
Expanded(
child: WebView(
onMapViewCreated: _onMapViewCreated,
),
),
],
),
);
}
// load default
void _onMapViewCreated(WebViewController controller) {
_webViewController = controller;
controller.setUrl(url: _urlController.text);
}
}
测试代码很简单,我们在一个 Column
组件中包含了一个TextFormField
(用于输入url
),一个ElevatedButton
(用于加载WebView
的url
),最底部就是我们的WebView
组件。
我们还实现了onMapViewCreated
,在这里我们收集_webViewController
并调用setUrl
方法。
我们也可以在TextEditingController
和WebViewController
的帮助下手动设置url
。
在 Android 模拟器上运行查看效果:
在iOS端的开发流程跟Android端十分类似,完整代码可以参考这里。
FlutterPlugin API
如果要开始开发一个新的 Flutter Android 插件,只需实现 FlutterPlugin
的下面方法即可:
public class MyPlugin implements FlutterPlugin {
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
// TODO: your plugin is now attached to a Flutter experience.
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
// TODO: your plugin is no longer attached to a Flutter experience.
}
}
您需要特别注意,在 onAttachedToEngine()
进行初始化,并且在 onDetachedFromEngine()
中进行清理插件的各种引用。
FlutterPluginBinding
为您的插件提供了几个重要的引用:
binding.getFlutterEngine()
返回插件附加到的FlutterEngine
,提供了诸如DartExecutor
、FlutterRenderer
等内容的获取。binding.getApplicationContext()
返回当前运行的安卓应用的Application Context
。
如果您的插件需要与 UI 进行交互,例如请求权限或更改 Android UI ,那么您就需要一些附加步骤来构建您的插件。您必须实现 ActivityAware
接口。(类似的,如果您的插件需要随时保持一个后台 Service
,请实现 ServiceAware
接口。)
public class MyPlugin implements FlutterPlugin, ActivityAware {
//...normal plugin behavior is hidden...
@Override
public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) {
// TODO: your plugin is now attached to an Activity
}
@Override
public void onDetachedFromActivityForConfigChanges() {
// TODO: the Activity your plugin was attached to was
// destroyed to change configuration.
// This call will be followed by onReattachedToActivityForConfigChanges().
}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) {
// TODO: your plugin is now attached to a new Activity
// after a configuration change.
}
@Override
public void onDetachedFromActivity() {
// TODO: your plugin is no longer associated with an Activity.
// Clean up references.
}
}
若需要与 Activity
交互,您已经实现 ActivityAware
的插件需要在 4
个不同的阶段实现不同的行为。首先,确保您的插件已经附加至 Activity
。您可以通过提供的 ActivityPluginBinding
获取到 Activity
及一些回调。
由于 Activity
有可能在配置变化时被销毁,您必须在 onDetachedFromActivityForConfigChanges()
方法中清理所有与 Activity
有关的引用,接着在 onReattachedToActivityForConfigChanges()
中重新建立它们。
最后,在 onDetachedFromActivity()
中清理所有与 Activity
有关的引用并返回与 UI 无关的配置。
我们可以得知通过实现 FlutterPlugin
这个类我们基本可以获得所需的一切:Flutter Engine对象、应用的Context上下文对象、最多再需要一个 Activity对象(通过实现ActivityAware接口获得)。这些已经足够我们去开发一个插件的所有功能。
此外,就无需任何其他配置了,这是因为从 embedding v2 版本开始会自动注册插件。这也是为什么在前面的部分示例代码中我们完成了FlutterPlugin的实现类后就大功告成了,并没有写注册该插件的代码。
对于稍微复杂的插件,建议可以将 FlutterPlugin
与 MethodCallHandler
拆分到不同的类中。
关于以前旧版本的升级
如果项目是从以前 Embedding V1 版本升级到 Embedding V2 版本,项目中可能有一个 MainActivity.java
,可以让其继承 FlutterActivity
像下面这样保持空实现即可:
package io.flutter.plugins.firebasecoreexample;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.firebase.core.FirebaseCorePlugin;
public class MainActivity extends FlutterActivity {
// 插件会自动注册,所以这里什么也不用做,甚至可以删除此类。
// 看到有的人会在这个类的configureFlutterEngine方法中拿到engine去注册FlutterPlugin其实没必要
}
如果您直接移除了 MainActivity.java
,不保留它,那么请更新插件中android/app/src/main/AndroidManifest.xml
内容以使用 io.flutter.embedding.android.FlutterActivity
。例如:
<activity android:name="io.flutter.embedding.android.FlutterActivity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
android:hardwareAccelerated="true"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
如果还想继续使用 Embedding V1 版本持续测试您的项目对 v1 版本的兼容性,那么可以在 MainActivity.java
同级目录下创建一个 EmbeddingV1Activity.java
文件,例如:
package io.flutter.plugins.batteryexample;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.battery.BatteryPlugin;
public class EmbeddingV1Activity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BatteryPlugin.registerWith(registrarFor("io.flutter.plugins.battery.BatteryPlugin"));
}
}
如果上面创建了 EmbeddingV1Activity.java
,别忘了加入/android/app/src/main/AndroidManifest.xml
:
<activity
android:name=".EmbeddingV1Activity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
android:hardwareAccelerated="true"
android:exported="true"
android:windowSoftInputMode="adjustResize">
</activity>
在上面基础上添加 <meta-data android:name="flutterEmbedding" android:value="2"/>
,可让示例应用使用 Embedding v2 版本。
另外, FlutterPlugin
类有一个静态的 registerWith()
方法,也是为了兼容旧版本的,现在就不要使用它了。
Platform Channel 架构分析
Platform Channel 涉及 Flutter 的 Framework
和 Embedder
两层,它们的架构大体类似,这里以Embedder
的架构为例进行分析。Embedder in Platform Channel关键类及其关系如图下所示。
其中,BasicMessageChannel
、 MethodChannel
、 EventChannel
是开发者和 Flutter Framework
进行通信的接口,它们的底层通信都是通过 messenger
字段所持有的BinaryMessenger
接口来实现的。
DefaultBinaryMessenger
是该接口的默认实现类,该类其实将所有工作都委托给DartMessenger
,DartMessenger
通过flutterJNI
字段持有FlutterJNI
的实例,可以与FlutterEngine
进行通信。DartMessenger
有两个关键字段:pendingReplies
字段记录了一个BinaryReply
接口的列表,用于分发Framework
的返回数据;messageHandlers
记录了一个BinaryMessageHandler
接口的列表,用于处理Framework
发送过来的请求,该接口的实现类分别持有MessageHandler
、MethodCallHandler
和StreamHandler
,这3个接口也是开发者经常需要重写的接口,它们将执行Embedder
侧真正的处理逻辑。
除了数据通信,以上3个Channel
类还通过codec
字段持有对应的编解码接口,即MessageCodec
和MethodCodec
。
BasicMessageChannel 流程分析
BasicMessageChannel
的执行流程:
其中,MessageHandler
是一个接口,具体由Native侧的开发者实现其中的onMessage()
方法向Flutter端回复消息。
下面是详细代码流程分析。
以Flutter向Platform发送消息为例,Flutter会调用BasicMessageChannel
的send
方法并获得其返回值,如代码清单9-1所示。
// 代码清单9-1 flutter/packages/flutter/lib/src/services/platform_channel.dart
Future<T> send(T message) async { // BasicMessageChannel
return codec.decodeMessage(
await binaryMessenger.send(name, codec.encodeMessage(message)));
}
由于Dart、C++、Java的对象不能直接转换,因此需要先通过codec
对象完成message
参数的序列化,完成序列化后,将通过binaryMessenger
完成消息的发送,如代码清单9-2所示。
// 代码清单9-2 flutter/packages/flutter/lib/src/services/binding.dart
// _DefaultBinaryMessenger
Future<ByteData?>? send(String channel, ByteData? message) {
final MessageHandler? handler = _mockHandlers[channel]; // 方便测试
if (handler != null) return handler(message);
return _sendPlatformMessage(channel, message);
}
Future<ByteData?> _sendPlatformMessage(String channel, ByteData? message) {
final Completer<ByteData?> completer = Completer<ByteData?>();
ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message,
// 见代码清单9-3
(ByteData? reply) { // 将在代码清单9-13中被触发
try {
completer.complete(reply); // Completer在此实现类似callback的效果
} catch (exception, stack) { ...... }
}
);
return completer.future;
}
以上逻辑的本质是调用Engine
的SendPlatformMessage
方法,如代码清单9-3所示。
// 代码清单9-3 engine/lib/ui/window/platform_configuration.cc
Dart_Handle SendPlatformMessage(Dart_Handle window, const std::string& name,
Dart_Handle callback, Dart_Handle data_handle) {
UIDartState* dart_state = UIDartState::Current();
if (!dart_state->platform_configuration()) { ...... } // 该字段为空,说明不是Main
// Isolate,产生异常
fml::RefPtr<PlatformMessageResponse> response;
if (!Dart_IsNull(callback)) { // 若callback有了定义,则将其封装成response,具体
// 调用过程见代码清单9-12
response = fml::MakeRefCounted<PlatformMessageResponseDart>(
tonic::DartPersistentValue(dart_state, callback),
dart_state->GetTaskRunners().GetUITaskRunner()); // 触发callback的线程
}
if (Dart_IsNull(data_handle)) { // 没有携带任何数据,即代码清单9-2中message为空
dart_state->platform_configuration()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>(name, response));
} else {
tonic::DartByteData data(data_handle); // Dart 引用转成C++引用
const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
dart_state->platform_configuration()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>( // 3个参数:channel名称、数据、回调
name, std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()),
response));
}
return Dart_Null();
}
以上逻辑主要完成了Dart侧数据的封装,并最终调用Engine的处理逻辑,如代码清单9-4所示。
// 代码清单9-4 engine/shell/common/engine.cc
void Engine::HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) {
if (message->channel() == kAssetChannel) { // Asset资源获取,特殊处理
HandleAssetPlatformMessage(std::move(message));
} else {
delegate_.OnEngineHandlePlatformMessage(std::move(message)); // Shell
}
}
以上逻辑会触发一个特殊的Channel:读取Assets资源的Channel。开发者自定义的Platform Channel将进入Shell的处理逻辑,如代码清单9-5所示。
// 代码清单9-5 engine/shell/common/shell.cc
void Shell::OnEngineHandlePlatformMessage(fml::RefPtr<PlatformMessage> message) {
if (message->channel() == kSkiaChannel) {
HandleEngineSkiaMessage(std::move(message));
return;
}
task_runners_.GetPlatformTaskRunner()->PostTask( // UI线程→Platform线程
[view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
if (view) {
view->HandlePlatformMessage(std::move(message)); // 见代码清单9-6
}
});
}
注意,这里又处理了一个特殊Channel,对于正常情况,Shell将在此处将请求信息转发到Platform
线程。对于Android平台,view的具体实现为PlatformViewAndroid
,其逻辑如代码清单9-6所示。
// 代码清单9-6 shell/platform/android/platform_view_android.cc
void PlatformViewAndroid::HandlePlatformMessage(
fml::RefPtr<flutter::PlatformMessage> message) {
int response_id = 0;
if (auto response = message->response()) {
response_id = next_response_id_++; // 本次请求的唯一id,用于返回阶段触发response
pending_responses_[response_id] = response; // 存储以备调用,详见代码清单9-12
} // 通过JNI在Embedder中调用,见代码清单9-7
jni_facade_->FlutterViewHandlePlatformMessage(message, response_id);
message = nullptr; // 使用完毕,释放PlatformMessage实例
}
以上逻辑将生成一个唯一的id
,并跟随message
信息一起进入后续逻辑,该id
将在Embedder返回时用以索引对应的response
。
最终,PlatformViewAndroid
将通过jni_facade_
向Embedder发起请求,如代码清单9-7所示。
// 代码清单9-7 flutter/shell/platform/android/platform_view_android_jni_impl.cc
void PlatformViewAndroidJNIImpl::FlutterViewHandlePlatformMessage(
fml::RefPtr<flutter::PlatformMessage> message, int responseId) {
JNIEnv* env = fml::jni::AttachCurrentThread();
auto java_object = java_object_.get(env); // FlutterJNI对象
if (java_object.is_null()) { return; }
fml::jni::ScopedJavaLocalRef<jstring> java_channel =
fml::jni::StringToJavaString(env, message->channel()); // 获取Channel名
if (message->hasData()) { // 携带参数
fml::jni::ScopedJavaLocalRef<jbyteArray> message_array(
env, env->NewByteArray(message->data().size())); // 参数转成Java可用的格式
env->SetByteArrayRegion(message_array.obj(), 0, message->data().size(),
reinterpret_cast<const jbyte*>(message->data().data()));
env->CallVoidMethod(java_object.obj(), g_handle_platform_message_method,
java_channel.obj(), message_array.obj(), responseId);
// 见代码清单9-8
} else { // 未携带参数
env->CallVoidMethod(java_object.obj(), g_handle_platform_message_method,
java_channel.obj(), nullptr, responseId);
}
}
以上逻辑主要是C++对象到Java对象的封装,g_handle_platform_message_method
对应的Embedder逻辑为FlutterJNI
的handlePlatformMessage
方法,该方法最终将触发DartMessenger
的handleMessageFromDart
方法,如代码清单9-8所示。
// 代码清单9-8 engine/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
@Override // DartMessenger
public void handleMessageFromDart(@NonNull final String channel,
@Nullable byte[] message, final int replyId) {
BinaryMessenger.BinaryMessageHandler handler = messageHandlers.get(channel);
if (handler != null) { // 说明Embedder侧的Platform Channel没有设置对应的handler
try {
final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message));
handler.onMessage( // 由具体的handler响应
buffer,
new Reply(flutterJNI, replyId)
); // 这里的Reply是BinaryMessenger.BinaryReply的实现类,区别于下面的Reply接口
} catch (Exception ex) { ...... } catch (Error err) { ...... }
// 执行过程中的异常
} else { // 告知异常
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
}
@Override // 每个Platform Channel创建后调用自身的setMessageHandler触发
public void setMessageHandler( @NonNull String channel,
@Nullable BinaryMessenger.BinaryMessageHandler handler) {
if (handler == null) {
messageHandlers.remove(channel);
} else {
messageHandlers.put(channel, handler);
}
}
BasicMessageChannel
对应的BinaryMessageHandler
为IncomingMessageHandler
,如代码清单9-9所示。
// 代码清单9-9 shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java
@Override // IncomingMessageHandler
public void onMessage(@Nullable ByteBuffer message, @NonNull final BinaryReply
callback) {
try {
handler.onMessage( // 由MessageHandler接口的实现者执行,见代码清单9-8
codec.decodeMessage(message), // 第1个参数,完成解码的消息
new Reply<T>() { // 第2个参数,实现了Reply接口的匿名实例
@Override
public void reply(T reply) { // 调用reply方法,返回数据
callback.reply(codec.encodeMessage(reply));
// 参数在此经过编码后成为二进制信息
} //
});
} catch (RuntimeException e) { callback.reply(null); }
}
handler
的类型为MessageHandler
,用户通过实现该接口,并在onMessage
中完成逻辑的处理,最后通过reply
的方法完成数据的返回,如代码清单9-10所示。
// 代码清单9-10 shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java
public interface MessageHandler<T> { // BasicMessageChannel的内部接口
void onMessage(@Nullable T message, @NonNull Reply<T> reply);
}
public interface Reply<T> {
void reply(@Nullable T reply);
}
当调用reply
方法时,由代码清单9-9可知,将触发BinaryReply
的callback
方法,其一般由DartMessenger
的静态内部类实现,如代码清单9-11所示。
// 代码清单9-11 engine/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
static class Reply implements BinaryMessenger.BinaryReply {
@Override
public void reply(@Nullable ByteBuffer reply) {
if (done.getAndSet(true)) {
throw new IllegalStateException("Reply already submitted");
}
if (reply == null) { // 返回信息为空
flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
} else {
flutterJNI.invokePlatformMessageResponseCallback(replyId,
reply, reply.position()); // 见代码清单9-12
}
}
}
以上逻辑最终将触发PlatformViewAndroid
的InvokePlatformMessageResponseCallback
方法,如代码清单9-12所示。
// 代码清单9-12 engine/shell/platform/android/platform_view_android.cc
void PlatformViewAndroid::InvokePlatformMessageResponseCallback(
JNIEnv* env, jint response_id, // 调用Platform方法时携带的id,见代码清单9-6
jobject java_response_data, // 数据的引用(起始位置)
jint java_response_position) { // 数据的结束位置
if (!response_id) return; // 没有id信息,直接返回,因为不会触发任何有意义的逻辑
auto it = pending_responses_.find(response_id); // 找到前面内容存储的response
if (it == pending_responses_.end()) return;
uint8_t* response_data = // Java侧数据的引用
static_cast<uint8_t*>(env->GetDirectBufferAddress(java_response_data));
std::vector<uint8_t> response = std::vector<uint8_t>( // 完整的数据信息
response_data, response_data + java_response_position);
auto message_response = std::move(it->second); // 取出callback
pending_responses_.erase(it);
message_response->Complete( // 触发回调
std::make_unique<fml::DataMapping>(std::move(response)));
}
以上逻辑主要是处理Embedder返回的数据,并调用之前注册的response
,由代码清单9-3可知,Complete
的逻辑如代码清单9-13所示。
// 代码清单9-13 engine/lib/ui/window/platform_message_response_dart.cc
void PlatformMessageResponseDart::Complete(std::unique_ptr<fml::Mapping> data) {
if (callback_.is_empty()) { return; }
is_complete_ = true;
ui_task_runner_->PostTask(fml::MakeCopyable( // Platform线程→UI线程
[callback = std::move(callback_), data = std::move(data)]() mutable {
std::shared_ptr<tonic::DartState> dart_state = callback.dart_state().lock();
if (!dart_state) { return; }
tonic::DartState::Scope scope(dart_state);
Dart_Handle byte_buffer = // 将数据转换为Dart可处理的形式
tonic::DartByteData::Create(data->GetMapping(), data->GetSize());
tonic::DartInvoke(callback.Release(), {byte_buffer}); // 调用Dart中的回调
}));
}
以上逻辑主要是切换到UI线程,并触发对应的callback
,由代码清单9-2可知,其触发的Framework的逻辑是返回Engine
提供的数据。
以上便是BasicMessageChannel
的主要逻辑,主要逻辑是数据在不同语言间的传递与编解码。
MethodChannel 流程分析
MethodChannel
是对BasicMessageChannel
的进一步封装,通过对外暴露method
参数,为方法调用提供了语义化的接口,但其核心流程和BasicMessageChannel
几乎一致。
其中,MethodCallHandler
是一个接口,具体由Native侧的开发者实现其中的onMethodCall()
方法向Flutter端发送数据。
下面是详细流程代码分析。
Flutter通过MethodChannel
调用Platform中的方法,入口是invokeMethod
方法,如代码清单9-14所示。
// 代码清单9-14 flutter/packages/flutter/lib/src/services/platform_channel.dart
// MethodChannel
Future<T?> invokeMethod<T>(String method, [ dynamic arguments ]) {
return _invokeMethod<T>(method, missingOk: false, arguments: arguments);
}
Future<T?> _invokeMethod<T>(String method, {
required bool missingOk, dynamic arguments }) async {
final ByteData? result = await binaryMessenger.send( // 见代码清单9-2
name,
codec.encodeMethodCall(MethodCall(method, arguments)),); // 见代码清单9-16
if (result == null) {
if (missingOk) { return null; } // 允许不返回任何数据
throw MissingPluginException('No implementation found ....3.'); // 异常情况处理
}
return codec.decodeEnvelope(result) as T?;
}
以上逻辑调用的接口和BasicMessageChannel
是一致的,故Engine
中的逻辑和前面内容的分析一致。但在Framework
和Embedder
中各有一处不同:一是codec
对象的类型是MethodCodec
的子类,其编码逻辑稍有差异,后面将详细分析;二是代码清单9-8中响应的handler
对象则将变成IncomingMethodCallHandler
类型,其onMessage
方法的逻辑如代码清单9-15所示。
// 代码清单9-15 engine/shell/platform/android/io/flutter/plugin/common/MethodChannel.java
// IncomingMethodCallHandler,在代码清单9-8中触发
public void onMessage(ByteBuffer message, final BinaryReply reply) {
final MethodCall call = codec.decodeMethodCall(message); // 解码,见代码清单9-17
try {
handler.onMethodCall( // handler是实现了MethodCallHandler接口的实例
call, // MethodCodec完成解码后的数据
new Result() {
@Override // 告知Flutter Framework方法执行成功,并返回结果
public void success(Object result) {
reply.reply(codec.encodeSuccessEnvelope(result));
}
@Override // 告知Flutter Framework方法执行错误
public void error(String errorCode,
String errorMessage, Object errorDetails) {
reply.reply(codec.encodeErrorEnvelope(
errorCode, errorMessage, errorDetails));
}
@Override // 无对应实现
public void notImplemented() {
reply.reply(null);
}
}); // Result
} catch (RuntimeException e) { ...... }
}
以上逻辑和代码清单9-9大体一致,在此不再赘述。
MethodChannel
和BasicMessageChannel
的根本差异在于其编码策略,以代码清单9-14中encodeMethodCall
方法为例,Framework侧的逻辑如代码清单9-16所示。
// 代码清单9-16 flutter/packages/flutter/lib/src/services/message_codecs.dart
// StandardMethodCodec
ByteData encodeMethodCall(MethodCall call) {
final WriteBuffer buffer = WriteBuffer();
messageCodec.writeValue(buffer, call.method); // 方法名作为第1个数据进行编码
messageCodec.writeValue(buffer, call.arguments); // 后续参数
return buffer.done();
}
而代码清单9-15中Embedder的解码逻辑如代码清单9-17所示。
// 代码清单9-17 engine/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java
@Override // StandardMethodCodec
public MethodCall decodeMethodCall(ByteBuffer methodCall) {
methodCall.order(ByteOrder.nativeOrder());
final Object method = messageCodec.readValue(methodCall); // 第1个数据是方法名
final Object arguments = messageCodec.readValue(methodCall);
if (method instanceof String && !methodCall.hasRemaining()) {
return new MethodCall((String) method, arguments);
}
throw new IllegalArgumentException("Method call corrupted");
}
这里的messageCodec
对象其实就是StandardMessageCodec
的一个实例,MethodCodec
的底层还是通过MessageCodec
进行编解码的,并默认以第1
个数据作为方法名,所以,可以认为MethodChannel
是BasicMessageChannel
的一个特例,因为它把方法名语义化了。
EventChannel 流程分析
EventChannel
是对MethodChannel
的语义化封装,其实就是MethodChannel
的一个更抽象的封装。
Flutter Framework
通过 EventChannel
可以获得一个Stream
,而该Stream
的数据正是来自Embedder
中MethodChannel
的调用。
首先分析Flutter中EventChannel
的注册逻辑,如代码清单9-18所示。
// 代码清单9-18 flutter/packages/flutter/lib/src/services/platform_channel.dart
Stream<dynamic> receiveBroadcastStream([ dynamic arguments ]) { // EventChannel
final MethodChannel methodChannel = MethodChannel(name, codec);
late StreamController<dynamic> controller;
controller = StreamController<dynamic>.broadcast(onListen: () async {
binaryMessenger.setMessageHandler(name, (ByteData? reply) async {
// 见代码清单9-21
if (reply == null) {
controller.close();
} else {
try {
controller.add(codec.decodeEnvelope(reply)); // 向Stream提供数据
} on PlatformException catch (e) {
controller.addError(e);
}
}
return null;
}); // setMessageHandler
try {
await methodChannel.invokeMethod<void>('listen', arguments); // 见代码清单9-20
} catch (exception, stack) { ...... }
}, onCancel: () async { // 取消对Stream监听所触发的逻辑
binaryMessenger.setMessageHandler(name, null);
try {
await methodChannel.invokeMethod<void>('cancel', arguments);
} catch (exception, stack) { ...... }
}); // controller
return controller.stream;
}
当Framework
通过receiveBroadcastStream
方法获取Stream
实例并开始监听时, 将触发onListen
回调。onListen
的逻辑主要是设置Framework
侧的 MessageHandler
,用以处理Embedder
后续将发送的数据。
Embedder
中,EventChannel
对应的handler
实例为IncomingStreamRequestHandler
类型,其onMessage
方法如代码清单9-19所示。
// 代码清单9-19 flutter/shell/platform/android/io/flutter/plugin/common/EventChannel.java
@Override // IncomingStreamRequestHandler
public void onMessage(ByteBuffer message, final BinaryReply reply) {
final MethodCall call = codec.decodeMethodCall(message);
if (call.method.equals("listen")) { // 开始监听
onListen(call.arguments, reply); // 见代码清单9-20
} else if (call.method.equals("cancel")) { // 取消监听
onCancel(call.arguments, reply);
} else {
reply.reply(null);
}
}
以上逻辑说明EventChannel其实就是MethodChannel的一个更抽象的封装。接下来以onListen
方法为例分析,如代码清单9-20所示。
// 代码清单9-20 flutter/shell/platform/android/io/flutter/plugin/common/EventChannel.java
private void onListen(Object arguments, BinaryReply callback) {
final EventSink eventSink = new EventSinkImplementation();
final EventSink oldSink = activeSink.getAndSet(eventSink);
if (oldSink != null) {
try {
handler.onCancel(null); // 取消原来的监听
} catch (RuntimeException e) { ...... }
}
try {
handler.onListen(arguments, eventSink); // 实现了StreamHandler接口的实例
callback.reply(codec.encodeSuccessEnvelope(null));
} catch (RuntimeException e) { ...... }
}
以上逻辑中,onListen
将调用StreamHandler
的onListen
接口,具体实现取决于开发者。onListen
的第2个参数是EventSinklmplementation
的实例,开发者可以通过其 success
方法向Flutter Framework
发送数据(即该方法成为Framework中Stream
的数据源),其逻辑如代码清单9-21所示。
// 代码清单9-21 flutter/shell/platform/android/io/flutter/plugin/common/EventChannel.java
public void success(Object event) { // EventSinkImplementation
if (hasEnded.get() || activeSink.get() != this) {
return;
} // 由于EventSinkImplementation是EventChannel的内部类,因此这里可以直接获取当前对象
EventChannel.this.messenger.send(name, codec.encodeSuccessEnvelope(event));
}
以上逻辑中,event
参数为开发者自定义的数据,codec
为MethodCodec
的实例。success
通过send
方法向Framework发送数据,而响应的逻辑则在代码清单9-18中,主要是将二进制数据解码成dynamic
类型的对象,并通知给Stream
。
所以EventChannel
的大致流程如下图:
其中,StreamHandler
是一个接口,具体由Native侧的开发者实现其中的onListen()
方法,该方法给Native侧提供EventSink
回调参数,Native侧利用EventSink.success()
向Flutter端发送数据。
至此,Platform Channel的底层源码分析完毕。可以看到,主要是不同语言间的调用转发和数据的编解码,以及通过对数据做语义化封装,进而提供更为抽象的MethodChannel
和EventChannel
。
Platform View原理分析
Platform Channel解决了Flutter复用Platform逻辑的问题,Platform View要解决的 是Flutter复用Platform的UI的问题, Flutter以彻底的跨平台为目标,但有些场景仍不得不复用Platform的组件,最典型的就是地图、WebView这种用Flutter重新实现将产生巨大工作量的UI组件,所以Flutter也需要提供原生UI的复用能力。
Platform View 关键类:
上图中,AndroidView
、PlatformViewLink
和UiKitView
是开发者使用的用于表示Platform View的Widget
接口,它们底层对应的RenderObject
分别为RenderAndroidView
、PlatformViewRenderBox
和RenderUiKitView
,前者将基于 TextureLayer
进行真正的绘制,而后两者将基于PlatformViewLayer
进行真正的绘制。
- 使用
TextureLayer
展示Platform View的方式被称为虚拟显示模式Virtual Display
,仅Android平台支持; - 使用
PlatformViewLayer
展示Platform View的方式被称为混合集成模式Hybrid Composition
,Android平台和iOS平台都支持。
PlatformViewController
和UiKitViewController
是对Platform View中使用的Platform Channel
的抽象封装,用于控制Platform View
在Embedder
中的各种属性和表现。
Virtual Display 原理分析
AndroidView的典型使用流程如代码清单9-22所示。
// 代码清单9-22 AndroidView的典型使用流程
Widget build(BuildContext context) {
final String viewType = 'hybrid-view-type'; // 类型id
final Map<String, dynamic> creationParams = <String, dynamic>{};
return AndroidView(
viewType: viewType, // 用于Embedder侧查找对应的Platform View
layoutDirection: TextDirection.ltr,
creationParams: creationParams, // Platform View的初始化参数
creationParamsCodec: const StandardMessageCodec(), // 编解码规则
);
}
对底层渲染来说,AndroidView
对应的RenderObject
为RenderAndroidView
,其paint
方法如代码清单9-23所示。
// 代码清单9-23 flutter/packages/flutter/lib/src/rendering/platform_view.dart
// RenderAndroidView
void paint(PaintingContext context, Offset offset) {
if (_viewController.textureId == null) return; // 必须要有对应的纹理id
if ((size.width < _currentAndroidViewSize.width ||
// 提供的大小小于Platform View的大小
size.height < _currentAndroidViewSize.height) && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect(true, offset, offset & size, // 裁剪
_paintTexture, clipBehavior: clipBehavior, oldLayer: _clipRectLayer);
return;
}
_clipRectLayer = null;
_paintTexture(context, offset); // 真正的绘制过程
}
ClipRectLayer? _clipRectLayer;
void _paintTexture(PaintingContext context, Offset offset) {
context.addLayer(TextureLayer( // 本质是添加一个独立图层——TextureLayer
rect: offset & _currentAndroidViewSize, // 绘制逻辑见代码清单9-34
textureId: _viewController.textureId!,
freeze: _state == _PlatformViewState.resizing,
));
}
由以上逻辑可知,Virtual Display 模式下,一个Platform View
将对应一个 TextureLayer
,其关键参数是textureld
,标志着当前TextureLayer
所需要渲染的纹理。
下面分析textureId
的生成。由于RenderAndroidView
的sizedByParent
字段为true
,因此会触发performResize
方法,如代码清单9-24所示。
// 代码清单9-24 flutter/packages/flutter/lib/src/rendering/platform_view.dart
void performResize() {
super.performResize();
_sizePlatformView();
}
Future<void> _sizePlatformView() async {
if (_state == _PlatformViewState.resizing || size.isEmpty) { return; }
_state = _PlatformViewState.resizing; // 更新状态
markNeedsPaint();
Size targetSize;
do {
targetSize = size;
await _viewController.setSize(targetSize); // 通知Platform View调整大小,
// 见代码清单9-25
_currentAndroidViewSize = targetSize; // 更新当前RenderObject大小,在Paint阶段使用
} while (size != targetSize); // 持续校正
_state = _PlatformViewState.ready; // 更新状态
markNeedsPaint();
} // _sizePlatformView()
以上逻辑将不断调节Platform View
的大小,直至预期的大小。注意,由于sizedByParent
字段为true
,因此RenderAndroidView
的大小完全由父节点决定。
继续分析setSize
方法的逻辑,如代码清单9-25所示。
// 代码清单9-25 flutter/packages/flutter/lib/src/services/platform_views.dart
// TextureAndroidViewController
Future<void> setSize(Size size) async {
if (_state == _AndroidViewState.waitingForSize) { // 首次调用,见代码清单9-26
_size = size;
return create(); // 见代码清单9-26
}
await SystemChannels.platform_views.invokeMethod<void>('resize',
<String, dynamic>{'id': viewId,'width': size.width, 'height': size.
height, });
}
在创建TextureAndroidViewController
的实例时,在初始化列表中_state
被默认初始化为waitingForSize
状态,因此首次会进入create
方法,如代码清单9-26所示。
// 代码清单9-26 flutter/packages/flutter/lib/src/services/platform_views.dart
Future<void> create() async {
await _sendCreateMessage();
_state = _AndroidViewState.created; // 更新状态为已创建
for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
callback(viewId); // 对外通知Platform View完成创建
}
}
Future<void> _sendCreateMessage() async { // 对应Embedder的逻辑,见代码清单9-27
final Map<String, dynamic> args = <String, dynamic>{
'id': viewId, // 计数id,对于Virtual Display模式无作用
'viewType': _viewType, // 类型id
'width': _size.width, 'height': _size.height, // 大小信息
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
};
if (_creationParams != null) {
final ByteData paramsByteData = _creationParamsCodec!.encodeMessage
(_creationParams)!;
args['params'] = Uint8List.view(paramsByteData.buffer, 0, paramsByteData.
lengthInBytes,);
}
_textureId = await SystemChannels.platform_views.invokeMethod<int>('create', args);
}
以上逻辑最终将调用Embedder中PlatformViewsChannel
内部的create
方法,如代码清单9-27所示。
// 代码清单9-27 engine/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java
private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
Map<String, Object> createArgs = call.arguments();
boolean usesHybridComposition = // 在Hybrid Composition模式下基于SurfaceView实
// 现,无须宽和高
createArgs.containsKey("hybrid") && (boolean) createArgs.get("hybrid");
double width = (usesHybridComposition) ? 0 : (double) createArgs.get("width");
double height = (usesHybridComposition) ? 0 : (double) createArgs.get("height");
PlatformViewCreationRequest request = // 解析Platform View的创建参数
new PlatformViewCreationRequest(
(int) createArgs.get("id"), // Framework对于Platform View的计数id
(String) createArgs.get("viewType"), // // Platform View的类型id
width, height, (int) createArgs.get("direction"),
createArgs.containsKey("params") // 其他参数,用于配置Platform View
? ByteBuffer.wrap((byte[]) createArgs.get("params")) : null);
try {
if (usesHybridComposition) { // 见代码清单9-49
handler.createAndroidViewForPlatformView(request);
result.success(null);
} else { // 见代码清单9-28
long textureId = handler.createVirtualDisplayForPlatformView(request);
result.success(textureId); // 返回纹理id,给TextureLayer使用
}
} catch (IllegalStateException exception) { ...... }
}
以上逻辑同时处理了Virtual Display
和 Hybrid Composition
,这里先分析前者,即createVirtualDisplayForPlatformView
方法的逻辑,如代码清单9-28所示。
// 代码清单9-28 engine/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java
public long createVirtualDisplayForPlatformView(
@NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH);
if (!validateDirection(request.direction)) { ...... }
if (vdControllers.containsKey(request.viewId)) { ...... }
PlatformViewFactory viewFactory = registry.getFactory(request.viewType);
if (viewFactory == null) { ...... } // 通过viewType获取对应的PlatformViewFactory
Object createParams = null;
if (request.params != null) { // 参数解析
createParams = viewFactory.getCreateArgsCodec().decodeMessage(request.params);
}
int physicalWidth = toPhysicalPixels(request.logicalWidth);
int physicalHeight = toPhysicalPixels(request.logicalHeight);
validateVirtualDisplayDimensions(physicalWidth, physicalHeight); // 设置宽和高
TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurface
Texture();
VirtualDisplayController vdController =
VirtualDisplayController.create( // 见代码清单9-30
context, accessibilityEventsDelegate,
viewFactory, textureEntry, // 创建View的factory实例和纹理
physicalWidth, physicalHeight, // View的宽高
request.viewId, createParams, // id、创建参数等信息
(view, hasFocus) -> { ...... });
if (vdController == null) { ...... }
if (flutterView != null) { // 与FlutterView绑定
vdController.onFlutterViewAttached(flutterView);
}
vdControllers.put(request.viewId, vdController);
View platformView = vdController.getView();
platformView.setLayoutDirection(request.direction);
contextToPlatformView.put(platformView.getContext(), platformView);
return textureEntry.id();
}
private int toPhysicalPixels(double logicalPixels) {
// 转换为原始大小,这是因为Virtual Display的API需要
return (int) Math.round(logicalPixels * getDisplayDensity());
}
private float getDisplayDensity() { // 屏幕密度
return context.getResources().getDisplayMetrics().density;
}
以上逻辑中,首先通过createSurfaceTexture
方法创建纹理并注册到Engine
中,其纹理id
最终会通过return
语句返回给Framework
,这样在代码清单9-34中渲染时,就能通过这个id
寻找对应的纹理,createSurfaceTexture
的逻辑如代码清单9-29所示。
// 代码清单9-29 engine/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java
@Override
public SurfaceTextureEntry createSurfaceTexture() {
final SurfaceTexture surfaceTexture = new SurfaceTexture(0);
// 用于Platform View的渲染
surfaceTexture.detachFromGLContext();
final SurfaceTextureRegistryEntry entry =
new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(),
surfaceTexture);
registerTexture(entry.id(), entry.textureWrapper());
return entry;
}
private void registerTexture(long textureId, @NonNull SurfaceTextureWrapper
textureWrapper) {
flutterJNI.registerTexture(textureId, textureWrapper);
// 注册到Engine中,见代码清单9-33
}
以上逻辑完成了纹理的注册,但是在分析真正的渲染之前,我们还需要更清晰地知道Embedder是如何生成纹理的,即PlatfromView
是如何创建并显示的。
首先分析Virtual Display关键类及其关系,如图9-4所示。
图中,VirtualDisplayController顾名思义就是Virtual Display流程的控制者,它持有一个VirtualDisplay(系统类)实例,该实例通过DisplayManager的createVirtualDisplay方法(系统API)进行创建,该实例表示一个虚拟屏幕,可以配置宽高、分辨率等信息。
VirtualDisplayController通过presentation字段持有SingleViewPresentation的实例,该实例是Platform View的渲染数据的生产者。具体来说,SingleViewPresentation的父类Presentation持有一个Display实例(VirtualDisplay提供),Presentation可以像Activity渲染在屏幕一样,渲染在Display中。PlatformView正是在SingleViewPresentation的生命周期回调onCreate中通过PlatformViewFactory完成创建的,并由PresentationState管理。
至此,渲染数据的生产已完成,VirtualDisplayController同时还持有渲染数据的消费者,即SurfaceTextureEntry。具体来说,SurfaceTextureEntry的实现类SurfaceTextureRegistryEntry间接持有SurfaceTexture的实例,Presentation基于Display(提供宽高、分辨率等信息)的渲染数据将作为SurfaceTexture的输入,并由Flutter Engine的Rasterizer完成最终的渲染。
总的来说,VirtualDisplayController连接了PlatformView和SurfaceTexture(即Flutter Framework的TextureLayer)。
继续沿着代码清单9-27中VirtualDisplayController
的create
方法分析,如代码清单9-30所示。
// 代码清单9-30 engine/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java
public static VirtualDisplayController create( ...... ) {
textureEntry.surfaceTexture().setDefaultBufferSize(width, height);
Surface surface = new Surface(textureEntry.surfaceTexture());
DisplayManager displayManager = // 以下主要是调用系统API
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
int densityDpi = context.getResources().getDisplayMetrics().densityDpi;
VirtualDisplay virtualDisplay = displayManager.createVirtualDisplay(
"flutter-vd", width, height, densityDpi, surface, 0);
if (virtualDisplay == null) { return null; }
return new VirtualDisplayController(
context, accessibilityEventsDelegate,
virtualDisplay,
viewFactory,
surface, textureEntry,
focusChangeListener, viewId, createParams);
}
private VirtualDisplayController( ...... ) {
// SKIP 成员变量赋值
presentation = new SingleViewPresentation( ...... );
presentation.show(); // 见代码清单9-31
}
VirtualDisplayController
的构造函数内部将创建一个SingleViewPresentation
的实例,并调用其show
方法,该方法将触发onCreate
回调(Presentation
可以理解为一个不可见的Activity
,它也有自己的生命周期回调),如代码清单9-31所示。
// 代码清单9-31 engine/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java
@Override // SingleViewPresentation
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // 配置Window信息
getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.
TRANSPARENT));
if (state.fakeWindowViewGroup == null) { // 用于接管WindowManager,详见正文分析
state.fakeWindowViewGroup = new FakeWindowViewGroup(getContext());
}
if (state.windowManagerHandler == null) {
WindowManager windowManagerDelegate =
(WindowManager) getContext().getSystemService(WINDOW_SERVICE);
state.windowManagerHandler =
new WindowManagerHandler(windowManagerDelegate, state.fakeWindowViewGroup);
}
container = new FrameLayout(getContext()); // 存放Platform View的容器
Context context = // 当前Presentation的上下文
new PresentationContext(getContext(), state.windowManagerHandler, outerContext);
if (state.platformView == null) { // 真正创建Platform View的地方
state.platformView = viewFactory.create(context, viewId, createParams);
}
View embeddedView = state.platformView.getView(); // 获取目标View
container.addView(embeddedView); // 开始配置View Tree
rootView = new AccessibilityDelegatingFrameLayout(
getContext(), accessibilityEventsDelegate, embeddedView);
rootView.addView(container);
rootView.addView(state.fakeWindowViewGroup);
embeddedView.setOnFocusChangeListener(focusChangeListener);
rootView.setFocusableInTouchMode(true); // 焦点设置
if (startFocused) {
embeddedView.requestFocus();
} else {
rootView.requestFocus();
}
setContentView(rootView); // 类似Activity的setContentView方法
}
以上逻辑中的create
方法和getView
方法是开发者在实现Platform View
时必须重写的,它们的使用时机(即目标Platform View真正的创建时机)正是在此时。
此外,需要特别注意的是,以上逻辑使用的是自定义的WindowManager
,它将接管Virtual Display
内部的addView
、removeView
、updateViewLayout
等操作,如果不处理,这些操作默认委托给全局的WindowManager
实例。这是为了处理一些特殊情况,比如在某个Platform View
的子View
下方弹出一个PopupWindow
,具体处理细节在此不再赘述。
至此完成Embedder侧逻辑的分析。下面开始分析TextureLayer
的渲染逻辑,如代码清单9-32所示。
// 代码清单9-32 engine/flow/layers/texture_layer.cc
void TextureLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("flutter", "TextureLayer::Paint");
std::shared_ptr<Texture> texture = context.texture_registry.GetTexture(texture_
id_);
if (!texture) { return; } // 没有对应的纹理
texture->Paint(*context.leaf_nodes_canvas, paint_bounds(), freeze_,
context.gr_context, filter_quality_); // 见代码清单9-34
}
以上逻辑十分清晰,主要是根据纹理id
拿到对应的纹理,然后调用其Paint
方法。这里首先分析纹理的注册逻辑,它由代码清单代码9-29所触发,对应的Engine
逻辑如代码清单9-33所示。
// 代码清单9-33 engine/shell/common/shell.cc
void Shell::OnPlatformViewRegisterTexture(std::shared_ptr<flutter::Texture> texture) {
task_runners_.GetRasterTaskRunner()->PostTask( // Raster线程
[rasterizer = rasterizer_->GetWeakPtr(), texture] {
if (rasterizer) {
if (auto* registry = rasterizer->GetTextureRegistry()) {
registry->RegisterTexture(texture); // 纹理注册
} // if
} // if
}); // PostTask
}
以上逻辑在Raster
线程中将目标Texture
注册到TextureRegistry
对象的mapping_
字段中,并在需要绘制时取出。在Android平台中Texture
的具体实现是AndroidExternalTextureGL
,其绘制逻辑如代码清单9-34所示。
// 代码清单9-34 engine/shell/platform/android/android_external_texture_gl.cc
void AndroidExternalTextureGL::Paint( ...... ) {
if (state_ == AttachmentState::detached) { return; }
if (state_ == AttachmentState::uninitialized) { // 首次使用,进行初始化
glGenTextures(1, &texture_name_);
Attach(static_cast<jint>(texture_name_));
state_ = AttachmentState::attached;
}
if (!freeze && new_frame_ready_) {
Update();
new_frame_ready_ = false;
}
GrGLTextureInfo textureInfo = {
GL_TEXTURE_EXTERNAL_OES, texture_name_, GL_RGBA8_OES};
GrBackendTexture backendTexture(1, 1, GrMipMapped::kNo, textureInfo);
sk_sp<SkImage> image = SkImage::MakeFromTexture( // 从纹理生成SkImage实例
context, backendTexture, kTopLeft_GrSurfaceOrigin,
kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr);
if (image) {
SkAutoCanvasRestore autoRestore(&canvas, true);
canvas.translate(bounds.x(), bounds.y());
canvas.scale(bounds.width(), bounds.height());
if (!transform.isIdentity()) { ...... }
SkPaint paint;
paint.setFilterQuality(filter_quality);
canvas.drawImage(image, 0, 0, &paint); // 绘制到Canvas
}
}
以上逻辑即纹理最后的绘制,可以看出底层还是通过Skia的API进行绘制的,具体细节在此不再赘述。
由以上分析可知,用Virtual Display
的方式集成Platform View
对于Flutter的侵入性比较小,Flutter甚至感知不到Platform View
的存在,开发者只是提供了一个 TextureLayer
,至于纹理的提供者是Platform View
还是视频流等其他来源,对Flutter来说是透明的。
Virtual Display
方案是比较符合跨平台思想的,即抹除了Platform自身UI体系的概 念,但也带来了一些奇怪的问题,比如无法响应复杂的手势。对Android端原生的EditText
、WebView
组件而言,它们本身对手势的处理就极其复杂,并且与Android的UI体系强绑定,当被转成一帧一帧的纹理之后,响应这些手势变得几乎不可能,因而出现了第2种方案-Hybrid Composition
。
Hybrid Composition 原理分析
Hybrid Composition
类型的Platform View
在Flutter中的使用示例如代码清单9-35所示。
// 代码清单9-35 PlatformViewLink使用示例
Widget build(BuildContext context) {
final String viewType = '<platform-view-type>'; // Platform View的类型id
final Map<String, dynamic> creationParams = <String, dynamic>{};
// Platform View的参数
return PlatformViewLink(viewType: viewType,
surfaceFactory: (BuildContext context, PlatformViewController controller) {
return AndroidViewSurface(
controller: controller,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
// 手势处理
hitTestBehavior: PlatformViewHitTestBehavior.opaque, // 单击测试的响应方式
);
},
onCreatePlatformView: (PlatformViewCreationParams params) {
return PlatformViewsService.initSurfaceAndroidView(
id: params.id, // 当前Platform View的计数id
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: StandardMessageCodec(),
)..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create(); // create方法的相关逻辑前面内容已详细分析过,在此不再赘述
},
);}
以上逻辑中,PlatformViewLink
对应的RenderObject
为PlatformViewRenderBox
,其绘制逻辑如代码清单9-36所示。
// 代码清单9-36 flutter/packages/flutter/lib/src/rendering/platform_view.dart
// PlatformViewRenderBox
void paint(PaintingContext context, Offset offset) {
assert(_controller.viewId != null);
context.addLayer(PlatformViewLayer(
rect: offset & size,
viewId: _controller.viewId,
));
}
以上逻辑主要是添加一个Layer
图层,对PlatformViewLayer
来说,计数id
是必不可少的,这是因为最后PlatformViewRenderBox
对应的绘制内容其实就是原生的View
,需要一个id
一一对应。
下面分析viewId
的生成。由代码清单9-35可知,该id
由onCreatePlatformView
决定,因此首先分析该函数的调用点,即_PlatformViewLinkState
的_initialize
方法,如代码清单9-37所示。
// 代码清单9-37 flutter/packages/flutter/lib/src/widgets/platform_view.dart
void _initialize() { // _PlatformViewLinkState
_id = platformViewsRegistry.getNextPlatformViewId(); // 计数加1
_controller = widget._onCreatePlatformView( // 开始真正创建Platform View
PlatformViewCreationParams._(
id: _id!, // 计数id
viewType: widget.viewType, // 类型id
onPlatformViewCreated: _onPlatformViewCreated,
onFocusChanged: _handlePlatformFocusChanged,
),
); // _onCreatePlatformView
}
void _onPlatformViewCreated(int id) {
setState(() { _platformViewCreated = true; });
}
Hybrid Composition
的流程和Virtual Display
相差很大,下面从Engine
和Embedder
两个阶段进行分析。
1. Engine处理阶段
首先分析PlatformLayer在Engine中的绘制逻辑。Flutter的渲染管道,在Hybrid Composition模式下,以下几个逻辑关系到Platform View的最终渲染:
- DrawToSurface方法中触发的BeginFrame方法
- Raster方法中触发的Preroll方法
- Raster方法中触发的PostPrerollAction方法
- Raster方法中触发的Paint方法
- DrawToSurface方法中触发的SubmitFrame方法
下面依次分析。
第1步,分析BeginFrame方法的逻辑,如代码清单9-38所示。
// 代码清单9-38 engine/shell/platform/android/external_view_embedder/external_view_embedder.cc
void AndroidExternalViewEmbedder::BeginFrame( ...... ) { // 开始渲染一帧
Reset();
if (frame_size_ != frame_size && raster_thread_merger->IsOnPlatformThread()) {
surface_pool_->DestroyLayers(jni_facade_);
}
surface_pool_->SetFrameSize(frame_size);
if (raster_thread_merger->IsOnPlatformThread()) {
jni_facade_->FlutterViewBeginFrame(); // 见代码清单9-39
}
frame_size_ = frame_size;
device_pixel_ratio_ = device_pixel_ratio;
}
void AndroidExternalViewEmbedder::Reset() {
previous_frame_view_count_ = composition_order_.size();
composition_order_.clear();
picture_recorders_.clear();
}
以上逻辑主要是在开始一帧的渲染前清理字段,并通知Embedder
,如代码清单9-39所示。
// 代码清单9-39 engine/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
public void onBeginFrame() {
currentFrameUsedOverlayLayerIds.clear(); // 存储覆盖在Platform View之上的Flutter UI
currentFrameUsedPlatformViewIds.clear(); // 存储当前帧的Platform View
}
Embedder
中的onBeginFrame
方法也用于清理字段,在此不再赘述。
第2步,分析PlatformViewLayer
的Preroll
方法,如代码清单9-40所示。
// 代码清单9-40 engine/flow/layers/platform_view_layer.cc
void PlatformViewLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
// SKIP LEGACY_FUCHSIA_EMBEDDER相关逻辑
set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(),
size_.height()));
if (context->view_embedder == nullptr) { return; }
context->has_platform_view = true; // 标记当前一帧存在Platform View
std::unique_ptr<EmbeddedViewParams> params =
std::make_unique<EmbeddedViewParams>(matrix, size_, context->mutators_stack);
context->view_embedder->PrerollCompositeEmbeddedView(view_id_, std::move(params));
}
以上逻辑最终将调用AndroidExternalViewEmbedder
的PrerollCompositeEmbeddedView
方法,如代码清单9-41所示。
// 代码清单9-41 engine/shell/platform/android/external_view_embedder/external_view_embedder.cc
void AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView(
int view_id, std::unique_ptr<EmbeddedViewParams> params) {
auto rtree_factory = RTreeFactory();
view_rtrees_.insert_or_assign(view_id, rtree_factory.getInstance());
// 新建一个RTree
auto picture_recorder = std::make_unique<SkPictureRecorder>();
picture_recorder->beginRecording(SkRect::Make(frame_size_), &rtree_factory);
picture_recorders_.insert_or_assign(view_id, std::move(picture_recorder));
composition_order_.push_back(view_id);
if (view_params_.count(view_id) == 1 && view_params_.at(view_id) == *params.
get()) {
return;
}
view_params_.insert_or_assign(view_id, EmbeddedViewParams(*params.get()));
}
以上逻辑仍是开始绘制前的相关变量的准备工作,最终目的是更新view_params_
,该字段包含Platform 在这里插入代码片
View的大小、位置、图层操作等信息。
第3步,分析PostPrerollAction
的逻辑,如代码清单9-42所示。
// 代码清单9-42 engine/shell/platform/android/external_view_embedder/external_view_embedder.cc
PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction(
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
if (!FrameHasPlatformLayers()) { // 没有Platform View则不需要进行处理
return PostPrerollResult::kSuccess;
}
if (!raster_thread_merger->IsMerged()) { // 线程尚未合并,详见10.2节
raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration);
// 见代码清单10-19
CancelFrame(); // 取消绘制当前帧
return PostPrerollResult::kSkipAndRetryFrame; // 合并完成后重新绘制
} // 如果当前帧有Platform View,则延长线程合并的时间
raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration);
// 见代码清单10-23
if (previous_frame_view_count_ == 0) {
return PostPrerollResult::kResubmitFrame;
}
return PostPrerollResult::kSuccess;
}
以上逻辑主要是线程合并相关的工作,相关细节将在10.2节详细分析,这里仅介绍其必要性:Flutter的UI最终绘制于Raster
线程,而Hybrid Composition
模式下的Platform View
是原生View,需要绘制于Platform
线程,由于两者需要同帧绘制,因此Flutter会做出牺牲,将自身UI也通过Platform
线程进行绘制。10.2节将从源码的角度分析实现以上能力的动态线程合并技术,在此不再赘述。
第4步,分析PlatformViewLayer
的Paint
方法,如代码清单9-43所示。
// 代码清单9-43 engine/flow/layers/platform_view_layer.cc
void PlatformViewLayer::Paint(PaintContext& context) const {
if (context.view_embedder == nullptr) { return; }
SkCanvas* canvas = context.view_embedder->CompositeEmbeddedView(view_id_);
context.leaf_nodes_canvas = canvas; // 记录PlatformViewLayer需要使用的Canvas
}
以上逻辑最终将调用AndroidExternalViewEmbedder的CompositeEmbeddedView方法,如代码清单9-44所示。
代码清单9-44 engine/shell/platform/android/external_view_embedder/external_view_embedder.cc
SkCanvas* AndroidExternalViewEmbedder::CompositeEmbeddedView(int view_id) {
if (picture_recorders_.count(view_id) == 1) {
return picture_recorders_.at(view_id)->getRecordingCanvas();
}
return nullptr;
}
以上逻辑将从picture_recorders_
中取出当前view_id
对应的SkCanvas
,而其创建逻辑在代码清单9-41中。
第5步,如果当前帧包含Platform View
,则将触发AndroidExternalViewEmbedder
的SubmitFrame
方法,如代码清单9-45所示。
// 代码清单9-45 shell/platform/android/external_view_embedder/external_view_embedder.cc
void AndroidExternalViewEmbedder::SubmitFrame(
GrDirectContext* context, std::unique_ptr<SurfaceFrame> frame,
const std::shared_ptr<fml::SyncSwitch>& gpu_disable_sync_switch) {
if (!FrameHasPlatformLayers()) {
frame->Submit(); // 没有Platform View,使用SurfaceFrame的Submit方法
return;
}
std::unordered_map<int64_t, std::list<SkRect>> overlay_layers;
std::unordered_map<int64_t, sk_sp<SkPicture>> pictures;
SkCanvas* background_canvas = frame->SkiaCanvas();
auto current_frame_view_count = composition_order_.size(); // 在代码清单9-41中注册
SkAutoCanvasRestore save(background_canvas, /*doSave=*/true);
for (size_t i = 0; i < current_frame_view_count; i++) {
int64_t view_id = composition_order_[i]; // 遍历每个Platform View
sk_sp<SkPicture> picture = picture_recorders_.at(view_id)->
finishRecordingAsPicture();
pictures.insert({view_id, picture});
overlay_layers.insert({view_id, {}});
sk_sp<RTree> rtree = view_rtrees_.at(view_id);
// 查找Flutter Widget和Platform View有交集的元素,见代码清单9-46
background_canvas->drawPicture(pictures.at(view_id));
}
// 见代码清单9-47
}
以上逻辑主要是遍历当前帧的所有PlatformViewLayer
图层,并将其绘制在background_canvas
上,对于与Platform View
有交集的Flutter UI元素,将进行合并,如代码清单9-46所示。
// 代码清单9-46 shell/platform/android/external_view_embedder/external_view_embedder.cc
for (ssize_t j = i; j >= 0; j--) {
int64_t current_view_id = composition_order_[j];
SkRect current_view_rect = GetViewRect(current_view_id);
std::list<SkRect> intersection_rects = // 通过RTree寻找有交集的UI元素
rtree->searchNonOverlappingDrawnRects(current_view_rect);
auto allocation_size = intersection_rects.size();
if (allocation_size > kMaxLayerAllocations) { // 合并以减少覆盖原生View导致的独立图层
SkRect joined_rect;
for (const SkRect& rect : intersection_rects) {
joined_rect.join(rect);
}
intersection_rects.clear();
intersection_rects.push_back(joined_rect); // 合并Flutter UI元素
} // if
for (SkRect& intersection_rect : intersection_rects) {
intersection_rect.set(intersection_rect.roundOut());
overlay_layers.at(view_id).push_back(intersection_rect);
// 记录在overlay_layers中
background_canvas->clipRect(intersection_rect, SkClipOp::kDifference);
} // for,同步对background_canvas的影响
} // for
以上逻辑主要是找到Flutter
和Platform View
中UI元素有交集的绘制节点,并将这些节点转换为独立的原生View进行绘制,之所以这么做是为了避免以下情况:Flutter
中的Platform View
上下都有Flutter UI
元素,此时如果简单地分为一个Flutter UI
图层和一个Platform View
图层,那么最终渲染的效果将改变,这就是混合渲染中常见的z-order
问题,后面内容将以图9-5为例具体分析。
此外,以上逻辑将通过kMaxLayerAllocations
控制图层数量,即如果有多个Flutter UI
位于Platform View
之上,它们将共用一个原生View作为图层,这样可以避免资源的浪费。
图9-5 Platform View示意
由于在Hybrid Composition模式下,大部分Flutter UI都将转换为对应的原生View,因此通过Layout Inspector工具进行观察是一种非常合适的方法。在图9-5中,存在一个Platform View,即图9-5中左上角区域,其大小为200×200。该Platform View由一个LinearLayout和一个TextView(图9-5中底部的黑色方块,大小为130×130)组成。其余6个方块大小均为100,位置如图9-5所示。对方块1而言,虽然和Platform View有交集,但是从z轴看仍然可以视为全局Flutter UI的一部分,故将处于background类型的FlutterImageView(即图9-5左边Component Tree的第一个FlutterImageView中)。对方块2、方块3(半透明)而言,它们与Platform View有交集,且从z轴上看会覆盖在Platform View上方,故会被渲染在一个overlay类型的FlutterImageView上。对方块4、方块5而言,它们也将被放置到一个独立的FlutterImageView中。而对于方块6,它虽然位于方块5的上方,但是本身与Platform View没有任何交集,因此会和方块1一起位于background类型的FlutterImageView中。
此外,图9-5还涉及3个细节。一是整个Component Tree的结构暗合了第4章的分析,即FlutterSplashView、FlutterView、FlutterSurfaceView依次组织嵌套。二是证实了Platform View确实位于FlutterMutatorView之内,后面将详细剖析。三是方块5和方块6的覆盖问题:在Flutter UI中,方块5和方块6对应的Widget依次放在Stack中,故方块6应该覆盖在方块5之上(图9-5中方块6上的黑色方框是后期标注的),实际渲染结果也确实如此。但是,考虑前面内容分析可知,方块5所在的FlutterImageView是位于方块6所在的FlutterImageView之上的,因为它们的容器FlutterView是FrameLayout的子类。但是,方块6却正确覆盖了方框5,这说明Flutter Engine在绘制期间做了处理,没有绘制这块区域(由代码清单9-48也可窥见一二)。由此可以想象,Hybrid Composition模式下的Platform View是非常复杂和消耗性能的,因为要做很多额外的计算。
下面分析Flutter Engine中最后的渲染逻辑,如代码清单9-47所示。
// 代码清单9-47 shell/platform/android/external_view_embedder/external_view_embedder.cc
auto should_submit_current_frame = previous_frame_view_count_ > 0;
if (should_submit_current_frame) { // 最后再检查一次
frame->Submit();
}
for (int64_t view_id : composition_order_) { // 处理每个PlatformViewLayer
SkRect view_rect = GetViewRect(view_id);
const EmbeddedViewParams& params = view_params_.at(view_id);
jni_facade_->FlutterViewOnDisplayPlatformView( // 见代码清单9-50
view_id, // 计数id
view_rect.x(), view_rect.y(), // 位置
view_rect.width(), view_rect.height(), // 大小
params.sizePoints().width() * device_pixel_ratio_,
params.sizePoints().height() * device_pixel_ratio_,
params.mutatorsStack()); // 各种Layer操作,比如Embedder将在Draw阶段进行裁剪
for (const SkRect& overlay_rect : overlay_layers.at(view_id)) {
std::unique_ptr<SurfaceFrame> frame = CreateSurfaceIfNeeded( // 见代码清单9-48
context, view_id, pictures.at(view_id), overlay_rect);
if (should_submit_current_frame) { frame->Submit(); }
}
}
以上逻辑主要是调用Embedder
的方法并携带必要的参数进行真正的渲染。其中,CreateSurfaceIfNeeded
方法的具体逻辑如代码清单9-48所示。
// 代码清单9-48 shell/platform/android/external_view_embedder/external_view_embedder.cc
std::unique_ptr<SurfaceFrame> AndroidExternalViewEmbedder::CreateSurfaceIfNeeded(
GrDirectContext* context, int64_t view_id, sk_sp<SkPicture> picture, const
SkRect& rect) {
std::shared_ptr<OverlayLayer> layer = // 见代码清单9-57的createOverlaySurface
surface_pool_->GetLayer(context, android_context_, jni_facade_,
surface_factory_);
std::unique_ptr<SurfaceFrame> frame = layer->surface->AcquireFrame(frame_size_);
jni_facade_->FlutterViewDisplayOverlaySurface(
// 见代码清单9-57的onDisplayOverlaySurface
layer->id, rect.x(), rect.y(), rect.width(), rect.height());
// 计数id、位置、宽高等信息
SkCanvas* overlay_canvas = frame->SkiaCanvas();
overlay_canvas->clear(SK_ColorTRANSPARENT); // 默认背景是透明的
overlay_canvas->translate(-rect.x(), -rect.y()); // 从目标位置开始绘制
overlay_canvas->drawPicture(picture); // 在Flutter Engine中绘制将通过原始组件显示
return frame;
}
2. Embedder处理阶段
在继续前面内容的分析之前,首先分析Platform View
的创建逻辑,由代码清单9-27可知,对应的创建逻辑为createAndroidViewForPlatformView
方法,如代码清单9-49所示。
//代码清单9-49 engine/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
@Override
public void createAndroidViewForPlatformView(
@NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT);
if (!validateDirection(request.direction)) { ...... }
final PlatformViewFactory factory = registry.getFactory(request.viewType);
if (factory == null) { ...... }
Object createParams = null;
if (request.params != null) {
createParams = factory.getCreateArgsCodec().decodeMessage(request.params);
} // 创建Platform View
final PlatformView platformView = factory.create(context, request.viewId,
createParams);
platformViews.put(request.viewId, platformView); // 以计数id作为索引存储
}
以上逻辑直接完成Platform View
的创建,并保存在platformViews
字段中。接下来分析onDisplayPlatformView
方法,如代码清单9-50所示。
// 代码清单9-50 engine/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
public void onDisplayPlatformView( ...... ) {
initializeRootImageViewIfNeeded(); // 第1步,见代码清单9-51
initializePlatformViewIfNeeded(viewId); // 第2步,见代码清单9-55
final FlutterMutatorView parentView = platformViewParent.get(viewId);
parentView.readyToDisplay(mutatorsStack, x, y, width, height);
//第3步,见代码清单9-56
parentView.setVisibility(View.VISIBLE);
parentView.bringToFront();
final FrameLayout.LayoutParams layoutParams =
new FrameLayout.LayoutParams(viewWidth, viewHeight);
final View view = platformViews.get(viewId).getView();
if (view != null) { // 设置大小信息
view.setLayoutParams(layoutParams);
view.bringToFront();
}
currentFrameUsedPlatformViewIds.add(viewId);
}
以上逻辑主要分为3步,后面依次分析。第1步,将当前用于渲染Flutter UI
的FlutterSurfaceView
转换为FlutterImageView
,Hybrid Composition
模式下将使用后者渲染Flutter UI
。具体初始化逻辑如代码清单9-51所示。
// 代码清单9-51 engine/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
private void initializeRootImageViewIfNeeded() {
if (!flutterViewConvertedToImageView) {
((FlutterView) flutterView).convertToImageView();
flutterViewConvertedToImageView = true;
}
}
在继续解读源码之前,首先分析为什么要转换为ImageView
,主要是因为SurfaceView
不能作为Android中View Tree
的一个节点,它无法和其他Platform View
混合,而TextureView
有自己的职责,不太适合同时承担Platform View
的这部分逻辑,需要独立出一个FlutterImageView
,专门用于此类场景的渲染。具体逻辑如代码清单9-52所示。
// 代码清单9-52 engine/shell/platform/android/io/flutter/embedding/android/FlutterView.java
public void convertToImageView() {
renderSurface.pause();
if (flutterImageView == null) { // 创建
flutterImageView = createImageView();
addView(flutterImageView);
} else {
flutterImageView.resizeIfNeeded(getWidth(), getHeight());
}
previousRenderSurface = renderSurface; // 用于恢复正常渲染模式
renderSurface = flutterImageView;
if (flutterEngine != null) { // 见代码清单9-53
renderSurface.attachToRenderer(flutterEngine.getRenderer());
}
}
public FlutterImageView createImageView() {
return new FlutterImageView(getContext(),
getWidth(), getHeight(), FlutterImageView.SurfaceKind.background);
}
以上逻辑首先创建一个FlutterImageView
实例并加入当前View Tree
,其次通过attachToRenderer
方法告知Engine
侧FlutterRenderer
发生了改变,具体逻辑如代码清单9-53所示。
// 代码清单9-53 engine/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java
@Override // FlutterImageView
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
if (isAttachedToFlutterRenderer) { return; }
switch (kind) {
case background:
flutterRenderer.swapSurface(imageReader.getSurface()); // 见代码清单9-54
break;
case overlay: // 将由FlutterJNI的createOverlaySurface方法处理
break;
}
setAlpha(1.0f);
this.flutterRenderer = flutterRenderer;
isAttachedToFlutterRenderer = true;
}
以上逻辑最终将调用Engine
侧的NotifySurfaceWindowChanged
方法,如代码清单9-54所示。
// 代码清单9-54 engine/shell/platform/android/platform_view_android.cc
void PlatformViewAndroid::NotifySurfaceWindowChanged(
fml::RefPtr<AndroidNativeWindow> native_window) { // 新的渲染输出
if (android_surface_) {
fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetRasterTaskRunner(), // Raster线程
[&latch, surface = android_surface_.get(),
native_window = std::move(native_window)]() {
surface->TeardownOnScreenContext();
surface->SetNativeWindow(native_window); // 设置新的渲染输出
latch.Signal();
});
latch.Wait();
}
}
至此,Flutter渲染的Surface
将由代码清单9-53中的imageReader
接管。接下来分析代码清单9-50中的第2步,如代码清单9-55所示。
// 代码清单9-55 engine/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
void initializePlatformViewIfNeeded(int viewId) {
final PlatformView platformView = platformViews.get(viewId);
if (platformView == null) { ...... }
if (platformViewParent.get(viewId) != null) {return; }
if (platformView.getView() == null) { ...... }
if (platformView.getView().getParent() != null) { ...... } // 各种异常情况的检查
final FlutterMutatorView parentView = new FlutterMutatorView(context,
context.getResources().getDisplayMetrics().density, androidTouchProcessor);
platformViewParent.put(viewId, parentView);
parentView.addView(platformView.getView()); // 包装Platform View
((FlutterView) flutterView).addView(parentView);
// 加入FlutterView,层级在Flutter UI之上
}
以上逻辑主要是将Platform View
放入FlutterMutatorView
中,后者统一在Draw
阶段执行Flutter
对PlatformViewLink
这个Widget
的裁剪等操作,具体逻辑见代码清单9-63。最后,将FlutterMutatorView
加入FlutterView
中。
继续代码清单9-50中第3步的分析,如代码清单9-56所示。
// 代码清单9-56 engine/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java
public void readyToDisplay( // FlutterMutatorView
@NonNull FlutterMutatorsStack mutatorsStack, int left, int top, int width,
int height) {
this.mutatorsStack = mutatorsStack;
this.left = left;
this.top = top;
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width,
height);
layoutParams.leftMargin = left;
layoutParams.topMargin = top;
setLayoutParams(layoutParams);
setWillNotDraw(false);
}
以上逻辑进一步初始化代码清单9-55中创建的FlutterMutatorView
,完成位置信息、大小的赋值,以备后续渲染。
至此,代码清单9-47中的FlutterViewOnDisplayPlatformView
方法所引发的逻辑全部完成。
如果Flutter UI
和Platform View
有覆盖重叠部分,将触发CreateSurfaceIfNeeded
方法对应的Embedder
逻辑,如代码清单9-57所示。
// 代码清单9-57 engine/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
@TargetApi(19) // PlatformViewsController,见代码清单9-48
public FlutterOverlaySurface createOverlaySurface() {
return createOverlaySurface(
new FlutterImageView(
flutterView.getContext()
// Overlay Surface的大小默认与背景FlutterView的大小一致
flutterView.getWidth(), flutterView.getHeight(),
FlutterImageView.SurfaceKind.overlay));
} // 封装并存储在overlayLayerViews字段,在渲染阶段取出
public FlutterOverlaySurface createOverlaySurface(@NonNull FlutterImageView imageView) {
final int id = nextOverlayLayerId++;
overlayLayerViews.put(id, imageView);
return new FlutterOverlaySurface(id, imageView.getSurface());
}
public void onDisplayOverlaySurface(int id, int x, int y, int width, int height) { // 真正显示
initializeRootImageViewIfNeeded(); // 见代码清单9-51
final FlutterImageView overlayView = overlayLayerViews.get(id);
if (overlayView.getParent() == null) { ((FlutterView) flutterView).addView
(overlayView); }
FrameLayout.LayoutParams layoutParams = // 真实的大小,如图9-5所示
new FrameLayout.LayoutParams((int) width, (int) height);
layoutParams.leftMargin = (int) x; // 位置信息
layoutParams.topMargin = (int) y;
overlayView.setLayoutParams(layoutParams);
overlayView.setVisibility(View.VISIBLE);
overlayView.bringToFront();
currentFrameUsedOverlayLayerIds.add(id);
}
以上逻辑主要是创建一个FlutterImageView
,用于与Platform View
发生重叠的Flutter UI
的渲染,它们将在后面内容的渲染阶段通过overlayLayerViews
字段进行最终的上屏操作。
由代码清单5-99可知,在Rasterizer::Draw
方法的最后阶段将调用EndFrame
,其对应的Embedder逻辑如代码清单9-58所示。
// 代码清单9-58 engine/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
public void onEndFrame() { // PlatformViewsController
final FlutterView view = (FlutterView) flutterView;
if (flutterViewConvertedToImageView && currentFrameUsedPlatformViewIds.isEmpty()) {
flutterViewConvertedToImageView = false; // 如果没有Platform View,则复原
view.revertImageView( () -> { finishFrame(false); }); // 见代码清单9-59
return;
}
final boolean isFrameRenderedUsingImageReaders =
flutterViewConvertedToImageView && view.acquireLatestImageViewFrame();
//见代码清单9-60
finishFrame(isFrameRenderedUsingImageReaders);
}
public boolean acquireLatestImageViewFrame() {
if (flutterImageView != null) {
return flutterImageView.acquireLatestImage();
}
return false;
}
以上逻辑中,首先判断当前是否使用ImageReader
进行Flutter UI
的渲染,无论结果如何,最终都将通过finishFrame
方法完成渲染,如代码清单9-59所示。
// 代码清单9-59 engine/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
private void finishFrame(boolean isFrameRenderedUsingImageReaders) {
for (int i = 0; i < overlayLayerViews.size(); i++) {
// 处理与Platform View有交集的Flutter UI
final int overlayId = overlayLayerViews.keyAt(i);
final FlutterImageView overlayView = overlayLayerViews.valueAt(i);
if (currentFrameUsedOverlayLayerIds.contains(overlayId)) { // 需要在当前帧渲染
((FlutterView) flutterView).attachOverlaySurfaceToRender(overlayView);
// 绑定
final boolean didAcquireOverlaySurfaceImage = // 可以从Surface中取得数据
overlayView.acquireLatestImage(); // 见代码清单9-60
isFrameRenderedUsingImageReaders &= didAcquireOverlaySurfaceImage; // 更新
} else {
if (!flutterViewConvertedToImageView) {
overlayView.detachFromRenderer();
}
overlayView.setVisibility(View.GONE);
} // if
} // for
for (int i = 0; i < platformViewParent.size(); i++) {
final int viewId = platformViewParent.keyAt(i);
final View parentView = platformViewParent.get(viewId);
if (isFrameRenderedUsingImageReaders &&
currentFrameUsedPlatformViewIds.contains(viewId)) {
parentView.setVisibility(View.VISIBLE);
} else {
parentView.setVisibility(View.GONE);
}
}
}
以上逻辑主要是更新FlutterImageView
和FlutterMutatorView
的可见性,其核心逻辑在于通过调用acquireLatestImage
方法进行判断,如代码清单9-60所示。
// 代码清单9-60 engine/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java
public boolean acquireLatestImage() {
if (!isAttachedToFlutterRenderer) { return false; }
int imageOpenedCount = imageQueue.size();
if (currentImage != null) { imageOpenedCount++; }
if (imageOpenedCount < imageReader.getMaxImages()) {
final Image image = imageReader.acquireLatestImage();
if (image != null) { imageQueue.add(image); } // 从imageReader中获取最新的Image
}
invalidate(); // 触发View刷新,见代码清单9-61和代码清单9-63
return !imageQueue.isEmpty(); // 获取成功
}
以上逻辑主要通过调用系统API判断Flutter UI
的渲染输出imageReader
是否有最新的Image
数据可用于渲染,底层细节不再赘述。invalidate
方法将触发页面的刷新。
// 代码清单9-61 engine/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java
@Override // FlutterImageView
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!imageQueue.isEmpty()) {
if (currentImage != null) { currentImage.close(); }
currentImage = imageQueue.poll();
updateCurrentBitmap(); // 见代码清单9-62
}
if (currentBitmap != null) {
canvas.drawBitmap(currentBitmap, 0, 0, null); // 绘制Bitmap
}
}
以上逻辑的核心在updateCurrentBitmap
中,如代码清单9-62所示。
// 代码清单9-62 engine/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java
private void updateCurrentBitmap() {
if (android.os.Build.VERSION.SDK_INT >= 29) {
final HardwareBuffer buffer = currentImage.getHardwareBuffer();
currentBitmap = Bitmap.wrapHardwareBuffer( // HardwareBuffer
buffer, ColorSpace.get(ColorSpace.Named.SRGB));
buffer.close();
} else { // Android 10以下版本的系统中,内存拷贝
final Plane[] imagePlanes = currentImage.getPlanes();
if (imagePlanes.length != 1) { return; }
final Plane imagePlane = imagePlanes[0];
final int desiredWidth = imagePlane.getRowStride() / imagePlane.getPixelStride();
final int desiredHeight = currentImage.getHeight();
if (currentBitmap == null || currentBitmap.getWidth() != desiredWidth
|| currentBitmap.getHeight() != desiredHeight) {
currentBitmap = Bitmap.createBitmap( // 创建Bitmap,将占用对应大小的内存空间
desiredWidth, desiredHeight, android.graphics.Bitmap.Config.ARGB_8888);
}
currentBitmap.copyPixelsFromBuffer(imagePlane.getBuffer()); // 内存拷贝
}
}
以上逻辑主要是更新currentBitmap
字段,它将在Draw
阶段绘制于Canvas
上,对于Android 10
以上版本的系统中,将通过硬件加速将Flutter UI
数据更新到currentBitmap
中;对于Android 10
以下版本的系统中,将通过内存拷贝完成。
FlutterMutatorView
的draw
方法如代码清单9-63所示。
// 代码清单9-63 engine/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java
@Override
public void draw(Canvas canvas) {
canvas.save();
for (Path path : mutatorsStack.getFinalClippingPaths()) {
Path pathCopy = new Path(path);
pathCopy.offset(-left, -top);
canvas.clipPath(pathCopy);
}
super.draw(canvas);
canvas.restore();
}
以上逻辑主要是在Platform View
开始绘制前执行Flutter
通过Widget
属性等方式对Platform View
所施加的裁剪操作等行为。
至此完成Hybrid Composition
的分析。相较于Virtual Display
,Hybrid Composition
由于使用了原生View,EditText
、WebView
这类组件的手势交互将变得不再困难,但由以上分析也可以推测,由于Draw
阶段的Bitmap
拷贝,Hybrid Composition
模式下的渲染性能将大大降低,特别是Android 10
以下版本的系统中。
基于以上分析,开发者应该在使用Platform View
时审慎、合理地选用恰当的方案。
Plugin原理分析
Platform Channel
和Platform View
基本解决了Flutter复用原生平台逻辑和UI的问题,但是,考虑到Flutter Engine
及其所依赖的宿主(Activity
或者Fragment
)都有自己独立的生命周期,Platform Channel
和Platform View
往往需要在合适的时机(可粗略地认为是创建后,销毁前,即onCreate
和onDestroy
两个回调中间)进行,如果由开发者自行管理,不仅工作量巨大,质量也可能良莠不齐。因此,Plugin的一大功能就是提供响应各种组件生命周期的回调入口。此外,Plugin也是flutter tool
所支持的复用Flutter和Embedder混合代码的方式,Flutter的官方仓库提供了flutter_webview
等Plugin,其本质就是对WebView
等原生能力的封装,为逻辑和UI的复用提供了可行的方案。
Plugin的注册入口是GeneratedPluginRegister
的registerGeneratedPlugins
方法,如代码清单9-64所示。
// 代码清单9-64 engine/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java
public static void registerGeneratedPlugins(@NonNull FlutterEngine flutterEngine) {
try {
Class<?> generatedPluginRegistrant =
Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
Method registrationMethod =
generatedPluginRegistrant.getDeclaredMethod("registerWith",
FlutterEngine.class);
registrationMethod.invoke(null, flutterEngine); // 反射调用flutter tool生产的类
} catch (Exception e) { ...... }}
以上逻辑将通过反射的方式调用GeneratedPluginRegistrant
的registerWith
方法,该类由flutter tool
生成,如代码清单9-65所示。
// 代码清单9-65 flutter tool为插件类生成的注册逻辑
@Keep // 避免混淆
public final class GeneratedPluginRegistrant {
public GeneratedPluginRegistrant() {}
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
flutterEngine.getPlugins().add(new AndroidPlatformImagesPlugin());
}
}
由以上逻辑可知,插件注册的本质是调用PluginRegistry
接口的add
方法,该接口的实现类为FlutterEngineConnectionRegistry
,其add
方法如代码清单9-66所示。
// 代码清单9-66 engine/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java
@Override // FlutterEngineConnectionRegistry
public void add(@NonNull FlutterPlugin plugin) {
if (has(plugin.getClass())) { return; } // 如果已经注册过,则直接返回
plugins.put(plugin.getClass(), plugin);
plugin.onAttachedToEngine(pluginBinding);
// 通过本回调向插件提供Embedder和Engine的资源
if (plugin instanceof ActivityAware) {
// 插件实现了该接口,能够响应Activity的生命周期回调
ActivityAware activityAware = (ActivityAware) plugin;
activityAwarePlugins.put(plugin.getClass(), activityAware);
if (isAttachedToActivity()) {
activityAware.onAttachedToActivity(activityPluginBinding);
// 通过本回调提供Activity资源
}
}
// SKIP ServiceAware、BroadcastReceiverAware、ContentProviderAware的绑定,逻辑同上
}
以上逻辑中首先通过plugins
字段存储要注册的FlutterPlugin
实例,然后判断FlutterPlugin
对象是否实现ActivityAware
等接口,并存储到对应字段中。顾名思义,ActivityAware
接口提供了当前插件所依赖的Activity
的各种生命周期回调,ServiceAware
等接口的功能与此类似,在此不再赘述。
以ActivityAware
接口为例,当宿主FlutterActivity
的onDestroy
回调触发时通过Delegate
调用FlutterEngineConnectionRegistry
对象的detachFromActivity
方法,如代码清单9-67所示。
代码清单9-67 engine/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnection Registry.java
@Override
public void detachFromActivity() { // FlutterEngineConnectionRegistry
if (isAttachedToActivity()) { // 遍历实现了ActivityAware接口的插件
for (ActivityAware activityAware : activityAwarePlugins.values()) {
activityAware.onDetachedFromActivity(); // 调用对应的回调
}
detachFromActivityInternal();
} else { ...... } // 无须处理
}
private void detachFromActivityInternal() { // 插件自身的清理工作
flutterEngine.getPlatformViewsController().detach();
exclusiveActivity = null;
activity = null;
activityPluginBinding = null;
}
以上逻辑主要是通知实现了ActivityAware
接口的插件,并进行全局清理。
至此,Plugin的逻辑分析完毕,Plugin本身没有什么特殊目的,可以认为是谷歌公司的工程师为Flutter和Embedder混合开发提供的一套脚手架,其特点是定义了明确的接口规范和可复用的工程组织方式。
FlutterActivity启动流程
Flutter热更新探索
主要思路:自定义FlutterLoader,反射修改libapp.so的存储路径的变量
保证在initConfig
之后,ensureInitializationComplete
之前修改上面这个值。
参考:
- platform-channels
- Using Flutter’s MethodChannel to invoke Kotlin code for Android
- Hosting native Android views in your Flutter app with Platform Views
- Flutter PlatformView: How to host native android and iOS view in Flutter
- plugin-api-migration
- 《Flutter内核源码剖析》