Flatterplattformkanäle für Linux

Einführung

Dieser Artikel richtet sich an Benutzer, die Plattformkanäle für Linux mit Flutter verwenden. Ich werde schreiben, wie man Dart in die Linux-Plattform integriert. Ich werde das Verfahren zum Erstellen der Flatterentwicklungsumgebung, das Verfahren zum Veröffentlichen als Lizenz oder Paket usw. nicht schreiben.

Die Linux-Plattform in Flutter ist derzeit für Desktop, Flutter for Desktop wird zum Zeitpunkt des Schreibens dieses Artikels (2020/11/3) als Alpha-Version behandelt. Bitte beachten Sie, dass sich der in diesem Artikel beschriebene Inhalt, insbesondere unter "linux /", in Zukunft möglicherweise erheblich geändert hat.

Aufgrund dieser Umstände wollte ich schreiben, um herauszufinden, wie man etwas macht. Wenn Sie Fehler oder veraltete Teile bemerken, würden wir uns freuen, wenn Sie darauf hinweisen könnten.

Wir haben in der folgenden Umgebung bestätigt.

$ uname -a
Linux chama 5.5.8 #1 SMP Sat Mar 7 22:29:22 JST 2020 x86_64 x86_64 x86_64 GNU/Linux
$ lsb_release -d
Description:    Ubuntu 18.04.5 LTS
$ flutter --version
Flutter 1.24.0-7.0.pre.42 • channel master • https://github.com/flutter/flutter
Framework • revision c0ef94780c (2 days ago) • 2020-10-31 03:12:27 -0700
Engine • revision 3460519398
Tools • Dart 2.12.0 (build 2.12.0-3.0.dev)

Übersicht über Plattformkanäle

Beim Aufrufen plattformspezifischer APIs in Flutter verwenden wir einen Mechanismus namens Plattformkanäle.

Mit Plattformkanälen können Sie flexibel mit einer Vielzahl von Plattformen arbeiten Es verfügt über einen einfachen asynchronen Nachrichtenübermittlungsmechanismus.

Wenn es nur asynchron ist, scheint der Code auf der Seite, die die API verwendet, schwer zu verstehen zu sein. Dart verfügt über Async / Warten, wodurch es flexibel und einfach zu implementieren ist, ohne die Lesbarkeit zu beeinträchtigen.

Platform Channels bietet je nach Zweck die folgenden drei APIs. Dieser Artikel ist leicht zu verstehen, wie diese richtig verwendet werden und wie sie verwendet werden.

Erstellen Sie zunächst eine Vorlage

Erstellen Sie den Befehl "Flattern erstellen", um ein Projekt zu erstellen. Zu diesem Zeitpunkt können Sie aus der Vorlage für die Plugin-Entwicklung ein Projekt erstellen, indem Sie "--template plugin" angeben. Geben Sie für "--org" die Organisation [Reverse Domain Name Notation] an (https://en.wikipedia.org/wiki/Reverse_domain_name_notation). Erstellen Sie mit dem folgenden Befehl ein Plugin für die Linux-Plattform mit dem Namen "platform_proxy".

flutter create --platforms linux --template plugin --org xyz.takeoverjp.example platform_proxy

Es ist nicht der Hauptpunkt, aber es wird empfohlen, an dieser Stelle ein Commit durchzuführen, damit Sie sehen können, was Sie manipuliert haben.

Versuchen Sie, die Beispiel-App auszuführen

Im generierten platform_proxy gibt es auch eine Beispielanwendung namens example. Wenn Sie die Beispiel-App ausführen, wird der folgende Bildschirm angezeigt. Wenn Sie Linux berührt haben, stammt es möglicherweise aus einer Zeichenfolge, aber es ist ein Beispiel, das das Ergebnis von "uname -v" ausgibt.

Screenshot from 2020-11-03 02-21-48.png

Lesen Sie den generierten Code

Sie können den Anrufverlauf verstehen, indem Sie die folgenden drei Punkte des generierten Codes beachten.

Bei der Entwicklung der Plugin-API werden wir auch den Komponententest ändern, sodass wir ihn auch hier vorstellen werden.

Plugin API-Aufrufer

platform_proxy/example/lib/main.dart


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 PlatformProxy.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'),
        ),
      ),
    );
  }
}

