File mode

Both Flutter and Native can read and write system files. So that’s one way to think about it. Used for the interaction between the Flutter and Native.

// Specify the file name
public static final String FILE_NAME = "FlutterSharedPreferences";
public static final String KEY_NAME = "flutter.proxy";

// Store the file contents
SpUtils.getInstance(FILE_NAME).put(KEY_NAME, result);
Copy the code

This file is available in the Flutter application

void setProxyConfig() {
  String proxyConfig = SpUtils.getString("proxy");
  if (proxyConfig.isNotEmpty) {
    ProxyEntity config = ProxyEntity.fromJson(json.decode(proxyConfig));
    if (config.isOpen) {
      ConstantConfig.localProxy = 'PROXY ${config.proxyUrl}'; }}}Copy the code

Routing mode

Because the engine of a Flutter is running in an Activity or Fragment. This way, before rendering the engine of a Flutter, we can pass the desired parameters into the Router parameters of the Flutter via the intent. In this way, the Flutter can resolve the desired parameters by resolving the Router parameters before rendering.

// Native data acquisition
override fun getInitialRoute(a): String {
        var path = intent.getStringExtra(PATH)
        if (path == null) {
            path = DEFAULT_PAGE
        }
        valparams = dispatchParam(intent.extras? .keySet())var result = if (params["data"] != null) {
            params["data"]!!!!! .wrapParam() }else {
            params.wrapParam()
        }

        return "${path}?$result"
    }
    
Copy the code

These parameters are obtained in the FLUTTER program

/// Flutter analytic data
var baseParam = RouterConfig.getRouterParam(path);
    if(baseParam ! =null) {
      setServerUp(baseParam.serverUrl);
      setProxyConfig();
      setLanguageUp(baseParam.language);
      UserConfig.setUserCode(baseParam.userCode, baseParam.token);
      setOtherUp(baseParam);
 }
Copy the code

Plug-in mode

Before introducing the plugin approach, it is necessary to talk about the Flutter engineering structure

Flutter engineering structure

At presentflutterProvide us with the following project template.

  1. Flutter Aplication
  2. Flutter Plugin
  3. FLutter package
  4. Flutter Module
Flutter Aplication

When you need a pure Flutter development project, you can consider using this template to build your project. If you try to create such a project, you will find that the directory structure of the project is as follows.

Notice that the Android folder and ios file here are not in front of it. This is different from the Flutter Module explained next.

Flutter Module

When you need to put your codeFlutterThe code toAARTry using this method to create your ownFlutterThe project. Let’s try to create oneFlutter ModuleLook at the project.

From the above figure, we can see that the file name of the Flutter Module project is the same as the file name of the Flutter Application project. However, the file name of the Flutter Module is hidden, that is, the file name is preceded by.

When we write the Flutter Module, we often use the Flutter Clean command to delete the.android and.ios. This means that any Native code you write in the Flutter Module will be deleted. The specific logic that Flutter Clean performs is as follows.

 @override
  Future<FlutterCommandResult> runCommand() async {
    // Clean Xcode to remove intermediate DerivedData artifacts.
    // Do this before removing ephemeral directory, which would delete the xcworkspace.
    final FlutterProject flutterProject = FlutterProject.current();
    if (globals.xcode.isInstalledAndMeetsVersionCheck) {
      await _cleanXcode(flutterProject.ios);
      await _cleanXcode(flutterProject.macos);
    }

    final Directory buildDir = globals.fs.directory(getBuildDirectory());
    deleteFile(buildDir);
    ///Deleted the dart_tool
    deleteFile(flutterProject.dartTool);
		///Delete. Android
    deleteFile(flutterProject.android.ephemeralDirectory);
    deleteFile(flutterProject.ios.ephemeralDirectory);
    deleteFile(flutterProject.ios.generatedXcodePropertiesFile);
    deleteFile(flutterProject.ios.generatedEnvironmentVariableExportScript);
    deleteFile(flutterProject.ios.compiledDartFramework);

    deleteFile(flutterProject.linux.ephemeralDirectory);
    deleteFile(flutterProject.macos.ephemeralDirectory);
    deleteFile(flutterProject.windows.ephemeralDirectory);
    deleteFile(flutterProject.flutterPluginsDependenciesFile);
    deleteFile(flutterProject.flutterPluginsFile);

    return const FlutterCommandResult(ExitStatus.success);
  }
