前言
61、Flutter插件通信原理<一>_风雨「83」的博客-CSDN博客Flutter与原生通讯 - Flutter Plugin - 知乎前言Flutter优势主要体现在UI上--高性能且跨平台表现一致。但是针对平台(Android、IOS)的实现,如:获取电量、判断WiFi使用、调起WebView加载网页等,得调用特定平台的API包。Flutter Plugin就是为调用平台API而生。下文中所提及到的"平台"指的是Android、IOS两端。介绍Flutter Plugin包含针对Android(Java或Kotlin代码)或iOS(Objectihttps://blog.csdn.net/wywinstonwy/article/details/123925721?spm=1001.2014.3001.5502Flutter中已经具体提到了Flutter与iOS,Android的通信原理。Flutter通信是靠MethodChannel进行通信的。
假设Flutter需要一个第三方的授权登录,而第三方目前没有支持到Flutter的版本,只支持Android,iOS版本,这个时候就需要自己开发Flutter与原生的通信插件。
通信插件的开发过程
1、创建Flutter_Plugin项目
新建flutter_plugin工程,选择工程类型,选项语言类型,并点击finish完成工程的创建。
2、Flutter_Plugin目录介绍
Android 就是我们开发安卓部分的位置
iOS 就是我们开发 iOS 的位置
lib 是与 Android 、iOS 联调的位置。也可以理解为Flutter 实现通信的位置
example 是测试的位置,当我们写完插件 可以直接运行 插件,example 可以理解为一个Flutter项目,只不过这个项目只给你写的插件服务? 。
?
?3、Flutter部分开发
1、添加原生、Flutter交互渠道
我们打开插件,找到lib ,在lib下面会有一个文件?test_flutter_plugin_demo.dart,在这个基础上我们进行扩展,更加灵活
import 'dart:async';
import 'package:flutter/services.dart';
typedef void TestViewCreatedCallback(TestFlutterPluginDemo controller);
class TestFlutterPluginDemo {
late MethodChannel _channel;
TestFlutterPluginDemo.init(int id){
// 原生与Flutter 交互渠道
_channel = new MethodChannel('test_flutter_plugin_demo');
_channel.setMethodCallHandler(platformCallHandler);///设置原生参数监听
}
///Flutter 调用原生
///这里我传了一个 字符串 当然也可以传Map
Future<List<dynamic>?> changeNativeTitle(String str) async{
return _channel.invokeListMethod('changeNativeTitle',str);
}
///实现监听原生方法回调
Future<dynamic> platformCallHandler(MethodCall call) async {
switch (call.method) {
case "clickAciton":
print('收到原生回调 ---- $call.arguments');
return ;
break;
}
}
}
这样就实现了 原生与Flutter之间的交互,等后续iOS,Android端代码完成就可以进行两端调试。
2、Flutter界面开发以及调用
在example/lib下创建view.dart 文件,并在example/lib/main.dart中引入视图view.dart并调用。
view.dart全部代码
import 'package:flutter/cupertino.dart';
import 'package:test_flutter_plugin_demo/test_flutter_plugin_demo.dart';
import 'package:flutter/services.dart';
import 'dart:io';
/// @Author wywinstonwy
/// @Date 2022/4/15 10:58 上午
/// @Description:
///我是使用的 StatefulWidget 使用StatelessWidget 也是一样
class TestView extends StatefulWidget {
///根据自己的需求创建初始化参数
final TestViewCreatedCallback ? onCreated; ///是上面创建的回调
final String ? titleStr;
TestView({
required Key key,
this.onCreated,
this.titleStr,
});
@override
_TestViewState createState() => _TestViewState();
}
class _TestViewState extends State<TestView> {
@override
Widget build(BuildContext context) {
return Container(
child: _loadNativeView(),
);
}
///加载原生视图
Widget _loadNativeView(){
///根据不同的平台显示相应的视图
if(Platform.isAndroid){ ///加载安卓原生视图
return AndroidView(
viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
creationParams: <String, dynamic>{ ///给原生传递初始化参数 就是上面定义的初始化参数
'titleStr':widget.titleStr,
},
/// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
/// 如果存在 creationParams,则该值不能为null
creationParamsCodec: const StandardMessageCodec(),
);
}else if(Platform.isIOS){///加载iOS原生视图
return UiKitView(
viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
creationParams: <String, dynamic>{ ///给原生传递初始化参数 就是上面定义的初始化参数
'titleStr':widget.titleStr,
},
/// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
/// 如果存在 creationParams,则该值不能为null
creationParamsCodec: const StandardMessageCodec(),
);
}else{
return Text('这个平台老子不支持');
}
}
///这个基本上是固定写法
Future<void> onPlatformViewCreated(id) async {
if (widget.onCreated == null) {
return;
}
widget.onCreated!(new TestFlutterPluginDemo.init(id));
}
}
example/lib/main.dart中调用view.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:test_flutter_plugin_demo/test_flutter_plugin_demo.dart';
import 'package:test_flutter_plugin_demo_example/view.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
///定义一个测试类的属性 用来调用原生方法 和原生交互
var testFlutterPluginDemo;
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion ='是是是';
// try {
// platformVersion =
// await TestFlutterPluginDemo.platformVersion ?? 'Unknown platform version';
// } on PlatformException {
// platformVersion = 'Failed to get platform version.';
// }
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
///初始化 测试视图的类
TestView testView = TestView(
onCreated: onTestViewCreated, key: ValueKey('testView'),
titleStr: 'flutter给原生的参数',
);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Column(children: [
Container(height: 200,width: 400,child: testView,),
FloatingActionButton(onPressed: onNativeMethon)
],),
),
);
}
void onNativeMethon(){
this.testFlutterPluginDemo.changeNativeTitle('Flutter 调用原生成功了');
}
void onTestViewCreated(testFlutterPluginDemo){
this.testFlutterPluginDemo = testFlutterPluginDemo;
}
}
4、iOS、Android介绍
1、iOS部分代码开发
iOS 找到 ios 目录,选择Reveal in Finder,因为现在这个ios 部分还没有pod install,我们这要先进行pod install,成功后直接打开项目即可,效果如下
??在这里我们找到TestFlutterPluginDemoPlugin,这个类隐藏的很深,他是Flutter 与原生交互的核心,在这了我们可以接收到Flutter的内容。
在此目录下分别创建TestFlutterPluginView和TestFlutterPluginViewFactory类
//
// TestFlutterPluginView.h
// Pods
//
// Created by wangyun on 2022/4/15.
//
#import <Foundation/Foundation.h>
#include <Flutter/Flutter.h>
@interface TestFlutterPluginView : NSObject<FlutterPlatformView>
- (id)initWithFrame:(CGRect)frame
viewId:(int64_t)viewId
args:(id)args
messager:(NSObject<FlutterBinaryMessenger>*)messenger;
@end
//
// TestFlutterPluginView.m
// Pods
//
// Created by wangyun on 2022/4/15.
//
#import "TestFlutterPluginView.h"
@interface TestFlutterPluginView ()
/** channel*/
@property (nonatomic, strong) FlutterMethodChannel *channel;
@property (nonatomic, strong) UIButton *button;
@property (nonatomic, strong) UITextField *textField;
@property (nonatomic, strong) UILabel *lblText;
@property (nonatomic, assign) NSInteger count;
@end
@implementation TestFlutterPluginView
{
CGRect _frame;
int64_t _viewId;
id _args;
}
- (id)initWithFrame:(CGRect)frame
viewId:(int64_t)viewId
args:(id)args
messager:(NSObject<FlutterBinaryMessenger>*)messenger
{
if (self = [super init])
{
_frame = frame;
_viewId = viewId;
_args = args;
NSLog(@"%@",args[@"titleStr"]);
///建立通信通道 用来 监听Flutter 的调用和 调用Fluttter 方法 这里的名称要和Flutter 端保持一致
_channel = [FlutterMethodChannel methodChannelWithName:@"test_flutter_plugin_demo" binaryMessenger:messenger];
__weak __typeof__(self) weakSelf = self;
[_channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
[weakSelf onMethodCall:call result:result];
}];
}
return self;
}
- (UIView *)view{
UIView *nativeView = [[UIView alloc] initWithFrame:_frame];
nativeView.backgroundColor = [UIColor redColor];
_button = [UIButton buttonWithType:UIButtonTypeSystem];
[_button setTitle:@"我是按钮" forState:UIControlStateNormal];
[_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_button setBackgroundColor:[UIColor blueColor]];
_button.frame = CGRectMake(10, 20, 100, 44);
[nativeView addSubview:_button];
self.lblText = [[UILabel alloc] initWithFrame:CGRectMake(140, 20, 200, 44)];
self.lblText.text =_args[@"titleStr"];
self.lblText.backgroundColor =[UIColor blueColor];
self.lblText.textColor =[UIColor whiteColor];
[nativeView addSubview:self.lblText];
[_button addTarget:self action:@selector(flutterMethod) forControlEvents:UIControlEventTouchUpInside];
_textField = [[UITextField alloc] initWithFrame:CGRectMake(100, 125, 200, 44)];
[_textField setText: [NSString stringWithFormat:@"这个数据是原生控制 %d",_count] ];
[nativeView addSubview:_textField];
return nativeView;
}
#pragma mark -- Flutter 交互监听
-(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{
//监听Fluter
if ([[call method] isEqualToString:@"changeNativeTitle"]) {
[_button setTitle:call.arguments forState:UIControlStateNormal];
}
}
//调用Flutter
- (void)flutterMethod{
self.count = self.count+1;
NSString *str = [NSString stringWithFormat:@"原生给flutter的参数 %ld",(long)self.count];
[self.channel invokeMethod:@"clickAciton" arguments:str];
[_textField setText: [NSString stringWithFormat:@"这个数据是原生控制 %ld",(long)_count] ];
if(_count%2==0){
self.lblText.backgroundColor =[UIColor blueColor];
self.lblText.textColor =[UIColor whiteColor];
}else{
self.lblText.backgroundColor =[UIColor orangeColor];
self.lblText.textColor =[UIColor blackColor];
}
}
@end
//
// TestFlutterPluginViewFactory.h
// Pods
//
// Created by wangyun on 2022/4/15.
//
#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface TestFlutterPluginViewFactory : NSObject<FlutterPlatformViewFactory>
/// 重写一个构造方法 来接收 Flutter 相关蚕食
/// @param messenger Flutter类 包含回调方法等信息
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
@end
NS_ASSUME_NONNULL_END
//
// TestFlutterPluginViewFactory.m
// Pods
//
// Created by wangyun on 2022/4/15.
//
#import "TestFlutterPluginViewFactory.h"
#import "TestFlutterPluginView.h"
@interface TestFlutterPluginViewFactory ()
@property(nonatomic)NSObject<FlutterBinaryMessenger>* messenger;
@end
@implementation TestFlutterPluginViewFactory
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
self = [super init];
if (self) {
self.messenger = messenger;
}
return self;
}
#pragma mark -- 实现FlutterPlatformViewFactory 的代理方法
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
return [FlutterStandardMessageCodec sharedInstance];
}
/// FlutterPlatformViewFactory 代理方法 返回过去一个类来布局 原生视图
/// @param frame frame
/// @param viewId view的id
/// @param args 初始化的参数
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args{
TestFlutterPluginView *testFlutterPluginView = [[TestFlutterPluginView alloc] initWithFrame:frame viewId:viewId args:args messager:self.messenger];
return testFlutterPluginView;
}
@end
2、Android 部分代码开发
Android 这部分和iOS 是同一个道理,没有丝毫区别,Android 我们也右键在工具中打开,然后如下图找到位置,Android 所有的代码都在这里进行
Android TestFlutterPluginView全部代码
package com.example.test_flutter_plugin_demo;
import android.content.Context;
import android.graphics.Color;
import android.graphics.SurfaceTexture;
import android.provider.CalendarContract;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.platform.PlatformView;
/**
* Created by sunyd on 1/25/22
*/
public class TestFlutterPluginView extends TextView implements PlatformView, MethodChannel.MethodCallHandler, TextureView.SurfaceTextureListener{
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
public Context context;
/**
* 通道
*/
private MethodChannel methodChannel = null;
public TestFlutterPluginView(Context context, int viewId, Object args, BinaryMessenger messenger) {
super(context);
this.context = context;
Toast.makeText(context, "创建关联成功", Toast.LENGTH_SHORT).show();
setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
setBackgroundColor(Color.argb(255,79,79,79)); //0完全透明 255不透明
//注册
methodChannel = new MethodChannel(messenger, "test_flutter_plugin_demo");
methodChannel.setMethodCallHandler(this);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
handleCall(call, result);
}
private void handleCall(MethodCall methodCall, MethodChannel.Result result) {
switch (methodCall.method) {
//开始预览
case "changeNativeTitle":
Toast.makeText(context, (String)methodCall.arguments, Toast.LENGTH_SHORT).show();
break;
default:
}
}
@Override
public View getView() {
return this;
}
@Override
public void dispose() {
}
}
Android TestFlutterPluginViewFactory全部代码
package com.example.test_flutter_plugin_demo;
import android.content.Context;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MessageCodec;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;
public class TestFlutterPluginViewFactory extends PlatformViewFactory {
private BinaryMessenger messenger = null;
public TestFlutterPluginViewFactory(BinaryMessenger messenger) {
super(StandardMessageCodec.INSTANCE);
this.messenger = messenger;
}
/**
* @param createArgsCodec the codec used to decode the args parameter of {@link #create}.
*/
public TestFlutterPluginViewFactory(MessageCodec<Object> createArgsCodec) {
super(createArgsCodec);
}
@Override
public PlatformView create(Context context, int viewId, Object args) {
return new TestFlutterPluginView(context, viewId, args, this.messenger);
}
}
到此,插件的开发就算是完事了。实现的效果(iOS,Android)如下:
下面就是使用这个插件了,我们如何集成到 别的项目里,在这里 我们只介绍 本地 使用
其实本地使用非常简单。
1、打开我们的项目
?2、打开pubspec.yaml
dependencies:
flutter:
sdk: flutter
test_flutter_plugin_demo:
path: /Users/yunwang/Documents/flutterStudy/flutter_plugin_demo
?flutter_plugin_demo 位插件的名称,就是我们创建插件时候的文件名称,path就是路径了,我们找到插件位置 将路径 粘贴到这里即可
3、pub get
到此就引用完成了。
4、使用我们就和example 里面一摸一样就可以了。
源码demo地址:test_flutter_plugin_demo: flutter插件开发,Android,iOS端通信。
|