PlatformProxy.platformVersion ist die von der Vorlage hinzugefügte API. Wie Sie in den Kommentaren sehen können, ist PlatformChannel eine asynchrone Nachricht. Da der diesmal verwendete MethodChannel auch ein asynchroner Aufruf ist, wird async / await verwendet. Für Darts async / await ist dieser Artikel leicht zu verstehen. Einfach ausgedrückt ist dies die Syntax zum Empfangen einer Antwort auf einen asynchronen Aufruf in der nächsten Ereignisschleife.

Plugin API Implementierung (Dart Teil)

platform_proxy/lib/platform_proxy.dart


class PlatformProxy {
  static const MethodChannel _channel =
      const MethodChannel('platform_proxy');

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

Eine Implementierung von PlatformProxy # platformVersion. Es erstellt einen "MethodChannel" für den "platform_proxy" und ruft den "MethodChannel # invokeMethod" auf.

Hier Wenn sich die Kanalnamen überschneiden, wird der Handler überschrieben und die Kommunikation ist nicht korrekt möglich. Ich konnte es nicht in der Kopffamilie finden, aber in dieser Seite (Domainname) / (Plugin-Name) / (Kanalname) Ist empfohlen. In der Vorlage wird, wie oben erwähnt, "MethodChannel" mit dem direkten Kanalnamen generiert. Vergessen Sie nicht, dies zu ändern, insbesondere für Plug-Ins, da ein hohes Risiko für Namenskonflikte besteht. Zum Beispiel in Flutter offizielles file_chooser Plugin als flutter / filechooser ist.

MethodChannel # invokeMethod Übergeben Sie den Methodennamen und die Argumente, die Sie aufrufen möchten, und rufen Sie sie asynchron auf. Da die Antwort "Zukunft" lautet, können Sie mit "Warten" auf das Ergebnis warten.

Der an invokeMethod übergebene Methodenname ist übrigens nur eine Zeichenfolge. Selbst wenn der Aufrufer den Methodennamen eingibt, führt dies nicht zu einem Kompilierungsfehler, sondern zu einer Laufzeitausnahme. Selbst wenn der Typ- oder Eigenschaftsname falsch ist, kann er beim Kompilieren nicht erkannt werden. Es liegt in der Verantwortung dieser Ebene, die Plugin-API besser lesbar zu machen und gleichzeitig einen solchen Missbrauch zu verhindern.

Plugin API Implementierung (nativer Teil)

platform_proxy/linux/platform_proxy_plugin.cc


static void platform_proxy_plugin_handle_method_call(
    PlatformProxyPlugin* self,
    FlMethodCall* method_call) {
  g_autoptr(FlMethodResponse) response = nullptr;

  const gchar* method = fl_method_call_get_name(method_call);

  if (strcmp(method, "getPlatformVersion") == 0) {
    struct utsname uname_data = {};
    uname(&uname_data);
    g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version);
    g_autoptr(FlValue) result = fl_value_new_string(version);
    response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
  } else {
    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
  }

  fl_method_call_respond(method_call, response, nullptr);
}
...
static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
                           gpointer user_data) {
  PlatformProxyPlugin* plugin = PLATFORM_PROXY_PLUGIN(user_data);
  platform_proxy_plugin_handle_method_call(plugin, method_call);
}

void platform_proxy_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
  PlatformProxyPlugin* plugin = PLATFORM_PROXY_PLUGIN(
      g_object_new(platform_proxy_plugin_get_type(), nullptr));

  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel =
      fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
                            "platform_proxy",
                            FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(channel, method_call_cb,
                                            g_object_ref(plugin),
                                            g_object_unref);

  g_object_unref(plugin);
}

Flutter für Desktop Linux implementiert Ereignisschleifen mit GLib. Ich war nicht an GLib gewöhnt, also war ich zuerst überrascht, GLib-Funktionen (g _ ***) sind fast Code für die Objektverwaltung, machen Sie sich also keine Sorgen. Es ist leichter zu verstehen, wenn Sie auf die Flatterfunktion (fl _ ***) achten.