Copy the code

In real development, we do have some mutual requirements with Flutter implemented using Native code. And our code doesn’t want to be deleted. This should be implemented using the Flutter Plugin.

Flutter Plugin

Create a project with a Flutter Plugin and view the structure of the project.

The project structure of the Flutter Plugin is similar to that of the Flutter Application, which means that you can store your code in a Native folder without it being deleted by Pub Clean. Of course there are some differences between Flutter Application and Flutter Application

  1. One of them is too manyexampleIs used to write use-case code, which is easy to run separately
  2. pubspec.yamlThere is a plug-in class that declares the current project. This plugin will be called when the engine is started natively
  3. The project will end up withAARIs imported into the project, whileFlutter ApplicationisAPP
Flutter Package

This is the project to build a pure DART.

Create and use plug-ins

  1. useIDEACreate a plug-in for the default template.
  2. Write logic code for plug-ins. (You can use nativeapiDo what you need to do)
  3. Import into the calling project that requires the plug-in and make the call with the following code.
  4. That’s itflutterComplete communication with native code.
  class FlutterSimplePlugin {
  static const MethodChannel _channel =
      const MethodChannel('flutter_simple_plugin');

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

  1. When we useymalThis is available when the file is imported into the plug-indartAbility.
  2. When we usepub runThe plugin code is registered with the native.
  3. When we startFlutterEngineThe written plug-in is initialized and waitsdartThe call.
The registration process for plug-ins

Let’s take a look at the plugin registration process. This helps us to understand the debugging of the code and the overall plug-in execution flow. When we create a Flutter Plugin project, an Android folder is retained by default and will not be deleted when we perform pub Clean. So, when our plug-in is used by other projects, will be integrated into a GeneratedPluginRegistrant class. This class is called by FlutterEngine and mounted throughout the Flutter life cycle.

  1. Flutter/Packages /flutter_tools/plugins.dart contains the flutter project resolution process. Let’s look at the corresponding code logic:

    /// Traversal plugin information, type= Android
    List<Map<String.dynamic>> _extractPlatformMaps(List<Plugin> plugins, String type) {
      final List<Map<String.dynamic>> pluginConfigs = <Map<String.dynamic> > [];for (final Plugin p in plugins) {
        final PluginPlatform platformPlugin = p.platforms[type];
        if(platformPlugin ! =null) { pluginConfigs.add(platformPlugin.toMap()); }}return pluginConfigs;
    }
    Copy the code
  2. Then after convenient plug-in information registered in the GeneratedPluginRegistrant

    const String _androidPluginRegistryTemplateNewEmbedding = ''' package io.flutter.plugins; import androidx.annotation.Keep; import androidx.annotation.NonNull; import io.flutter.embedding.engine.FlutterEngine; {{#needsShim}} import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; {{/needsShim}} /** * Generated file. Do not edit. * This file is generated by the Flutter tool based on the * plugins that support the Android platform. */ @Keep public final class GeneratedPluginRegistrant { public static void registerWith(@NonNull FlutterEngine flutterEngine) { {{#needsShim}} ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine); {{/needsShim}} {{#plugins}} {{#supportsEmbeddingV2}} flutterEngine.getPlugins().add(new {{package}}.{{class}}()); {{/supportsEmbeddingV2}} {{^supportsEmbeddingV2}} {{#supportsEmbeddingV1}} {{package}}.{{class}}.registerWith(shimPluginRegistry.registrarFor("{{package}}.{{class}}")); {{/supportsEmbeddingV1}} {{/supportsEmbeddingV2}} {{/plugins}} } } ''';
    Copy the code
    1. When we start using FlutterEngine, we register these plug-ins with FlutterEngine

        private void registerPlugins(a) {
          try{ Class<? > generatedPluginRegistrant = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
            Method registrationMethod =
                generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
            registrationMethod.invoke(null.this);
          } catch (Exception e) {
            Log.w(
                TAG,
                "Tried to automatically register plugins with FlutterEngine ("
                    + this
                    + ") but could not find and invoke the GeneratedPluginRegistrant."); }}Copy the code

After completing the analysis of the plug-in process, we can consider whether there are some problems with the system’s own plug-ins.

Problems with the native plugin
  1. MethodChannelBelong to hard-coded into the project,ioswithandroidThe uniformity is poor
  2. _channel.invokeMethodThere is no mandatory type for the return value of.
  3. Not conducive to subsequent iterations

Pigeon way

Create and use Pigeon

  1. In the projectpubspec.yamlIn-file importpigeonRely on.
  2. Then you need a testDartandFlutterWhat interfaces and data are required. Native callsFlutterThe code needs to useFlutterApiAnnotations, andFlutterCall nativeApiYou need toHostApiAnnotation.
import 'package:pigeon/pigeon.dart';

/// Parameters passed to native
class ToastContent {
  String? content;
  bool? center;
}

/// Flutter calls the native methods
@HostApi(a)abstract class ToastApi {
  /// Interface protocols
  void showToast(ToastContent content);
}
Copy the code
  1. Once we have defined the data structures needed for both ends, we can use thempigeonIt’s time to generate code automatically.
flutter pub run pigeon 
 #So once you've defined the protocol, Pigeon will parse this class, generate it in a certain format
 --input test/pigeon/toast_api.dart 
 #The generated DART file
 --dart_out lib/toast.dart 
 #Generated object-c file
 --objc_header_out ios/Classes/toast.h 
 --objc_source_out ios/Classes/toast.m 
 #Generated Java files
 --java_out android/src/main/kotlin/com//flutter/basic/flutter_pigeon_plugin/ToastUtils.java 
 #Generated Java registration
 --java_package "com.flutter.basic.flutter_pigeon_plugin"
Copy the code
  1. After executing the above command, the corresponding protocol code will be created in the corresponding folder and we need to inject our implementation into the corresponding code
 /** Sets up an instance of `ToastApi` to handle messages through the `binaryMessenger`. */
    static void setup(BinaryMessenger binaryMessenger, ToastApi api) {
      {
        BasicMessageChannel<Object> channel =
            new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.ToastApi.showToast".new StandardMessageCodec());
        if(api ! =null) {
          channel.setMessageHandler((message, reply) -> {
            Map<String, Object> wrapped = new HashMap<>();
            try {
              @SuppressWarnings("ConstantConditions")
              ToastContent input = ToastContent.fromMap((Map<String, Object>)message);
              api.showToast(input);
              wrapped.put("result".null);
            }
            catch (Error | RuntimeException exception) {
              wrapped.put("error", wrapError(exception));
            }
            reply.reply(wrapped);
          });
        } else {
          channel.setMessageHandler(null); }}}}Copy the code
  1. pigeonDoes not inject itself automaticallyGeneratedPluginRegistrantIn, this means that you need to manuallypigeonGenerated code injected intoFlutterEngineIn the. (When destroying, remember to de-register).
   ToastUtils.ToastApi.setup(flutterPluginBinding.binaryMessenger){
      Toast.makeText(flutterPluginBinding.getApplicationContext(),it.content,Toast.LENGTH_SHORT).show();
   }
