一步步实现一个Flutter plugin插件

前言

plugin是属于package的一种,区别是包含有原生的代码,比如Android的java或kotlin代码,或者iOS的Object-C或Swift代码。是通过Flutter Platform Channel实现的。

新建项目

方法一:命令行创建

  • --org后面为域名
  • --template后面设置成plugin表示为插件类型
  • 最后为插件名
flutter create --org com.himmy --template=plugin pluginname

 回车后开始创建,创建过程如下所示


C:\Users\Him>d:

D:\>cd ws

D:\ws>flutter create --org com.himmy --template=plugin pluginbatterylevel
Creating project pluginbatterylevel...
  pluginbatterylevel\.gitignore (created)
  pluginbatterylevel\.idea\libraries\Dart_SDK.xml (created)
  pluginbatterylevel\.idea\libraries\Flutter_for_Android.xml (created)
  pluginbatterylevel\.idea\modules.xml (created)
  pluginbatterylevel\.idea\runConfigurations\example_lib_main_dart.xml (created)
  pluginbatterylevel\.idea\workspace.xml (created)
  pluginbatterylevel\.metadata (created)
  pluginbatterylevel\android\build.gradle (created)
  pluginbatterylevel\android\pluginbatterylevel_android.iml (created)
  pluginbatterylevel\android\src\main\kotlin\com\himmy\pluginbatterylevel\PluginbatterylevelPlugin.kt (created)
  pluginbatterylevel\android\.gitignore (created)
  pluginbatterylevel\android\gradle\wrapper\gradle-wrapper.properties (created)
  pluginbatterylevel\android\gradle.properties (created)
  pluginbatterylevel\android\settings.gradle (created)
  pluginbatterylevel\android\src\main\AndroidManifest.xml (created)
  pluginbatterylevel\CHANGELOG.md (created)
  pluginbatterylevel\ios\Classes\PluginbatterylevelPlugin.h (created)
  pluginbatterylevel\ios\Classes\PluginbatterylevelPlugin.m (created)
  pluginbatterylevel\ios\Classes\SwiftPluginbatterylevelPlugin.swift (created)
  pluginbatterylevel\ios\.gitignore (created)
  pluginbatterylevel\ios\Assets\.gitkeep (created)
  pluginbatterylevel\ios\pluginbatterylevel.podspec (created)
  pluginbatterylevel\lib\pluginbatterylevel.dart (created)
  pluginbatterylevel\LICENSE (created)
  pluginbatterylevel\pluginbatterylevel.iml (created)
  pluginbatterylevel\pubspec.yaml (created)
  pluginbatterylevel\README.md (created)
  pluginbatterylevel\test\pluginbatterylevel_test.dart (created)
