Cet article est destiné à ceux qui utilisent les canaux de plate-forme pour Linux avec Flutter. J'écrirai comment intégrer Dart à la plateforme Linux. Je n'écrirai pas la procédure de création de l'environnement de développement Flutter, la procédure de publication sous forme de licence ou de package, etc.
La plate-forme Linux de Flutter est actuellement pour Desktop,
Flutter for Desktop est traité comme une version alpha au moment de la rédaction de cet article (2020/11/3).
Veuillez noter que le contenu décrit dans cet article, en particulier sous linux /
, peut avoir changé de manière significative à l'avenir.
En raison de telles circonstances, j'avais l'intention d'écrire en pensant à «comment savoir comment faire». Si vous remarquez des erreurs ou des pièces obsolètes, nous vous serions reconnaissants de bien vouloir les signaler.
Nous avons confirmé dans l'environnement suivant.
$ 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)
Lors de l'appel d'API spécifiques à la plate-forme dans Flutter, nous utilisons un mécanisme appelé Platform Channels.
Les canaux de plate-forme vous permettent de travailler de manière flexible avec une variété de plates-formes Il dispose d'un mécanisme de transmission de messages asynchrone simple.
S'il est uniquement asynchrone, le code du côté qui utilise l'API semble difficile à comprendre, Dart a async / await, ce qui le rend flexible et simple à implémenter sans compromettre la lisibilité.
Platform Channels fournit les trois API suivantes en fonction de l'objectif. Cet article est facile à comprendre pour savoir comment les utiliser correctement et comment ils sont utilisés.
Créez une commande flutter create
pour créer un projet.
À ce stade, vous pouvez créer un projet à partir du modèle pour le développement du plugin en spécifiant --template plugin
.
Pour --org
, spécifiez l'organisation Notation inversée du nom de domaine.
Créez un plugin pour la plateforme Linux avec le nom "platform_proxy" avec la commande suivante.
flutter create --platforms linux --template plugin --org xyz.takeoverjp.example platform_proxy
Ce n'est pas le point principal, mais il est recommandé de git commit à ce stade afin que vous puissiez voir ce que vous avez falsifié.
Dans le platform_proxy
généré, il y a aussi un exemple d'application appelé exemple
.
Lorsque vous exécutez l'exemple d'application, l'écran suivant s'affiche.
Si vous avez touché Linux, cela peut provenir d'une chaîne, mais c'est un exemple qui produit le résultat de uname -v
.
Vous pouvez comprendre le flux d'appels en prêtant attention aux trois suivants du code généré.
platform_proxy/example/lib/main.dart
platform_proxy/lib/platform_proxy.dart
platform_proxy/linux/platform_proxy_plugin.cc
De plus, lors du développement de l'API Plugin, nous modifierons également le test unitaire, nous allons donc le présenter ici également.
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
est l'API ajoutée par modèle.
Comme vous pouvez le voir dans les commentaires, PlatformChannel est un message asynchrone.
Puisque le MethodChannel utilisé cette fois est également un appel asynchrone, async / await est utilisé.
Pour async / await de Dart, cet article est facile à comprendre.
En termes simples, c'est la syntaxe pour recevoir une réponse à un appel asynchrone dans la prochaine boucle d'événements.
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;
}
}
Une implémentation de PlatformProxy # platformVersion
.
Il crée un MethodChannel
pour le platform_proxy
et appelle le MethodChannel # invokeMethod
.
Ici, Si les noms de canaux se chevauchent, le gestionnaire sera écrasé et la communication ne sera pas possible correctement.
Je n'ai pas pu le trouver à la tête de la famille, mais dans cette page (nom de domaine) / (nom du plugin) / (nom du canal)
Est recommandé.
Dans le modèle, comme mentionné ci-dessus, MethodChannel
est généré avec le nom du canal direct,
N'oubliez pas de le changer, en particulier pour les plugins, car le risque de conflits de noms est élevé.
Par exemple, dans Flutter official file_chooser plugin, comme flutter / filechooser
est.
MethodChannel # invokeMethod
Transmettez le nom de la méthode et les arguments que vous souhaitez appeler et appelez-les de manière asynchrone.
De plus, puisque la réponse est Future, vous pouvez attendre le résultat avec await.
Au fait, le nom de la méthode passé à invokeMethod
n'est qu'une chaîne.
Même si l'appelant saisit le nom de la méthode, cela n'entraînera pas une erreur de compilation mais une exception d'exécution.
Même si le type ou le nom de propriété est incorrect, il ne peut pas être détecté au moment de la compilation.
Il est de la responsabilité de cette couche de rendre l'API du plugin plus lisible tout en empêchant une telle utilisation abusive.
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 for Desktop Linux implémente des boucles d'événements à l'aide de GLib.
Je n'étais pas habitué à GLib, donc j'ai été décontenancé au début,
Les fonctions GLib (g _ ***
) sont presque du code pour la gestion des objets, alors ne vous inquiétez pas.
Il sera plus facile à comprendre si vous faites attention à la fonction de scintillement (fl _ ***
).
Vers le FlMethodChannel
généré par fl_method_channel_new
lors de l'enregistrement du plugin
La fonction de rappel est enregistrée avec fl_method_channel_set_method_call_handler
.
Lorsque l'API cible est appelée côté Dart, le Flutter Engine appelle method_call_cb
et
Appelez platform_proxy_plugin_handle_method_call
avec les arguments convertis de manière appropriée.
Ensuite, récupérez le nom de la méthode avec fl_method_call_get_name
et effectuez le traitement correspondant.
Dans ce getPlatformVersion
,uname (2)
est appelé.
Définissez le résultat comme une valeur de retour avec FL_METHOD_RESPONSE (fl_method_success_response_new (result))
Nous renvoyons une réponse au Flutter Engine en appelant fl_method_call_respond
.
De plus, la communication des canaux de plate-forme est effectuée par le thread principal est requise.
Si vous avez calculé le résultat dans un autre thread, utilisez g_main_context_invoke
etc. pour déplacer le contexte vers le thread principal, puis renvoyez la réponse.
Vous pouvez exécuter un test unitaire de l'API Plugin en exécutant flutter test
.
MethodChannel
a une méthode pour DI appelée setMockMethodCallHandler
.
Un test unitaire peut être effectué avec la partie native séparée.
Au contraire, il ne s'agit pas d'un test d'implémentation de la partie Native, il faut donc le faire séparément.
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');
});
}
Lors de l'appel de fonctions de plate-forme à partir de Dart dans BasicMessageChannel Vous pouvez ajouter une API en ajoutant N aux trois codes ci-dessus.
Si vous voulez retourner un autre type ou prendre un argument exemple et [commentaire d'en-tête](https://github.com/flutter/engine/tree/master/shell/platform/linux/public/ flutter_linux) sera utile.
Veuillez également vous référer aux exemples de projets suivants pour d'autres variantes. takeoverjp/flutter-linux-plugin-example: A plugin example for platform channels on Linux desktop.
D'ailleurs, dans la méthode ci-dessus, le nom de la méthode est traité comme une simple chaîne de caractères, Le type de l'argument et de la valeur de retour pour chaque méthode est un gentleman's agreement. Si cela diffère entre la partie Dart et la partie native, ce sera une exception ou le pire comportement indéfini. Si vous y accédez toujours via le plugin et que vous le testez correctement, vous pouvez réduire la possibilité de problèmes réels, mais vous avez toujours des préoccupations.
En guise de contre-mesure, un package pour la génération automatique appelé pigeon est préparé. A partir d'un fichier Pigeon écrit dans un sous-ensemble de Dart, nous allons générer le code pour communiquer entre la plateforme et Dart comme décrit ci-dessus.
Cependant, le bureau n'est pas encore pris en charge à la version 0.1.14 au moment de la rédaction de l'article (2020/11/3). Il est toujours disponible en tant qu'outil pour générer une implémentation de la partie Dart. C'est un élément important pour faire correspondre le symbole avec la partie native, donc ce sera un peu décevant, mais Je laisserai la méthode pour référence.
Le flux de génération automatique est le suivant. Fichier de définition de l'API et [Generated Dart code](https://github.com Si vous regardez également /takeoverjp/flutter-linux-plugin-example/blob/master/lib/pigeon_platform_proxy.dart), il peut être plus facile de saisir l'image.
pigeon
à dev_dependencies
dans pubspec.yaml
flutter pub run pigeon --input pigeons/platform_proxy_message.dart --dart_out lib/pigeon_platform_proxy.dart
Après cela, la partie native sera implémentée en fonction du fichier de fléchettes généré. Étant donné que le fichier de fléchettes généré utilise BasicMessageChannel, il est également nécessaire de faire correspondre la partie native.
En tant que restriction de l'API créée par Pigeon (0.1.14), l'argument et la valeur de retour doivent être la classe définie dans un ou moins de fichiers de définition d'API. Autrement dit, il ne prend pas en charge l'acceptation des entiers comme arguments, l'acceptation de plusieurs valeurs comme arguments ou le renvoi des entiers. En conséquence, cela passe toujours par la classe, donc le passeur et le receveur sont un peu redondants. En outre, il ne semblait pas encore prendre en charge les notifications d'événements et les appels de méthode du côté de la plate-forme.
J'ai essayé de voir comment utiliser les canaux de plate-forme dans Linux Desktop en fonction du code généré en tant que modèle de plugin. Les canaux de la plate-forme eux-mêmes sont simples, mais c'est assez grande impression car il est nécessaire d'implémenter à la fois la partie Dart et la partie native.
Pour le traitement qui gère de grandes quantités de données, les canaux de plate-forme qui exécutent la sérialisation sont désavantageux, donc je pense qu'il est préférable d'utiliser ffi.
D'autre part, si vous voulez faire IPC et que le partenaire de communication prend en charge socket / dbus / grpc de domaine unix, etc. En premier lieu, vous pourrez peut-être atteindre votre objectif en utilisant dart: io / dbus.dart / grpc-dart directement à partir de Dart.
Dans cet esprit, les canaux de plate-forme sont nécessaires pour utiliser l'API java / kotlin / Obj-C / swift platfrom pour Android / iOS, Si vous ne pensez qu'à Linux, cela ne joue peut-être pas beaucoup.
C'est à cette époque que j'ai dû bien connaître chaque méthode afin de pouvoir choisir la méthode qui convient à ma situation. .. ..
Recommended Posts