Zum FlMethodChannel, der von fl_method_channel_new bei der Registrierung des Plugins generiert wird Die Rückruffunktion ist bei fl_method_channel_set_method_call_handler registriert.

Wenn die Ziel-API auf der Dart-Seite aufgerufen wird, ruft die Flutter Engine method_call_cb und auf Rufen Sie platform_proxy_plugin_handle_method_call mit den entsprechenden Argumenten auf.

Holen Sie sich dann den Methodennamen mit fl_method_call_get_name und führen Sie die entsprechende Verarbeitung durch. In dieser "getPlatformVersion" wird "uname (2)" aufgerufen. Setzen Sie das Ergebnis als Rückgabewert mit "FL_METHOD_RESPONSE (fl_method_success_response_new (result))" Wir senden eine Antwort an die Flutter Engine zurück, indem wir "fl_method_call_respond" aufrufen.

Es ist notwendig, Plattformkanäle mit dem Hauptthread zu kommunizieren. Wenn Sie das Ergebnis in einem anderen Thread berechnet haben, verwenden Sie "g_main_context_invoke" usw., um den Kontext in den Hauptthread zu verschieben und dann die Antwort zurückzugeben.

Unit Test der Plugin API

Sie können einen Komponententest der Plugin-API ausführen, indem Sie den Flattertest ausführen. MethodChannel hat eine Methode für DI namens setMockMethodCallHandler. Ein Komponententest kann durchgeführt werden, wobei der native Teil getrennt ist. Im Gegenteil, es ist kein Test für die Implementierung des nativen Teils, daher muss er separat durchgeführt werden.

test/plugin_method_test.dart


void main() {
  const MethodChannel channel = MethodChannel('platform_proxy');

  TestWidgetsFlutterBinding.ensureInitialized();

  setUp(() {
    channel.setMockMethodCallHandler((MethodCall methodCall) async {
      return '42';
    });
  });

  tearDown(() {
    channel.setMockMethodCallHandler(null);
  });

  test('getPlatformVersion', () async {
    expect(await PlatformProxy.platformVersion, '42');
  });
}

API hinzufügen

Beim Aufrufen von Plattformfunktionen von Dart in BasicMessageChannel Sie können eine API hinzufügen, indem Sie den obigen drei Codes N hinzufügen.

