Flutter 笔记 | Flutter Native 插件开发 (Android)

news2024/12/29 8:45:05

oh, 我亲爱的朋友,很高兴你来到了这里!既然来了,那么就让我们在这篇糟糕的烂文章中,一起来学习一下,如何在一个糟糕的 Flutter 混合应用中开发一个糟糕的 Android Native 烂插件吧!😑

首先,先考虑第一个问题:混合开发中如何将Flutter集成到现有的Android应用中呢?

主要步骤:

  1. 首先,创建Flutter module;
  2. 为已存在的Android应用添加Flutter module依赖;
  3. 在Koltin中调用Flutter module;
  4. 编写Dart代码;
  5. 运行项目;
  6. 热重启/重新加载;
  7. 调试Dart代码;
  8. 发布应用;

在 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')

setBindingevaluate 允许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'
		}
    }
}

注意:

  1. Flutter v1.17 版本开始,Flutter module仅仅支持 AndroidX 的应用。
  2. release 模式下Flutter仅支持以下架构:x86_64,armeabi-v7a,arm64-v8a,不支持mipsx86;所以引入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 项目的时候,如果发现重启/重新加载不起作用了,那在混合开发中怎么启用重启/重新加载呢:

  1. 手机连接我们的 电脑
  2. 关闭我们的App应用;然后运行 flutter attach; (在对应的 flutter_module 项目根路径)
    注意:如果你同时有多个模拟器或连接的设备,运行flutter attach会提示你选择一个设备,接下来我们需要flutter attach -d 设备ID 来指定一个设备:如flutter attach -d emulator-5554
  3. 当出现 “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 代码

在混合开发模式下, 如何更好的调试我们的代码:

  1. 关闭App(这一步很关键)
  2. 点击 Android Studio 的 Flutter Attch 按钮(前提是 安装过flutter 与 dart 插件)
  3. 打上断点,启动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
    }
} 

其中可以看到,缓存的keymoduleName,刚好供之后具体模块的使用。

【预加载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当中调用HiFlutterCacheManagerpreLoad方法预加载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 系统上使用 MethodChannelAndroidiOS 系统使用 MethodChanneliOS 来接收和返回来自 MethodChannel 的方法调用。在开发平台插件的时候,可以减少样板代码。

Platform Channel支持的数据类型

标准平台通道使用标准消息编解码器,它支持简单的类似 JSON 值的高效二进制序列化,例如布尔值、数字、字符串、字节缓冲区及这些类型的列表和映射(详情请参阅 StandardMessageCodec)。当你发送和接收值时,它会自动对这些值进行序列化和反序列化。

以下是Platform Channel支持的kotlinDart的对应数据类型:

在这里插入图片描述

其他语言请参考 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 方法替换为包含以字符串形式显示电池状态、并包含一个用于刷新该值的按钮的小型用户界面。


Widget 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), // 显示电池状态
        ],
      ),
    ),
  );
}

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 实例渲染为纹理,因此它不会嵌入到 Android Activity 的视图层次结构中。某些平台交互(例如键盘处理和辅助功能)可能无法正常工作。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中注册,也可以在插件中注册

  1. 如果要在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())
    }
}
  1. 如果要在插件中进行注册,修改您插件的主类 (例如: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,请执行下列操作:

  1. 添加下面的导入代码:
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';
  1. 实现 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,请执行下列操作:

  1. 添加下面的导入代码:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
  1. 实现 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 中创建使用 WebViewWidget代码:

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);
  }
}

