Flutter第十五彈 Flutter插件

目標:

1.Flutter插件是什么?有什么作用?

插件 (plugin) 是 package 的一種,全稱是 plugin package,我們簡稱為 plugin,中文叫插件。

2.怎么創建Flutter插件?

一、什么是插件

在flutter中,一個插件叫做一個package,使用packages的目的就是為了達到模塊化,可以創建出可被復用和共享的代碼,這和大多數編程語言中的模塊、包的概念相同。創建出來的package可以在pubspec.yaml中直接依賴。

1.1 package組成

Flutter插件組成

  • 一個pubspec.yaml文件一個元數據文件,聲明了聲明了package的名稱、版本、作者等信息。
  • 一個lib文件夾:包含里package的公開代碼,文件夾至少需要存在<pakcage-name>.dart這個文件。

注意:<pakcage-name>.dart這個文件必須存在,因為這是方便使用的人快速import這個package來使用它,可以把它理解成一種必須要遵守的規則。

1.2 package分類

package可以分為兩種:純dart代碼的package和帶有特定平臺代碼的package。

  • Dart packages:這是一個只有dart代碼的package,里面包含了flutter的特定功能,所以它依賴于flutter的framework,也決定了它只能用在flutter上。
  • plugin packages:這是一個既包含了dart代碼編寫的api,又包含了平臺(Android/IOS)特定實現的package,可以被Android和ios調用。
  • FFI 插件
    用 Dart 語言編寫針對一個或多個特定平臺的 API,使用 Dart FFI (Android、iOS、macOS)。

> 上面應該很好理解,可以理解成java jar包和Android sdk的區別。而要開發的日志插件就是第二種。

二、插件開發

2.1 創建package

可以使用AS創建插件

然后點擊next。

?

然后點擊 Create按鈕,開始創建插件項目。

如果是采用Flutter命令創建項目

// 想要創建初始的 Flutter package,請使用帶有 --template=package 標志的 flutter create 命令:flutter create --template=package hello

2.2 項目文件結構

項目文件結構如下:

LICENSE 文件
大概率會是空的一個許可證文件。

  • test/hello_test.dart 文件

Package 的?單元測試?文件。

  • hello.iml 文件

由 IntelliJ 生成的配置文件。

  • .gitignore 文件

告訴 Git 系統應該隱藏哪些文件或文件夾的一個隱藏文件。

  • .metadata 文件

IDE 用來記錄某個 Flutter 項目屬性的的隱藏文件。

  • pubspec.yaml 文件

pub 工具需要使用的,包含 package 依賴的 yaml 格式的文件。

  • README.md 文件

起步文檔,用于描述 package。

  • lib/hello.dart 文件

package 的 Dart 實現代碼。

  • .idea/modules.xml.idea/workspace.xml 文件

IntelliJ 的各自配置文件(包含在 .idea 隱藏文件夾下)。

  • CHANGELOG.md 文件

又一個大概率為空的文檔,用于記錄 package 的版本變更。插件的native端實現

  • android/

插件包API的Android實現

  • iOS

插件包API的ios實現.

  • example/:

? ?一個依賴于該插件的Flutter應用程序,來說明如何使用它

lib庫定義插件的主要功能。

2.3 實現插件

對于純 Dart 庫的 package,只要在 lib/<package name>.dart 文件中添加功能實現,或在 lib 目錄中的多個文件中添加功能實現。
如果要對 package 進行測試,在 test 目錄下添加 單元測試。

2.3.1 創建MethodChannel

項目默認生成了插件MethodChannel

1. 創建MethodChannel