Copy the code
  1. And eventually we’ll be able todartIn the callNativeThe code of
ToastApi().showToast(ToastContent().. content="I'm test data.");
Copy the code

Pigeon’s principle and code parser

  1. So one of the things that pigeon does is it generates code based on a protocol. Thus programmatically constrain the corresponding interface.

  2. The /bin/7950. dart main method of a flutter pub run pigeon library will parse a flutter pub run pigeon.

////Bin /pigeon. Dart command entry
Future<void> main(List<String> args) async {
  exit(await runCommandLine(args));
}

/// pigeon/lib/pigeon_lib. Dart files
static PigeonOptions parseArgs(List<String> args) {
    // Note: This function shouldn't perform any logic, just translate the args
    // to PigeonOptions. Synthesized values inside of the PigeonOption should
    // get set in the `run` function to accomodate users that are using the
    // `configurePigeon` function.
    final ArgResults results = _argParser.parse(args);

    final PigeonOptions opts = PigeonOptions();
    opts.input = results['input'];
    opts.dartOut = results['dart_out'];
    opts.dartTestOut = results['dart_test_out'];
    opts.objcHeaderOut = results['objc_header_out'];
    opts.objcSourceOut = results['objc_source_out'];
    opts.objcOptions = ObjcOptions(
      prefix: results['objc_prefix']); opts.javaOut = results['java_out'];
    opts.javaOptions = JavaOptions(
      package: results['java_package']); opts.dartOptions = DartOptions().. isNullSafe = results['dart_null_safety'];
    return opts;
  }