在这段代码中,我们返回基于平台的AndroidViewUiKitView。(有关它们的完整内容请查看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 继承了 PlatformViewMethodCallHandlerFlutterWebView 的主要作用就是可以生产一个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(用于加载WebViewurl),最底部就是我们的WebView 组件。

我们还实现了onMapViewCreated,在这里我们收集_webViewController并调用setUrl方法。

我们也可以在TextEditingControllerWebViewController的帮助下手动设置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 ,提供了诸如 DartExecutorFlutterRenderer 等内容的获取。
  • 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的实现类后就大功告成了,并没有写注册该插件的代码。

对于稍微复杂的插件,建议可以将 FlutterPluginMethodCallHandler 拆分到不同的类中。

关于以前旧版本的升级

如果项目是从以前 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 的 FrameworkEmbedder 两层,它们的架构大体类似,这里以Embedder的架构为例进行分析。Embedder in Platform Channel关键类及其关系如图下所示。

在这里插入图片描述

其中,BasicMessageChannelMethodChannelEventChannel是开发者和 Flutter Framework 进行通信的接口,它们的底层通信都是通过 messenger 字段所持有的BinaryMessenger接口来实现的。

  • DefaultBinaryMessenger是该接口的默认实现类,该类其实将所有工作都委托给DartMessengerDartMessenger通过flutterJNI字段持有FlutterJNI的实例,可以与Flutter Engine进行通信。
  • DartMessenger有两个关键字段: pendingReplies字段记录了一个BinaryReply接口的列表,用于分发Framework的返回数据; messageHandlers记录了一个BinaryMessageHandler接口的列表,用于处理Framework发送过来的请求,该接口的实现类分别持有MessageHandlerMethodCallHandlerStreamHandler,这3个接口也是开发者经常需要重写的接口,它们将执行Embedder侧真正的处理逻辑。

除了数据通信,以上3个Channel类还通过codec字段持有对应的编解码接口,即MessageCodecMethodCodec

BasicMessageChannel 流程分析

BasicMessageChannel的执行流程:

在这里插入图片描述

在这里插入图片描述

其中,MessageHandler是一个接口,具体由Native侧的开发者实现其中的onMessage()方法向Flutter端回复消息。

下面是详细代码流程分析。

FlutterPlatform发送消息为例,Flutter会调用BasicMessageChannelsend方法并获得其返回值,如代码清单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;
}

以上逻辑的本质是调用EngineSendPlatformMessage方法,如代码清单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逻辑为FlutterJNIhandlePlatformMessage方法,该方法最终将触发DartMessengerhandleMessageFromDart方法,如代码清单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对应的BinaryMessageHandlerIncomingMessageHandler,如代码清单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可知,将触发BinaryReplycallback方法,其一般由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
    }
  }
}

以上逻辑最终将触发PlatformViewAndroidInvokePlatformMessageResponseCallback方法,如代码清单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中的逻辑和前面内容的分析一致。但在FrameworkEmbedder中各有一处不同:一是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大体一致,在此不再赘述。

MethodChannelBasicMessageChannel的根本差异在于其编码策略,以代码清单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个数据作为方法名,所以,可以认为MethodChannelBasicMessageChannel的一个特例,因为它把方法名语义化了。

EventChannel 流程分析

EventChannel是对MethodChannel的语义化封装,其实就是MethodChannel的一个更抽象的封装。

Flutter Framework 通过 EventChannel 可以获得一个Stream,而该Stream的数据正是来自EmbedderMethodChannel的调用。

首先分析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将调用StreamHandleronListen接口,具体实现取决于开发者。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参数为开发者自定义的数据,codecMethodCodec的实例。success通过send方法向Framework发送数据,而响应的逻辑则在代码清单9-18中,主要是将二进制数据解码成dynamic类型的对象,并通知给Stream

所以EventChannel的大致流程如下图:

在这里插入图片描述

其中,StreamHandler是一个接口,具体由Native侧的开发者实现其中的onListen()方法,该方法给Native侧提供EventSink回调参数,Native侧利用EventSink.success()Flutter端发送数据。

至此,Platform Channel的底层源码分析完毕。可以看到,主要是不同语言间的调用转发和数据的编解码,以及通过对数据做语义化封装,进而提供更为抽象的MethodChannelEventChannel

Platform View原理分析

Platform Channel解决了Flutter复用Platform逻辑的问题Platform View要解决的 是Flutter复用Platform的UI的问题, Flutter以彻底的跨平台为目标,但有些场景仍不得不复用Platform的组件,最典型的就是地图WebView这种用Flutter重新实现将产生巨大工作量的UI组件,所以Flutter也需要提供原生UI的复用能力。

