Flutter开发插件(swift、kotlin)

开发环境

flutter doctor                   
[✓] Flutter (Channel stable, 3.7.7,on macOS 13.1 22C65 darwin-x64, locale
    zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.1)
[✓] IntelliJ IDEA Ultimate Edition (version 2021.3)
[✓] VS Code (version 1.76.2)

一:新建插件工程

若已打开Android Studio:File->New->New Flutter Project->Next

  1. Project name:工程名 注意非驼峰命名,采用下划线连接(xx_xx_xx,lower_case_with_underscores)

  2. Project location:保存路径,文件夹名注意与Project name一致,若不改则使用Project name名称

  3. Description:工程介绍

  4. Project type:工程选择,这里选择plugin

  5. Organization:包名

二:iOS功能开发

1:开发目录确定

在项目根目录 example的iOS目录下执行pod install

cd example/ios

pod install

右键iOS,最底部Flutter -> Open iOS module in Xcode或者在文件夹example/ios中找到Runner.xcworkspace双击打开,这个时候就能看到Runner和Pod两个target。

  • Runner:example 可运行的主工程,用来测试插件的,用来Flutter启动iOS的

  • Pod:将插件生成本地pod库,用来调试

    点击展开Pod->Development Pods->...->Classes,一直到Classes就是我们插件代码放置的位置,可以看到已经生成的xxxPlugin.swift

2:定义方法

根目录的lib文件夹中自动生成的三个文件

  • xx_dart:项目调用插件的唯一途径

  • xx_method_channel:平台通道,将API暴露给Flutter应用程序,<u>若不需要跨端就直接在此处处理逻辑了</u>

  • xx_platform_interface:平台接口,定义了插件的API。

也是是我们的开发顺序是:xx_dart->xx_platform_interface->xx_method_channel

  1. xx_dart:定义我们此插件需要为别的项目提供哪些能力,也就是插件的api。

  2. xx_platform_interface:定义平台也就是原生端需要提供给插件的响应api,接口声明

  3. xx_method_channel:通过通道调用原生方法

原生去响应通道的方法:

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    result("iOS " + UIDevice.current.systemVersion)
  }

已自动生成的方法,需要根据call.method判断Dart那边调用原生的方法。

学习方法使用,无非就是相互调用,参数互传。

思路:我需要插件帮我计算下a+b=?,插件需要2秒后给我个"hellow world"

xx.dart中定义方法

class xxPlayer {
  Future<String?> getPlatformVersion() {
    return xxPlayerPlatform.instance.getPlatformVersion();
  }
  // 原生去计算A+B
  Future<int?> sumAB(){
    return xxPlayerPlatform.instance.sumAB(a,b);
  }

  // 监听原生调用
  void listenNative(NativeCallBack nativeCallBack) {
    return EchoPlayerPlatform.instance.listenNative(nativeCallBack);
  }
}

xx_platform_interface.dart声明接口

Future<int?> sumAB(int a, int b) {
    throw UnimplementedError('sumAB() has not been implemented.');
  }

 void listenNative(NativeCallBack nativeCallBack) {
   throw UnimplementedError('startCount() has not been implemented.');
 }

xx_method_channel.dart重写实现接口,通道调用原生

 typedef NativeCallBack= void Function(dynamic result);

   @override
  Future<int?> sumAB(int a,int b) async {
    final result = await methodChannel
        .invokeMethod<int>('sumAB', {"a": a, "b": b});
    return result;
  }

 // 和fullter调用原生一样,都是通过通道通信的
  @override
  void listenNative(NativeCallBack nativeCallBack) {
    methodChannel.setMethodCallHandler((call) {
      if (call.method == 'countUp') {
        nativeCallBack(call.arguments);
      }
      return Future(() => null);
    });
  }

xxPlugin.swift中实现方法

 public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        if call.method == "sumAB" {
            // 取参数  插件接口已定义必填,可强制拆包
            let args = call.arguments as! [String: Int]
            let a = args["a"]!
            let b = args["b"]!
            result(sumAB(a: a, b: b))
            // 执行方法并回调
            result(sumAB(a: a, b: b))
        } else if call.method == "startCount" {
           // 定时器模拟重复调用的场景,延迟或者其他情况直接使用channel.invokeMethod即可
            countTime()
        }
        result("iOS " + UIDevice.current.systemVersion)
 }
 public func sumAB(a: Int, b: Int) -> Int {
    return a + b
 }
 var count = 0
 public func countTime() {
   Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(sendToFlutter), userInfo: nil, repeats: true)
 }

 @objc func sendToFlutter() {
    count += 1
    channel!.invokeMethod("countUp", arguments: ["count": count])
  }

exmaple/main.dart 调用测试

TextButton(
     onPressed: () {
       _xxPlugin
        .sumAB(3, 4)
        .then((value) => {debugPrint(value.toString())});
       },
      child: const Text("SumAB"))

 TextButton(
        onPressed: () {
          //开启定时计数
          _echoPlayerPlugin.startCount();
         //监听计数回调
          _echoPlayerPlugin.listenNative((result) {
             print(result);
            });
           },
       child: const Text("countUp"))

三:Android功能开发

在工程example/android 上右键open Android module in Android studio,依赖加载有些久,打开后就可以看到我们其实要开发的就是个Android的module。

Android这边要好些的是channel已经是是变量,可以直接主动像flutter发送消息

class XXPlugin: 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 Activity
  private lateinit var channel : MethodChannel

  //注册通道
  override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    channel = MethodChannel(flutterPluginBinding.binaryMessenger, "echo_player")
    channel.setMethodCallHandler(this)
  }

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

sumABstartCount的实现


    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 == "sumAB") {
            // 获取传递的参数
            val args = call.arguments as? Map<String, Int>
            val a = args?.get("a")
            val b = args?.get("b")
            if (a != null && b != null) {
                result.success(a + b)
            }
        } else if (call.method == "startCount") {
            // 定时器模拟重复调用的场景,延迟或者其他情况直接使用channel.invokeMethod即可
            countTime()
        } else {
            result.notImplemented()
        }
    }

    fun countTime() {
        val timer = Timer()
        val delay = 0L // 0 second delay
        val period = 1000L // 1 second interval

        // 回去主线程 否则会崩溃
        val handler = android.os.Handler(Looper.getMainLooper())

        val task = object : TimerTask() {
            override fun run() {
                handler.post{
                    count++
                    channel.invokeMethod("countUp", mapOf("count" to count))
                }

            }
        }
        timer.scheduleAtFixedRate(task, delay, period)
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容