Running "flutter pub get" in pluginbatterylevel...                  5.2s
  pluginbatterylevel\example\.gitignore (created)
  pluginbatterylevel\example\.idea\libraries\Dart_SDK.xml (created)
  pluginbatterylevel\example\.idea\libraries\Flutter_for_Android.xml (created)
  pluginbatterylevel\example\.idea\libraries\KotlinJavaRuntime.xml (created)
  pluginbatterylevel\example\.idea\modules.xml (created)
  pluginbatterylevel\example\.idea\runConfigurations\main_dart.xml (created)
  pluginbatterylevel\example\.idea\workspace.xml (created)
  pluginbatterylevel\example\.metadata (created)
  pluginbatterylevel\example\android\app\build.gradle (created)
  pluginbatterylevel\example\android\app\src\main\kotlin\com\himmy\pluginbatterylevel_example\MainActivity.kt (created)
  pluginbatterylevel\example\android\build.gradle (created)
  pluginbatterylevel\example\android\pluginbatterylevel_example_android.iml (created)
  pluginbatterylevel\example\android\app\src\debug\AndroidManifest.xml (created)
  pluginbatterylevel\example\android\app\src\main\AndroidManifest.xml (created)
  pluginbatterylevel\example\android\app\src\main\res\drawable\launch_background.xml (created)
  pluginbatterylevel\example\android\app\src\main\res\mipmap-hdpi\ic_launcher.png (created)
  pluginbatterylevel\example\android\app\src\main\res\mipmap-mdpi\ic_launcher.png (created)
  pluginbatterylevel\example\android\app\src\main\res\mipmap-xhdpi\ic_launcher.png (created)
  pluginbatterylevel\example\android\app\src\main\res\mipmap-xxhdpi\ic_launcher.png (created)
  pluginbatterylevel\example\android\app\src\main\res\mipmap-xxxhdpi\ic_launcher.png (created)
  pluginbatterylevel\example\android\app\src\main\res\values\styles.xml (created)
  pluginbatterylevel\example\android\app\src\profile\AndroidManifest.xml (created)
  pluginbatterylevel\example\android\gradle\wrapper\gradle-wrapper.properties (created)
  pluginbatterylevel\example\android\gradle.properties (created)
  pluginbatterylevel\example\android\settings.gradle (created)
  pluginbatterylevel\example\ios\Runner\AppDelegate.swift (created)
  pluginbatterylevel\example\ios\Runner\Runner-Bridging-Header.h (created)
  pluginbatterylevel\example\ios\Runner.xcodeproj\project.pbxproj (created)
  pluginbatterylevel\example\ios\Runner.xcodeproj\xcshareddata\xcschemes\Runner.xcscheme (created)
  pluginbatterylevel\example\ios\Flutter\AppFrameworkInfo.plist (created)
  pluginbatterylevel\example\ios\Flutter\Debug.xcconfig (created)
  pluginbatterylevel\example\ios\Flutter\Release.xcconfig (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Contents.json (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-1024x1024@1x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-20x20@1x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-20x20@2x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-20x20@3x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-29x29@1x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-29x29@2x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-29x29@3x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-40x40@1x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-40x40@2x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-40x40@3x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-60x60@2x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-60x60@3x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-76x76@1x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-76x76@2x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\AppIcon.appiconset\Icon-App-83.5x83.5@2x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\LaunchImage.imageset\Contents.json (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\LaunchImage.imageset\LaunchImage.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\LaunchImage.imageset\LaunchImage@2x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\LaunchImage.imageset\LaunchImage@3x.png (created)
  pluginbatterylevel\example\ios\Runner\Assets.xcassets\LaunchImage.imageset\README.md (created)
  pluginbatterylevel\example\ios\Runner\Base.lproj\LaunchScreen.storyboard (created)
  pluginbatterylevel\example\ios\Runner\Base.lproj\Main.storyboard (created)
  pluginbatterylevel\example\ios\Runner\Info.plist (created)
  pluginbatterylevel\example\ios\Runner.xcodeproj\project.xcworkspace\contents.xcworkspacedata (created)
  pluginbatterylevel\example\ios\Runner.xcworkspace\contents.xcworkspacedata (created)
  pluginbatterylevel\example\lib\main.dart (created)
  pluginbatterylevel\example\pluginbatterylevel_example.iml (created)
  pluginbatterylevel\example\pubspec.yaml (created)
  pluginbatterylevel\example\README.md (created)
  pluginbatterylevel\example\test\widget_test.dart (created)
Running "flutter pub get" in example...                             5.6s
Wrote 93 files.

All done!
[√] Flutter is fully installed. (Channel stable, v1.9.1+hotfix.4, on Microsoft Windows [Version 10.0.18362.295], locale
    en-001)
[√] Android toolchain - develop for Android devices is fully installed. (Android SDK version 28.0.3)
[√] Android Studio is fully installed. (version 3.4)
[!] IntelliJ IDEA Ultimate Edition is partially installed; more components are available. (version 2018.2)
[√] Connected device is fully installed. (1 available)

Run "flutter doctor" for information about installing additional components.

In order to run your application, type:

  $ cd pluginbatterylevel\example
  $ flutter run

Your application code is in pluginbatterylevel\example\lib\main.dart.

Your plugin code is in pluginbatterylevel\lib\pluginbatterylevel.dart.

Host platform code is in the "android" and "ios" directories under pluginbatterylevel.
To edit platform code in an IDE see https://flutter.dev/developing-packages/#edit-plugin-package.


D:\ws>

方法二:Android Studio创建

File->New->New Flutter Project

选择Flutter Plugin

 填写项目名和项目路径

设置包名,点击Finish就可以开始创建项目

 项目结构

项目结构如下所示,主要包括如下几个部分

  • android:为安卓平台相关的代码
  • ios:为iOS平台相关的代码
  • example:该插件使用的示例项目
  • lib:里面的dart代码为插件代码

具体实现

其实新创建的plugin项目就是一个很简单,但是也很完整的插件,可以直接运行,实现的功能是调用原生代码获取设备的Android版本号

接下来我们就一步步分析该项目,看实现一个plugin都需要哪些步骤

1.Flutter端代码

插件的Flutter端代码在lib文件夹下,如下图的plugin_version.dart文件

实现代码很简单,如下图所示

首先创建一个MethodChannel对象,参数为该channel的名字,需保证唯一

然后创建一个platformVersion方法来获取版本号,通过MethodChannel调用原生的代码来获取版本号,具体在Android或者iOS端的插件代码中实现

invokeMethod中传入的参数为方法名,用于区分不同的方法,同个插件中可以实现多个方法提供不同的功能

该方法是异步返回的,通过Future和async实现

import 'dart:async';
import 'package:flutter/services.dart';

class PluginVersion {
  static const MethodChannel _channel = const MethodChannel('plugin_version');

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}

2.Android端代码

代码很简单,首先提供一个静态方法registerWith,用于注册该插件,该方法在具体使用的项目中的Android原生代码中调用,后面会介绍。创建MethodChannel的第二个参数为channel名字,需与步骤一种Flutter端代码中的channel名一致。

setMethodCallHandler方法注册监听,PluginVersionPlugin实现了MethodCallHandler 接口,实现了里面的onMethodCall方法,当Flutter端有请求时,onMethodCall会被回调,根据不同的方法名调用不同的native代码,然后返回处理结果。

通过Result对象来设置处理结果,result.success设置成功的结果,result.error设置失败的结果,result.notImplemented表示找不到实现的方法。

kotlin代码

package com.himmy.plugin_version

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
import io.flutter.plugin.common.PluginRegistry.Registrar

class PluginVersionPlugin: MethodCallHandler {
  companion object {
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      val channel = MethodChannel(registrar.messenger(), "plugin_version")
      channel.setMethodCallHandler(PluginVersionPlugin())
    }
  }

  override fun onMethodCall(call: MethodCall, result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }
}

Android端默认的实现代码是kotlin,我们也可以仿照原来的代码,写一个Java的实现

package com.himmy.pluginbatterylevel;

import android.os.Build;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;

public class PluginVersionPlugin implements MethodChannel.MethodCallHandler {

    public static void registerWith(PluginRegistry.Registrar registrar) {
        MethodChannel channel = new MethodChannel(registrar.messenger(), "plugin_version");
        channel.setMethodCallHandler(new PluginbatterylevelPlugin());
    }

    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        if ("getPlatformVersion".equals(methodCall.method)) {
            result.success("Android " + Build.VERSION.RELEASE);
        } else {
            result.notImplemented();
        }
    }
}

3.iOS端代码

iOS端主要有如下三个文件

具体代码实现如下所示

PluginVersionPlugin.h

#import <Flutter/Flutter.h>

@interface PluginVersionPlugin : NSObject<FlutterPlugin>
@end

 PluginVersionPlugin.m

#import "PluginVersionPlugin.h"
#import <plugin_version/plugin_version-Swift.h>

@implementation PluginVersionPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  [SwiftPluginVersionPlugin registerWithRegistrar:registrar];
}
@end