Platform View 关键类:

在这里插入图片描述

上图中,AndroidViewPlatformViewLinkUiKitView是开发者使用的用于表示Platform View的Widget接口,它们底层对应的RenderObject分别为RenderAndroidViewPlatformViewRenderBoxRenderUiKitView,前者将基于 TextureLayer进行真正的绘制,而后两者将基于PlatformViewLayer进行真正的绘制。

  • 使用TextureLayer展示Platform View的方式被称为虚拟显示模式Virtual Display,仅Android平台支持;
  • 使用PlatformViewLayer 展示Platform View的方式被称为混合集成模式Hybrid Composition,Android平台和iOS平台都支持。

PlatformViewControllerUiKitViewController是对Platform View中使用的Platform Channel的抽象封装,用于控制Platform ViewEmbedder中的各种属性和表现。

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对应的RenderObjectRenderAndroidView,其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的生成。由于RenderAndroidViewsizedByParent字段为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 DisplayHybrid 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中VirtualDisplayControllercreate方法分析,如代码清单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内部的addViewremoveViewupdateViewLayout等操作,如果不处理,这些操作默认委托给全局的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端原生的EditTextWebView组件而言,它们本身对手势的处理就极其复杂,并且与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对应的RenderObjectPlatformViewRenderBox,其绘制逻辑如代码清单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可知,该idonCreatePlatformView决定,因此首先分析该函数的调用点,即_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相差很大,下面从EngineEmbedder两个阶段进行分析。

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步,分析PlatformViewLayerPreroll方法,如代码清单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));
}

以上逻辑最终将调用AndroidExternalViewEmbedderPrerollCompositeEmbeddedView方法,如代码清单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步,分析PlatformViewLayerPaint方法,如代码清单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,则将触发AndroidExternalViewEmbedderSubmitFrame方法,如代码清单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

以上逻辑主要是找到FlutterPlatform 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 UIFlutterSurfaceView转换为FlutterImageViewHybrid 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方法告知EngineFlutterRenderer发生了改变,具体逻辑如代码清单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阶段执行FlutterPlatformViewLink这个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 UIPlatform 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);
    }
  }
}

以上逻辑主要是更新FlutterImageViewFlutterMutatorView的可见性,其核心逻辑在于通过调用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以下版本的系统中,将通过内存拷贝完成。

FlutterMutatorViewdraw方法如代码清单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 DisplayHybrid Composition由于使用了原生ViewEditTextWebView这类组件的手势交互将变得不再困难,但由以上分析也可以推测,由于Draw阶段的Bitmap拷贝,Hybrid Composition模式下的渲染性能将大大降低,特别是Android 10以下版本的系统中。

基于以上分析,开发者应该在使用Platform View时审慎、合理地选用恰当的方案。

Plugin原理分析

Platform ChannelPlatform View基本解决了Flutter复用原生平台逻辑和UI的问题,但是,考虑到Flutter Engine及其所依赖的宿主(Activity或者Fragment)都有自己独立的生命周期,Platform ChannelPlatform View往往需要在合适的时机(可粗略地认为是创建后,销毁前,即onCreateonDestroy两个回调中间)进行,如果由开发者自行管理,不仅工作量巨大,质量也可能良莠不齐。因此,Plugin的一大功能就是提供响应各种组件生命周期的回调入口。此外,Plugin也是flutter tool所支持的复用Flutter和Embedder混合代码的方式,Flutter的官方仓库提供了flutter_webview等Plugin,其本质就是对WebView等原生能力的封装,为逻辑和UI的复用提供了可行的方案。

Plugin的注册入口是GeneratedPluginRegisterregisterGeneratedPlugins方法,如代码清单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) { ...... }}

以上逻辑将通过反射的方式调用GeneratedPluginRegistrantregisterWith方法,该类由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接口为例,当宿主FlutterActivityonDestroy回调触发时通过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的存储路径的变量

在这里插入图片描述
aa