flutter_log_plugin_method_channel.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';import 'flutter_log_plugin_platform_interface.dart';/// An implementation of [FlutterLogPluginPlatform] that uses method channels.
class MethodChannelFlutterLogPlugin extends FlutterLogPluginPlatform {/// The method channel used to interact with the native platform.@visibleForTestingfinal methodChannel = const MethodChannel('flutter_log_plugin');@overrideFuture<String?> getPlatformVersion() async {final version = await methodChannel.invokeMethod<String>('getPlatformVersion');return version;}
}
2.定義插件方法?
  • 創建了一個MethodChannel,名稱為flutter_log_plugin
  • 提供了一個方法訪問getPlatformVersion

我們看看其調用的方式,通過創建的methodChannel.invokeMethod來調用原生實現。

final version = await methodChannel.invokeMethod<String>('getPlatformVersion');

2.3.2 插件方法Native實現

在項目 android 目錄下,增加對?MethodChannel 方法的實現。

默認的插件實現的功能:Dart通過插件,獲取 native端系統版本信息。

在android/src.main? 下,實現了Native方法

package com.example.flutter_log_pluginimport androidx.annotation.NonNullimport io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result/** FlutterLogPlugin */
class FlutterLogPlugin: FlutterPlugin, MethodCallHandler {/// The MethodChannel that will the communication between Flutter and native Android////// This local reference serves to register the plugin with the Flutter Engine and unregister it/// when the Flutter Engine is detached from the Activityprivate lateinit var channel : MethodChanneloverride fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_log_plugin")channel.setMethodCallHandler(this)}/*** 方法調用處理** @author zhouronghua* @time 2024/6/27 下午3:12*/override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {if (call.method == "getPlatformVersion") {result.success("Android ${android.os.Build.VERSION.RELEASE}")} else {result.notImplemented()}}override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {channel.setMethodCallHandler(null)}
}
MethodChannel提供Flutter與原生系統之間的通信。
1.綁定MethodChannel: Activity attach到Flutter引擎
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it/// when the Flutter Engine is detached from the Activityprivate lateinit var channel : MethodChanneloverride fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_log_plugin")channel.setMethodCallHandler(this)}

因為Flutter提供的引擎是“flutter_log_plugin”名稱,因此通過 命名找到對應的MethodChannel。

這樣Activity就可以綁定Flutter MethodChannel,可以建立通信通道。

設置MethodCallHandler,注冊插件到Flutter引擎。

channel.setMethodCallHandler(this)
2. Flutter調用Native方法

建立通道以后,Flutter調用Native端方法,方法名為getPlatformVersion,沒有參數。

  /*** 方法調用處理** @author zhouronghua* @time 2024/6/27 下午3:12*/override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {if (call.method == "getPlatformVersion") {result.success("Android ${android.os.Build.VERSION.RELEASE}")} else {result.notImplemented()}}

Native端接收方法調用的入口是 onMethodCall

  • 首先匹配方法名
  • 根據call的參數進行處理
  • 返回方法調用結果,通過result保存結果值。
  • 如果對應名稱的方法未實現,則設置 result.notImplented()

當前獲取安卓系統版本,返回結果是

"Android ${android.os.Build.VERSION.RELEASE}"
3.Activity與Flutter引擎斷開時注銷插件
  override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {channel.setMethodCallHandler(null)}

?2.3.3 實現日志打印插件

1.聲明接口方法

在lib插件的接口文件flutter_log_plugin_platform_interface.dart

聲明接口方法 logI

  /*** 聲明接口方法** @author zhouronghua* @time 2024/6/27 下午4:05*/void logI(String tag, String message) {throw UnimplementedError('logI() has not been implemented.');}