Wenn Sie einen anderen Typ zurückgeben oder ein Argument verwenden möchten Beispiel und [Header-Kommentar](https://github.com/flutter/engine/tree/master/shell/platform/linux/public/ flutter_linux) wird hilfreich sein.

Weitere Variationen finden Sie in den folgenden Beispielprojekten. takeoverjp/flutter-linux-plugin-example: A plugin example for platform channels on Linux desktop.

[Referenz] Über das automatisch generierte Paket Pigeon

Übrigens wird bei der obigen Methode der Methodenname als einfache Zeichenfolge behandelt. Der Typ für das Argument und den Rückgabewert für jede Methode ist eine Gentleman-Vereinbarung. Wenn dies zwischen dem Dart-Teil und dem Native-Teil abweicht, ist dies eine Ausnahme oder das schlimmste undefinierte Verhalten. Wenn Sie immer über das Plugin darauf zugreifen und es ordnungsgemäß testen, können Sie die Möglichkeit tatsächlicher Probleme verringern, haben aber immer noch Angst.

Als Gegenmaßnahme wird ein Paket zur automatischen Generierung namens pigeon vorbereitet. Aus einer Pigeon-Datei, die in einer Teilmenge von Dart geschrieben ist, generieren wir den Code für die Kommunikation zwischen der Plattform und Dart wie oben beschrieben.

Zum Zeitpunkt des Schreibens des Artikels (2020/11/3) wird Desktop jedoch noch nicht mit 0.1.14 unterstützt. Es ist weiterhin als Werkzeug verfügbar, um eine Implementierung des Dart-Teils zu generieren. Es ist ein wichtiges Element, das Symbol mit dem Native-Teil abzugleichen, daher wird es eine etwas enttäuschende Verwendung sein, aber Ich werde die Methode als Referenz belassen.

Der Fluss der automatischen Erzeugung ist wie folgt. API-Definitionsdatei und generierter Dart-Code Wenn Sie sich auch /takeoverjp/flutter-linux-plugin-example/blob/master/lib/pigeon_platform_proxy.dart ansehen, ist es möglicherweise einfacher, das Bild zu erfassen.

  1. Fügen Sie "pigeon" zu "dev_dependencies" in "pubspec.yaml" hinzu
  2. Bereiten Sie eine Dartdatei vor, die die API-Prototypdeklaration und die erforderlichen Typen definiert.
  3. Generieren Sie den Dart-Teil mit dem folgenden Befehl
flutter pub run pigeon --input pigeons/platform_proxy_message.dart --dart_out lib/pigeon_platform_proxy.dart

Danach wird der native Teil gemäß der generierten Dartdatei implementiert. Da die generierte Dartdatei BasicMessageChannel verwendet, muss auch der native Teil übereinstimmen.

Als Einschränkung der von Pigeon (0.1.14) erstellten API müssen das Argument und der Rückgabewert die Klasse sein, die in einer oder mehreren API-Definitionsdateien definiert ist. Das heißt, es wird nicht unterstützt, Ints als Argumente zu akzeptieren, mehrere Werte als Argumente zu akzeptieren oder Ints zurückzugeben. Infolgedessen durchläuft es immer die Klasse, sodass sowohl der Passant als auch der Empfänger etwas redundant sind. Außerdem schien es noch keine Ereignisbenachrichtigungen und Methodenaufrufe von der Plattformseite zu unterstützen.

abschließend

Ich habe versucht, anhand des als Plugin-Vorlage generierten Codes herauszufinden, wie Plattformkanäle in Linux Desktop verwendet werden. Die Plattformkanäle selbst sind einfach, aber es ist ein ziemlich großer Eindruck, da sowohl der Dart-Teil als auch der Native-Teil implementiert werden müssen.

Für die Verarbeitung, die große Datenmengen verarbeitet, sind Plattformkanäle, auf denen Serialisierung ausgeführt wird, nachteilig. Ich denke, es ist besser, ffi zu verwenden.

Wenn Sie jedoch IPC möchten und der Kommunikationspartner Unix Domain Socket / dbus / grpc usw. unterstützt. Erstens können Sie Ihr Ziel möglicherweise erreichen, indem Sie dart: io / dbus.dart / grpc-dart direkt von Dart verwenden.

Vor diesem Hintergrund sind Plattformkanäle für Android / iOS erforderlich, um die Java / Kotlin / Obj-C / Swift-Plattform aus der API verwenden zu können. Wenn Sie nur an Linux denken, kommt es möglicherweise nicht sehr ins Spiel.

Ungefähr zu dieser Zeit musste ich jede Methode richtig kennen, damit ich die Methode auswählen konnte, die zu meiner Situation passt. .. ..

Referenz

Recommended Posts

Flatterplattformkanäle für Linux
pyenv für Linux
[Für Memo] Linux Teil 2
Wofür ist Linux?
Ihr eigener Koredake ist ein Linux-Befehl
Linux Kernel Build für DE10nano
Platform Channel VS FFI (Fremdfunktionsschnittstelle) unter Flutter unter Linux
Empfohlene Linux-Distribution für Anfänger
[Linux] [Grundeinstellung] [Flattern] Zusammenfassung
Linux Command Dictionary (für mich)
Linux Command Memorandum [für Anfänger]
Praktische Linux-Verknüpfung (für Anfänger)
Praktische Tastenkombination für Linux-Befehle! !! !!
Häufig verwendete Linux-Befehle (für Anfänger)
[Muss für Anfänger] Grundlagen von Linux
Installationsverfahren für Teamviewer für Linux (CentOS)
pykintone unter Windows Subsystem für Linux
Linux-Grundausbildung für Front-End-Ingenieure
Verwenden Sie Azure AD für die Linux-Authentifizierung