preface

The development of Flutter application involves platform-related interface calls, such as database operation, camera call, and external browser jump. Flutter itself does not support implementing these functions directly on the platform, but in actual development we will find that pub. dev will provide packages that need to support these functions. These packages are actually add-ons developed for Flutter. They call the platform API through the add-ons interface to implement the original platform specific functions.

For example

Open source libraries such as SQLite, URL_launchr, shared_preference, etc. published on Pub are actually platform functionality plug-ins. Internally, they implement functional code on Android and iOS respectively, and call the current corresponding platform interface through MethodChannel.

SharePreference

For example, the shared_preference plugin provides local persistent storage for Android and iOS. If you’re an Android developer, you’re probably familiar with sharePreference, which stores content in key-value pairs and supports basic data types such as Boolean, Int, String, and Double. When a project references the sharePreference plugin it can be found in the project Flutter Plugins and you can see the sp functionality implemented in both native platforms.

Let’s start with the Dart code of the SP plug-in to learn how to implement native code calls.

Create channels

Sp plug-in share_preferences Dart code file. The first line of code in the Dart is to declare a MethodChannel object, loading paths for plugins. The flutter. IO/shared_preferences, The path is the name of the class calling the native interface.

const MethodChannel _kChannel =
    MethodChannel('plugins.flutter.io/shared_preferences');
Copy the code

The native implementation class can be found in the plug-in code.

  • iOS

SharedPreferencesPlugin. M files in the statement

static NSString *const CHANNEL_NAME = @"plugins.flutter.io/shared_preferences";
Copy the code
  • Android

In Android this is the package name path, and the package name path is reversed.

package io.flutter.plugins.sharedpreferences;
public class SharedPreferencesPlugin implements MethodCallHandler {.. }Copy the code

Initialize a key-value pair

SharedPreferences are created in singleton mode. The initialization process retrieves all local persistent data using the _getSharedPreferencesMap method.

static const String _prefix = 'flutter.';
  static SharedPreferences _instance;
  static Future<SharedPreferences> getInstance() async {
    if (_instance == null) {
      final Map<String.Object> preferencesMap =
          await _getSharedPreferencesMap();
      _instance = SharedPreferences._(preferencesMap);
    }
    return _instance;
  }
Copy the code

Call the native interface through a channel

_kchannel. invokeMapMethod<String, Object>(‘getAll’); Call the “getAll” method in the native via the native channel.

static Future<Map<String.Object>> _getSharedPreferencesMap() async {
    // Critical code calls the native "getAll" method
    final Map<String.Object> fromSystem =
        await _kChannel.invokeMapMethod<String.Object> ('getAll');
    assert(fromSystem ! =null);
    // Strip the flutter. prefix from the returned preferences.
    final Map<String.Object> preferencesMap = <String.Object> {};for (String key in fromSystem.keys) {
      assert(key.startsWith(_prefix));
      preferencesMap[key.substring(_prefix.length)] = fromSystem[key];
    }
    return preferencesMap;
  }
Copy the code

Android native method calls

The native calling class needs to implement the Flutter’s MethodCallHandler interface, and MethodCallHandler has only one interface method that returns MethodCall and methodChannel. Result.

The MethodCall class contains two arguments, method and argument: method is the name of the called method; Argument is the Dart argument Object. Methodchannel. Result is the callback interface used to return the Result of native Flutter interactions. It provides three callback interface calls: success, error, and notImplemented. An Object can be returned to a Flutter from a successful or failed interface.

public class SharedPreferencesPlugin implements MethodCallHandler {... }public interface MethodCallHandler {
    @UiThread
    void onMethodCall(@NonNull MethodCall var1, @NonNull MethodChannel.Result var2);
}
Copy the code

IOS OC also need to implement interface: @ interface FLTSharedPreferencesPlugin: NSObject

Return to the SharedPreferencePlugin to implement the onMethodCall interface. Get the required method from the method of the MethodCall, and then get the argument from the argument. Native Preferences are then called to store the Flutter operation, and the Result callback interface notifies the Flutter operation based on the call. The same is true for other operations, as long as call.method is defined, the desired result can be achieved.

 @Override
  public void onMethodCall(MethodCall call, MethodChannel.Result result) {
    String key = call.argument("key");
    try {
      switch (call.method) {
        case "setBool":
          commitAsync(preferences.edit().putBoolean(key, (boolean) call.argument("value")), result);
          break;
        case "setDouble":...case "commit":
        // We've been committing the whole time.
         result.success(true); .default:
          result.notImplemented();
          break;
}
Copy the code

Through the channel

The MethodChannel class is implemented in platorm_channel.dart in the Flutter Service package file.

class MethodChannel {
  /// None of [name], [binaryMessenger], or [codec] may be null.
  const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), this.binaryMessenger = defaultBinaryMessenger ])
    : assert(name ! =null),
      assert(binaryMessenger ! =null),
      assert(codec ! =null); . }Copy the code