保证在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内核源码剖析》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/546650.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

研报精选230519

目录 【行业230519头豹研究院】2023年中国产后康复设备行业词条报告 【行业230519山西证券】有色金属行业周报&#xff1a;锂价快速回升&#xff0c;释放锂电行业复苏信号 【行业230519头豹研究院】2023年中国氢能重卡行业词条报告 【个股230519西南证券_森麒麟】腾飞的高端轮胎…

网页外包开发的测试方法及工具

网页开发的软件项目完成代码开发后需要进行全面的测试&#xff0c;这是正规的软件公司开发软件项目必须要做的工作&#xff0c;这方面有不少好用的工具供大家使用。今天和大家分享这方面的知识&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件…

Elasticsearch实战之处理邮件附件进行进行内容全文检索

目录 一、系统环境和软件要求 二、软件说明 三、定义文本抽取管道(pipeline) 四、建立索引设置文档结构映射 五、插入文档 六、查询文档 需求是将本地邮件内容以及PDF&#xff0c;EXCEL&#xff0c;WORD等附件内容进行处理&#xff0c;保存到ES数据库&#xff0c;实现邮件…

【I2C 通信的工作原理是什么?Arduino 和 I2C 教程】

【I2C 通信的工作原理是什么?Arduino 和 I2C 教程】 1. 概述2. I2C 的工作原理3. I2C 协议4. 例程5. Arduino I2C 代码在本教程中,我们将了解 I2C 通信协议的工作原理,我们还将使用 Arduino 板和使用该协议的传感器制作一个实际示例。您可以观看以下视频或阅读下面的书面教程…

chatgpt赋能Python-python3虚拟环境搭建

Python3虚拟环境搭建&#xff1a;介绍和步骤 Python是一门非常强大的编程语言&#xff0c;因此在许多不同类型的项目中都广泛使用。但是&#xff0c;不同项目可能需要使用不同版本的Python库和依赖项。这就是使用Python的虚拟环境的重要性&#xff0c;可以避免不同项目之间的冲…

「实验记录」MIT 6.824 Raft Lab2B Log Replication

#Lab2B - Log Replication I. SourceII. My CodeIII. MotivationIV. SolutionS1 - leader上任即初始化S2 - leader发送AppendEntriesS3 - follower接收AppendEntriesS4 - leader收到AppendEntries 回信S5 - candidate选举限制S6 - defs.go约定俗成和实现Start() V. Result I. S…

yolov7结构改进策略解析

论文链接&#xff1a;https://arxiv.org/abs/2207.02696 代码链接&#xff1a;https://github.com/WongKinYiu/yolov7 具体分割如何训练&#xff0c;请参考我之前的博客论文&#xff1a; https://blog.csdn.net/qq_41920323/article/details/129464115?spm1001.2014.3001.5502…

联用多个插件可以让 GPT-4 的能力更加强大,实现更加复杂的操作

&#x1f680; 联用多个插件可以让 GPT-4 的能力更加强大&#xff0c;实现更加复杂的操作。 联用多个插件可以让 GPT-4 的能力更加强大&#xff0c;实现更加复杂的操作。 不过&#xff0c;使用插件和联网功能也有一些要注意的地方。 首先是安全性问题&#xff0c;特别是像购…

Burpsuite模块—-Intruder模块详解

一、简介 Burp Intruder是一个强大的工具&#xff0c;用于自动对Web应用程序自定义的攻击&#xff0c;Burp Intruder 是高度可配置的&#xff0c;并被用来在广范围内进行自动化攻击。你可以使用 Burp Intruder 方便地执行许多任务&#xff0c;包括枚举标识符&#xff0c;获取有…

Kali-linux密码在线破解

为了使用户能成功登录到目标系统&#xff0c;所以需要获取一个正确的密码。在Kali中&#xff0c;在线破解密码的工具很多&#xff0c;其中最常用的两款分别是Hydra和Medusa。本节将介绍使用Hydra和Medusa工具实现密码在线破解。 8.1.1 Hydra工具 Hydra是一个相当强大的暴力密…