Copy the code
  1. Eventually, the corresponding code is generated based on the corresponding format.

void _writeHostApi(Indent indent, Api api) {
  assert(api.location == ApiLocation.host);

  indent.writeln(
      '/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/');
  indent.write('public interface ${api.name} ');
  indent.scoped('{'.'} ', () {
    for (final Method method in api.methods) {
      final String returnType =
          method.isAsynchronous ? 'void' : method.returnType;
      final List<String> argSignature = <String> [];if(method.argType ! ='void') {
        argSignature.add('${method.argType} arg');
      }
      if (method.isAsynchronous) {
        final String returnType =
            method.returnType == 'void' ? 'Void' : method.returnType;
        argSignature.add('Result<$returnType> result');
      }
      indent.writeln('$returnType ${method.name}(${argSignature.join(', ')}); ');
    }
    indent.addln(' ');
    indent.writeln(
        '/** Sets up an instance of `${api.name}` to handle messages through the `binaryMessenger`. */');
    indent.write(
        'static void setup(BinaryMessenger binaryMessenger, ${api.name} api) ');
    indent.scoped('{'.'} ', () {
      for (final Method method in api.methods) {
        final String channelName = makeChannelName(api, method);
        indent.write(' ');
        indent.scoped('{'.'} ', () {
          indent.writeln('BasicMessageChannel<Object> channel =');
          indent.inc();
          indent.inc();
          indent.writeln(
              'new BasicMessageChannel<>(binaryMessenger, "$channelName", new StandardMessageCodec()); ');
          indent.dec();
          indent.dec();
          indent.write('if (api ! = null) ');
          indent.scoped('{'.'} else {', () {
            indent.write('channel.setMessageHandler((message, reply) -> ');
            indent.scoped('{'.'}); ', () {
              final String argType = method.argType;
              final String returnType = method.returnType;
              indent.writeln('Map
      
        wrapped = new HashMap<>(); '
      ,>);
              indent.write('try ');
              indent.scoped('{'.'} ', () {
                final List<String> methodArgument = <String> [];if(argType ! ='void') {
                  indent.writeln('@SuppressWarnings("ConstantConditions")');
                  indent.writeln(
                      '$argType input = $argType.fromMap((Map<String, Object>)message); ');
                  methodArgument.add('input');
                }
                if (method.isAsynchronous) {
                  final String resultValue =
                      method.returnType == 'void' ? 'null' : 'result.toMap()';
                  methodArgument.add(
                    'result -> { '
                    'wrapped.put("${Keys.result}", $resultValue); '
                    'reply.reply(wrapped); '
                    '} ',); }final String call =
                    'api.${method.name}(${methodArgument.join(', ')}) ';
                if (method.isAsynchronous) {
                  indent.writeln('$call; ');
                } else if (method.returnType == 'void') {
                  indent.writeln('$call; ');
                  indent.writeln('wrapped.put("${Keys.result}", null); ');
                } else {
                  indent.writeln('$returnType output = $call; ');
                  indent.writeln(
                      'wrapped.put("${Keys.result}", output.toMap()); '); }}); indent.write('catch (Error | RuntimeException exception) ');
              indent.scoped('{'.'} ', () {
                indent.writeln(
                    'wrapped.put("${Keys.error}", wrapError(exception)); ');
                if (method.isAsynchronous) {
                  indent.writeln('reply.reply(wrapped); '); }});if(! method.isAsynchronous) { indent.writeln('reply.reply(wrapped); '); }}); }); indent.scoped(null.'} ', () {
            indent.writeln('channel.setMessageHandler(null); '); }); }); }}); }); }Copy the code