The same MethodChannel class file can also be found in JavaSDK supported by Flutter, with the same member variables and methods. Can be said to get through the platform key class. MethodChannel is a final class in JavaSDK. The MethodChannel of a Flutter has three member variables: BinaryMessenger, BinaryMessenger, and MethodCodec. BinaryMessenger and MethodCodec are two interface members.

public final class MethodChannel {
    private static final String TAG = "MethodChannel#";
    private final BinaryMessenger messenger;
    private final String name;
    private final MethodCodec codec;
    public MethodChannel(BinaryMessenger messenger, String name) {
        this(messenger, name, StandardMethodCodec.INSTANCE);
    }
    // Method calls are sent through the BinaryMessenger messenger member
    @UiThread
    public void invokeMethod(String method, @Nullable Object arguments, MethodChannel.Result callback) {
        this.messenger.send(this.name, this.codec.encodeMethodCall(new MethodCall(method, arguments)), callback == null ? null : newMethodChannel.IncomingResultHandler(callback)); }... }Copy the code

The fact that Dart’s MethodChannel and native class’s MethodChannel have the same members and methods also confirms that native and Flutter call each other.

Here for example webview_flutter plug-in source code, in FlutterWebViewClient. There is a method in Java by methodChannel. InvokeMethod calls onPageFinished method.

private void onPageFinished(WebView view, String url) {
    Map<String, Object> args = new HashMap<>();
    args.put("url", url);
    methodChannel.invokeMethod("onPageFinished", args);
  }
Copy the code

Dart does see the methodCall interface implementation in webView_method_channel.dart that accepts the onPageFinished method. This means that in addition to native methods that can be called in a Flutter, native methods can also be called in a Flutter. In other words, bidirectional access is realized, which also provides the possibility of mixed development.

Future<bool> _onMethodCall(MethodCall call) async {
    switch (call.method) {
        ......
      case 'onPageFinished':
        _platformCallbacksHandler.onPageFinished(call.arguments['url']);
        return null;
    }
    throw MissingPluginException(
        '${call.method} was invoked but has no handler');
  }
Copy the code

The plug-in registry

Implement the plugin registration in the Android Java code and instantiate the pluginregistry.Registrar BinaryMessenger as the communication channel to register the plugin name. The setMethodCallHandler then sends the plug-in object through Messenger.

  / / SharedPreferencesPlugin MethodChannel registration method
  public static void registerWith(PluginRegistry.Registrar registrar) {
   // Instantiate the method channel and set the channel and plug-in name
    MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
    SharedPreferencesPlugin instance = new SharedPreferencesPlugin(registrar.context());
    // Send the plugin via Registrarchannel.setMethodCallHandler(instance); }.../ / global plugin GeneratedPluginRegistrant entrance, all plug-ins are registered in the register and only once
  public final class GeneratedPluginRegistrant {
  public static void registerWith(PluginRegistry registry) {
    if (alreadyRegisteredWith(registry)) {
      return; }... SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")); }...// The MainActivity of Flutter registers the global plugin
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
  }
Copy the code

The source of the PluginRegistry object needs to be traced to the FlutterActivityDelegate in the FlutterActivity, followed by the FlutterView member within the Delegate. FlutterView implements the BinaryMessenger interface via DartExecutor, where DartMessenger is really responsible for sending piped-communication messages. Further code is handed over to the JNI Native layer for processing without further elaboration. For more details, see Gityuan’s in-depth understanding of Flutter engine startup and Xianyu’s understanding of how Platform Channel works

One more point: In the FlutterView source, you can see that the View instantiates the necessary platform channels when it initializes. In this way, it is clear that Flutter’s ability to invoke multi-platform features is the same as it was before. The developers developed custom plug-ins to extend Flutter’s existing platform channel.

this.navigationChannel = new NavigationChannel(this.dartExecutor);
this.keyEventChannel = new KeyEventChannel(this.dartExecutor);
this.lifecycleChannel = new LifecycleChannel(this.dartExecutor);
this.localizationChannel = new LocalizationChannel(this.dartExecutor);
this.platformChannel = new PlatformChannel(this.dartExecutor);
this.systemChannel = new SystemChannel(this.dartExecutor);
this.settingsChannel = new SettingsChannel(this.dartExecutor);
final PlatformPlugin platformPlugin = new PlatformPlugin(activity, this.platformChannel);
Copy the code

PS: If the underlying interface of the platform is different between different versions, it will still have a great impact on the later adaptation. In contrast, the platform SDK generally does not have too much interface change, but it is not a bad thing to worry more.

Implement custom plug-ins

The official website describes how to develop the plug-in package Demo. See the documentation for

If the function of custom plug-in is involved in the subsequent development requirements, you can introduce the development of custom plug-in separately.

reference

  • Medium.com/flutter/exe…
  • Flutter. Dev/docs/develo…
  • Flutter. Dev/docs/develo…
  • Gityuan.com/2019/06/22/…
  • Juejin. Cn/post / 684490…