浅谈分布式事物解决方案

目录 背景 1 XA规范分布式事物方案 1.1 俩阶段提交&#xff08;2PC&#xff09; 1.2 三阶段提交&#xff08;3PC&#xff09; 2 补偿事务&#xff08;TCC&#xff09; 3 可靠消息最终一致性方案 4 可靠消息最终一致性方案 5 SAGA事物 6 Seata AT 模式 背景 分布式事务出现…

使用Git-lfs上传超过100m的大文件到GitHub

文章目录 1. 安装 git-lfs2. 在Git中安装git-ifs3. 找到工程中的所有大文件4.执行完这行命令&#xff0c;项目目录下会生成文件 .gitattributes&#xff0c;此时Git push将 .gitattributes 提交到远程仓库。 5. 需要注意的事 1. 安装 git-lfs Git Large File Storage | Git La…

Day44【动态规划】完全背包、518.零钱兑换 II、377.组合总和 Ⅳ

完全背包 文章讲解 视频讲解 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将物品装入背包里的最大价值 完全背包和01背包问题…

F103ZET6使用FSMC和HAL点亮ILI9341

前言 将标准库下的ILI9341驱动移植到使用CubeMX生成的HAL库环境&#xff0c;并成功运行。 一、STM32CubeMX生成框架 &#xff08;一&#xff09;配置RCC、SYS和时钟树 参见常规配置。 &#xff08;二&#xff09;配置FSMC 1、原理图引脚定义 LCD8080接口使用的引脚主要分…

【数据结构】线性表 ⑤ ( 双循环链表 | 双循环链表特点 | 双循环链表插入操作处理 | 代码示例 - 使用 Java 实现 双循环链表 )

文章目录 一、双循环链表二、双循环链表特点三、双循环链表插入操作处理四、代码示例 - 使用 Java 实现 双循环链表 一、双循环链表 " 双循环链表 " 是 在 单循环链表 的基础上 , 在每个 节点 中 , 新增一个 指针 , 指向 该节点 的 前驱节点 ; 双向循环链表 每个 节…

头歌计算机组成原理实验—运算器设计(8)第8关:乘法流水线设计

第8关&#xff1a;乘法流水线设计 实验目的 学生掌握运算流水线基本概念&#xff0c;理解将复杂运算步骤细分成子过程的思想&#xff0c;能够实现简单的乘法运算流水线。 视频讲解 实验内容 在 Logisim 中打开 alu.circ 文件&#xff0c;在6位补码阵列乘法器中利用5位阵列乘…

React学习笔记三-模块与组件的理解

此文章是本人在学习React的时候&#xff0c;写下的学习笔记&#xff0c;在此纪录和分享。此为第三篇&#xff0c;主要介绍react中的模块与组件。 目录 1.模块与组件 1.1模块 1.2组件 1.3模块化 1.4组件化 2.React面向组件编程 2.1函数式组件 2.2类组件 2.2.1类知识的复…

防火墙(一)

防火墙知识 一、iptables概述二、四表五链四表五链iptables防火墙的使用方法 三、示例操作四、规则的匹配通用匹配&#xff1a;隐含匹配&#xff1a;端口匹配&#xff1a; --sport源端口、--dport目的端口TCP标志位匹配&#xff1a;ICMP类型匹配&#xff1a;显示匹配&#xff1…

Windows 安装MySQL 8.0 超详细教程(mysql 8.0.30)

目录 一、删除以前安装的MySQL服务 1、查找以前是否装有mysql 2、删除mysql &#xff08;1&#xff09;停止mysql服务&#xff1a; &#xff08;2&#xff09;删除mysql服务&#xff1a; 3.检查mysql是否已删除 二、下载mysql二进制包 三、解压二进制包&#xff0c;编辑…

一图看懂 setuptools 模块:一个功能齐全、积极维护且稳定的库,旨在方便打包Python项目,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 setuptools 模块&#xff1a;一个功能齐全、积极维护且稳定的库&#xff0c;旨在方便打包Python项目&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;…