SwiftPluginVersionPlugin.swift

import Flutter
import UIKit

public class SwiftPluginVersionPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "plugin_version", binaryMessenger: registrar.messenger())
    let instance = SwiftPluginVersionPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

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

4.plugin引用和使用

新建的插件项目默认提供了一个示例项目,这是一个Flutter项目,引用了该插件项目,可以直接运行

对插件的引用配置在pubspec.yaml文件中,如下所示,我们配置了对plugin_version插件的引用,path表示本地文件引用,使用的是相对路径../表示上一级目录,也就是我们的插件项目。

dev_dependencies:
  flutter_test:
    sdk: flutter

  plugin_version:
    path: ../

 配置完对插件的引用后,点击pubspec.yaml编辑页顶部的Package get安装插件

在如下路径有一个自动生成的GeneratedPluginRegistrant类,里面会自动生成对对插件PluginVersionPlugin的导入和注册

自动生成的GeneratedPluginRegistrant代码如下

package io.flutter.plugins;

import io.flutter.plugin.common.PluginRegistry;
import com.himmy.plugin_version.PluginVersionPlugin;

/**
 * Generated file. Do not edit.
 */
public final class GeneratedPluginRegistrant {
  public static void registerWith(PluginRegistry registry) {
    if (alreadyRegisteredWith(registry)) {
      return;
    }
    PluginVersionPlugin.registerWith(registry.registrarFor("com.himmy.plugin_version.PluginVersionPlugin"));
  }

  private static boolean alreadyRegisteredWith(PluginRegistry registry) {
    final String key = GeneratedPluginRegistrant.class.getCanonicalName();
    if (registry.hasPlugin(key)) {
      return true;
    }
    registry.registrarFor(key);
    return false;
  }
}

 GeneratedPluginRegistrant代码中PluginVersionPlugin类的导入和注册是报红的,这个不用在意,以为这代码是自动生成的,可以正常运行,registry.registrarFor传入的是PluginVersionPlugin类的完整路径,我猜测是通过反射来完成注册的

示例中的MainActivity中的代码也是自动生成的,里面在onCreate方法中调用GeneratedPluginRegistrant.registerWith完成对所有插件的注册。

完成插件的引用和注册后,就可以在Dart代码中使用插件了,下面是具体的使用,页面很简单,直接调用插件的PluginVersion.platformVersion方法获取设备Android版本号,然后显示在页面正中间。

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:plugin_version/plugin_version.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    try {
      platformVersion = await PluginVersion.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Text('Running on: $_platformVersion\n'),
        ),
      ),
    );
  }
}

参考

 

 

posted @ 2019-10-11 15:43  野猿新一  阅读(241)  评论(0编辑  收藏  举报