2. 插件端定義日志打印方法實現 logI
  /*** 日志打印:I級別* 說明: 日志打印不需要接收結果,因此不需要異步回調** @author zhouronghua* @time 2024/6/27 下午3:45*/@overridevoid logI(String tag, String message) {/// 調用原生方法logI, 參數集為 {tag, message}/// 參數集按照鍵值對傳遞methodChannel.invokeMethod('logI', {"tag": tag, "message": message});}

調用的Native段方法名為 “logI”,對應的參數為:

{"tag": tag, "message": message}

參數一般使用鍵值對進行傳遞,參數之間采用逗號分隔。

注意,此處一定要使用注解?@override,否則調用logI編譯報錯

3.Native端實現方法接收處理

在android/src.main下,FlutterLogPlugin增加日志方法調用的實現。

/*** 方法調用處理** @author zhouronghua* @time 2024/6/27 下午3:12*/override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {if (call.method == "getPlatformVersion") {// 獲取系統版本信息result.success("Android ${android.os.Build.VERSION.RELEASE}")} else if (call.method == "logI") {// 日志打印處理 logI(參數要與插件的鍵保持一致)final String tag = call.argument("tag")final String message = call.argument("message")android.util.Log.i(tag, message)} else {result.notImplemented()}}
4.Flutter插件入口添加日志打印方法

FlutterLogPlugin中,增加日志打印入口調用

/*** Flutter插件入口* 門面模式** @author zhouronghua* @time 2024/6/27 下午4:11*/
class FlutterLogPlugin {Future<String?> getPlatformVersion() {return FlutterLogPluginPlatform.instance.getPlatformVersion();}/*** 日志打印調用** @author zhouronghua* @time 2024/6/27 下午4:11*/void logI(String tag, String message) {return FlutterLogPluginPlatform.instance.logI(tag, message);}}

這個是典型的門面模式,外部調用的使用不需要關注Flutter插件內部實現細節。

5.測試日志打印方法

在example/lib下,main.dart中,測試日志打印方法調用。

// Platform messages are asynchronous, so we initialize in an async method.Future<void> initPlatformState() async {// 調用日志打印_flutterLogPlugin.logI("MyApp", "開始初始化平臺");String platformVersion;// Platform messages may fail, so we use a try/catch PlatformException.// We also handle the message potentially returning null.try {platformVersion = await _flutterLogPlugin.getPlatformVersion() ??'Unknown platform version';} on PlatformException {platformVersion = 'Failed to get platform version.';}// 調用日志打印_flutterLogPlugin.logI("MyApp", "平臺信息是:$platformVersion");// If the widget was removed from the tree while the asynchronous platform// message was in flight, we want to discard the reply rather than calling// setState to update our non-existent appearance.if (!mounted) return;setState(() {_platformVersion = platformVersion;});}

問題一:編譯報錯logI未實現

還是報錯

Restarted application in 1,896ms.
E/flutter ( 2023): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: MissingPluginException(No implementation found for method logI on channel flutter_log_plugin)
E/flutter ( 2023): #0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:165:7)
E/flutter ( 2023): <asynchronous suspension>
E/flutter ( 2023): 
E/flutter ( 2023): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: MissingPluginException(No implementation found for method logI on channel flutter_log_plugin)
E/flutter ( 2023): #0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:165:7)
E/flutter ( 2023): <asynchronous suspension>
E/flutter ( 2023): 

test模塊,flutter_log_plugin_test.dart缺少對應的logI方法的實現,因此報錯。添加

class MockFlutterLogPluginPlatform with MockPlatformInterfaceMixinimplements FlutterLogPluginPlatform {@overrideFuture<String?> getPlatformVersion() => Future.value('42');@overridevoid logI(String tag, String message) {debugPrint("tag=$tag message=$message");}
}
6.打包apk

Terminal中,編譯安卓APK。

$ cd example/android$ gradlew clean$ flutter build apk

如果報錯,修正報錯信息,重新打包試試。

出現下面的信息則編譯成功。

安裝運行APK,看看是否打印日志信息

能夠輸出對應的日志信息。

三、插件原理

Plugin其實就是一個特殊的Package。Flutter Plugin提供Android或者iOS的底層封裝,在Flutter層提供組件功能,使Flutter可以較
方便的調取Native的模塊。很多平臺相關性或者對于Flutter實現起來比較復雜的部分,都可以封裝成Plugin。

3.1?Platform Channel

Platform Channel:

1. Flutter App (Client),通過MethodChannel類向Platform發送調用消息;
2. Android Platform (Host),通過MethodChannel類接收調用消息;
3. iOS Platform (Host),通過FlutterMethodChannel類接收調用消息。

  • > PS:消息編解碼器,是JSON格式的二進制序列化,所以調用方法的參數類型必須是可JSON序列化的。
  • > PS:方法調用,也可以反向發送調用消息。

3.2 安卓平臺

FlutterActivity,是Android的Plugin管理器,它記錄了所有的Plugin,并將Plugin綁定到FlutterView。?

3.3?理解Platform Channel工作原理


Flutter定義了三種不同類型的Channel,它們分別是

  • BasicMessageChannel:用于傳遞字符串和半結構化的信息。
  • MethodChannel:用于傳遞方法調用(method invocation)。
  • EventChannel: 用于數據流(event streams)的通信。

三種Channel之間互相獨立,各有用途,但它們在設計上卻非常相近。每種Channel均有三個重要成員變量:

  • name: ?String類型,代表Channel的名字,也是其唯一標識符。
  • messager:BinaryMessenger類型,代表消息信使,是消息的發送與接收的工具。
  • codec: MessageCodec類型或MethodCodec類型,代表消息的編解碼器。
  • Channel name

? ? 一個Flutter應用中可能存在多個Channel,每個Channel在創建時必須指定一個獨一無二的name,Channel之間使用name來區分彼此。當有消息從Flutter端發送到Platform端時,會根據其傳遞過來的channel name找到該Channel對應的Handler(消息處理器)。

  • 消息信使:BinaryMessenger
  • 平臺通道數據類型支持和解碼器
  • 標準平臺通道使用標準消息編解碼器,以支持簡單的類似JSON值的高效二進制序列化,例如 booleans,numbers, Strings, byte buffers, List, Maps(請參閱StandardMessageCodec了解詳細信息)。 當您發送和接收值時,這些值在消息中的序列化和反序列化會自動進行。

下表顯示了如何在宿主上接收Dart值,反之亦然:

?3.4 解碼器

?

消息解碼器主要將二進制格式的數據轉換為Handler能夠識別的數據,Flutter定義了兩種Codec:

MessageCodec和MethodCodec。

四、插件打包和發布

4.1 插件檢查

一旦完成了 package 的實現,你便可以將其提交到?pub.dev?上,以便其他開發者可以輕松地使用它。

發布你的 package 之前,確保檢查了這幾個文件:pubspec.yamlREADME.md?和?CHANGELOG.md,確保它們完整且正確,另外,為了提高 package 的可用性,可以考慮加入如下的內容:

  • 代碼的示例用法

  • 屏幕截圖,GIF 動畫或者視頻

  • 代碼庫的正確指向鏈接

運行 dry-run 命令以檢驗是否所有內容都通過了分析:

$ flutter packages pub publish --dry-run

修正提示錯誤信息。?

pubspec.yaml 中anthor字段不需要了,直接刪除

修改后再次執行。?

Package validation found the following potential issue:
* Packages with an SDK constraint on a pre-release of the Dart SDK should themselves be published as a pre-release version. If this package needs Dart version 2.17.0-239.0.dev, consider publishing the package as a pre-release instead.See https://dart.dev/tools/pub/publishing#publishing-prereleases For more information on pre-releases.Package has 1 warning.
pub finished with exit code 65

?

4.2 插件發布

最后一步是發布,請注意:發布是永久性?的,運行以下提交命令:

flutter pub publish

設置了中國鏡像的開發者們請注意:目前所存在的鏡像都不能(也不應該)進行 package 的上傳。如果你設置了鏡像,執行上述發布代碼可能會造成發布失敗。網絡設定好后,無需取消中文鏡像,執行下述代碼可直接上傳:

flutter pub publish --server=https://pub.dartlang.org

?

Dart 概覽 | Dart

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/38188.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/38188.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/38188.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【成都活動邀請函】7月6 | PowerData 數字經濟-“成都“開源行!

【成都活動邀請函】7月6 | PowerData 數字經濟-"成都"開源行&#xff01; 活動介紹活動信息線上直播掃碼報名往期活動回顧專注數據開源&#xff0c;推動大數據發展 活動介紹 九天開出一成都&#xff0c;萬戶千門入畫圖。 自古以來&#xff0c;成都便是國家發展的重要…

第2章-Python編程基礎

#本章目標 1&#xff0c;了解什么是計算機程序 2&#xff0c;了解什么是編程語言 3&#xff0c;了解編程語言的分類 4&#xff0c;了解靜態語言與腳本語言的區別 5&#xff0c;掌握IPO程序編寫方法 6&#xff0c;熟練應用輸出函數print與輸入函數input 7&#xff0c;掌握Python…

【機器學習】機器學習的重要技術——生成對抗網絡:理論、算法與實踐

引言 生成對抗網絡&#xff08;Generative Adversarial Networks, GANs&#xff09;由Ian Goodfellow等人在2014年提出&#xff0c;通過生成器和判別器兩個神經網絡的對抗訓練&#xff0c;成功實現了高質量數據的生成。GANs在圖像生成、數據增強、風格遷移等領域取得了顯著成果…

leetCode.97. 交錯字符串

leetCode.97. 交錯字符串 題目思路 代碼 class Solution { public:bool isInterleave(string s1, string s2, string s3) {int n s1.size(), m s2.size();if ( s3.size() ! n m ) return false;vector<vector<bool>> f( n 1, vector<bool> (m 1));s1 …

C語言使用void *類型作為函數傳參

C語言使用void *怎么理解&#xff1a; 根據本人的理解&#xff0c;他就是指向操作數據區的首地址而已 凡是void指的數據區都要進行第二次初始化數據類型&#xff08;即dtype p(dtype)pdata&#xff09;*。 舉兩個例子&#xff1a; 傳入函數&#xff1a; void tx_data(void …

Sparse4D v3: Advancing End-to-End 3D Detection and Tracking

Sparse4D v3: Advancing End-to-End 3D Detection and Tracking 相關內容&#xff1a;總覽&#xff0c;Sparse4D v1&#xff0c;Sparse4D v2&#xff0c; 單位&#xff1a;地平線(Sparse4D v1 v2 原班人馬) GitHub&#xff1a;https://github.com/HorizonRobotics/Sparse4D …

昇思25天學習打卡營第5天 | 網絡構建

目錄 1.定義模型類 2.模型層 nn.Flatten nn.Dense nn.ReLU nn.SequentialCell nn.Softmax 3.模型參數 代碼實現&#xff1a; 總結 神經網絡模型是由神經網絡層和Tensor操作構成的&#xff0c; mindspore.nn提供了常見神經網絡層的實現&#xff0c; 在MindSpore中&a…

啟動spring boot項目停止 提示80端口已經被占用

可能的情況: 檢查并結束占用進程: 首先,你需要確定哪個進程正在使用80端口。在Windows上,可以通過命令行輸入netstat -ano | findstr LISTENING | findstr :80來查看80端口的PID,然后在任務管理器中結束該進程。在

AI智能客服項目拆解(1) 產品大綱

本文作為拆解AI智能客服項目的首篇&#xff0c;以介紹產品大綱為主。后續以某AI智能客服產品為例&#xff0c;拆解相關技術細節。 AI智能客服是一種基于人工智能技術的客戶服務解決方案&#xff0c;旨在提高客戶滿意度和優化企業運營。利用人工智能和自然語言處理技術&#xff…

MySQL之索引失效的情況

什么情況下索引會失效&#xff1f; 違反最左前綴原則范圍查詢右邊的列不能使用索引不要在索引列上進行運算操作字符串不加單引號導致索引失效以%開頭的like模糊查詢 什么情況下索引會失效&#xff1f; 示例&#xff0c;有user表如下 CREATE TABLE user (id bigint(20) NOT NU…

實驗1 多層感知器設計(MLP)

1.實驗目的 掌握多層感知器的原理。掌握多層感知器的設計、訓練和測試。2.實驗要求 設計一個多層感知器,用于對給定的數據進行分類。要求代碼格式規范,注釋齊全,程序可正常運行。 3.模型設計 實驗設計一個多層感知機,三層機構,只含一個隱藏層,輸入層,隱藏層,輸出層 1…

JAVA期末速成庫(11)第十二章

一、習題介紹 第十二章 Check Point&#xff1a;P454 12.1&#xff0c;12.9&#xff0c;12.10&#xff0c;12,12 二、習題及答案 12.1 What is the advantage of using exception handling? 12.1使用異常處理的優勢是什么? 答:使用異常處理有以下優勢&#xff1a; 1. 提高…

C++ 模板類的示例-數組

類模板可以有非通用類型參數&#xff1a;1&#xff09;通常是整型&#xff08;C20標準可以用其它的類型&#xff09;&#xff1b;2&#xff09;實例化模板時必須用常量表達式&#xff1b;3&#xff09;模板中不能修改參數的值&#xff1b;4&#xff09;可以為非通用類型參數提供…

Android中使用performClick觸發點擊事件

Android中使用performClick觸發點擊事件 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;今天我們將探討在Android開發中如何使用 performClick() 方法來觸發點擊…

數據庫-python SQLite3

數據庫-python SQLite3 一&#xff1a;sqlite3 簡介二: sqlite3 流程1> demo2> sqlite3 流程 三&#xff1a;sqlite3 step1> create table2> insert into3> update4> select1. fetchall()2. fetchone()3. fetchmany() 5> delete6> other step 四&#…

Spark join數據傾斜調優

Spark中常見的兩種數據傾斜現象如下 stage部分task執行特別慢 一般情況下是某個task處理的數據量遠大于其他task處理的數據量&#xff0c;當然也不排除是程序代碼沒有冗余&#xff0c;異常數據導致程序運行異常。 作業重試多次某幾個task總會失敗 常見的退出碼143、53、137…

【電路筆記】-放大器類型

放大器類型 文章目錄 放大器類型1、概述2、關于偏置的注意事項3、A類(Class A)放大器4、B類(Class B)放大器5、AB類(Class AB)放大器6、C類(Class C)放大器7、總結1、概述 放大器通常根據輸出級的結構進行分類。 事實上,功率放大確實發生在該階段,因此輸出信號的質量和…

Arduino (esp ) 下String的內存釋放

在個人的開源項目 GitHub - StarCompute/tftziku: 這是一個通過單片機在各種屏幕上顯示中文的解決方案 中為了方便快速檢索使用了string&#xff0c;于是這個string在esp8266中占了40多k,原本以為當string設置為""的時候這個40k就可以回收&#xff0c;結果發覺不行…

【JS異步編程】async/await——用同步代碼寫異步

歷史小劇場 懂得暴力的人&#xff0c;是強壯的&#xff1b;懂得克制暴力的人&#xff0c;才是強大的。----《明朝那些事兒》 什么是 async/await async: 聲明一個異步函數 自動將常規函數轉換成Promise&#xff0c;返回值也是一個Promise對象&#xff1b;只有async函數內部的異…

Java SE入門及基礎(59) 線程的實現(上) 線程的創建方式 線程內存模型 線程安全

目錄 線程&#xff08;上&#xff09; 1. 線程的創建方式 Thread類常用構造方法 Thread類常用成員方法 Thread類常用靜態方法 示例 總結 2. 線程內存模型 3.線程安全 案例 代碼實現 執行結果 線程&#xff08;上&#xff09; 1. 線程的創建方式 An application t…