一、背景

微信小程序倒退的越来越快,目前小程序甚至取代了大部分 App 的生态位,公司的坑位不增反降,只能让原生利用开发兼顾或换岗进行小程序的开发。

以我的理论状况来讲,公司利用采纳的 Flutter 框架,同样的性能不可避免的就会存在 Flutter 利用开发和微信小程序开发兼顾的状况,这种反复造轮子的工作十分低效。

为什么会呈现这种状况呢?随着 2019 年 5 月 Google I/O 上 Flutter 1.5.4 的公布,宣示着 Flutter 真正开始进入全终端时代,意味着只须要写一份代码,不须要任何额定的修改改,就能够运行在 iOS、Android、Web、PC 上。Flutter 正在革命性的扭转挪动开发的生态系统,从面向各个终端的开发,转向面向框架开发,不仅会扭转开发者的开发方式,也有越来越多的公司开始关注应用 Flutter。

Flutter 作为一个跨平台的框架,其开发技术栈交融了 Native 和前端的技术,不仅波及到了 Native(Android、iOS )的开发常识,又汲取了很多前端(例如 React)的技术理念和框架,并且在此基础上又有晋升,造成 Flutter 本人独特的技术思维。

但目前来讲,Flutter 并不反对小程序,Flutter for Web 尽管最初也会生成 JS 代码,然而 Flutter 生成的 JS 和 CSS 都是不能批改的。而在 Flutter 中也没方法通过 Dart 间接调用小程序的接口,所以现阶段用 Flutter 开发小程序不是太好的抉择。

二、一些解决思路

事实上,随着小程序这种轻量级终端的呈现,公司和业务也不得不向着互联网巨头的流量抬头,同时小程序的逐步风靡,也使得用户下载 App 的习惯产生了变动。不论购物、订餐还是办事,人们都会首先查找“关上即用,即用即走”的小程序,省去了下载 App 的繁琐流程。 

当然也晓得很多开发者对于小程序是有十分多意见的,App 也不会说死就死,毕竟 App 绝对于小程序来讲,还是有很多劣势。所以 App 和小程序开发都共存的状况下,如何解决效率问题?是否让过往开发的小程序间接运行在 Flutter 开发的利用中呢?同样一个性能业务仅需一次小程序开发,即可实现在除了微信端的其它 App 中也运行起来。

在 Google 找相干的解决方案和材料的时候,发现国外简直没有这种计划,国内倒是有厂商在做这块,想想也的确合乎情理。基于公司 Flutter 框架的根底现实情况下,名为 FinClip 小程序容器技术的产品是可能反对除原生 iOS、Android 之外的 Flutter 和 React Native ,于是大略测试了下这个产品。

三、FinClip实操

原理其实挺简略的,FinClip 提供了小程序 SDK 给 Flutter 利用进行集成,这样以来 App 即领有了一套可运行小程序业务代码的宿主环境,原理示意图如下。

3.1 获取凭据

集成 SDK 须要在FinClip 平台中创立利用并绑定小程序,取得每个利用专属的 SDK KEY 及 SDK SECRET ,随后能够在集成 SDK 时填写对应的参数。关上小程序时 SDK 会主动初始化,并校验 SDK KEY、SDK SECRET 与 BundleID (Application ID) 是否正确。

3.2 集成插件

首先,关上Flutter我的项目,而后在我的项目的pubspec.yaml 文件中增加依赖。

mop: latest.version

依赖阐明参考:https://pub-web.flutter-io.cn/packages/mop

如果电脑是 mac M1 芯片,还须要在 iOS 文件夹的 Podfile 文件减少以下 3 行代码:

config.build settings ['ENABLE BITCODE'] = 'NO'config.build settings ['IPHONEOS DEPLOYMENT TARGET'] = 19.0'config.build settings ['EXCLUDED ARCHS (sdk=iphonesimulator*]'] = 'arm64 1386'

依赖示例如下:

3.3 Flutter API

在集成mop插件后,应用 SDK 提供的 API 之前必须要初始化 SDK 。上面我列举官网的一些必要的 API ,更具体的也能够查阅官网文档。

1,初始化sdk接口

在应用sdk提供的api之前必须要初始化sdk,初始化sdk的接口为:

  ///  ///  /// initialize mop miniprogram engine.  /// 初始化小程序  /// [sdkkey] is required. it can be getted from api.finclip.com  /// [secret] is required. it can be getted from api.finclip.com  /// [apiServer] is optional. the mop server address. default is https://mp.finogeek.com  /// [apiPrefix] is optional. the mop server prefix. default is /api/v1/mop  /// [cryptType] is optional. cryptType, should be MD5/SM  /// [disablePermission] is optional.  /// [encryptServerData] 是否对服务器数据进行加密,须要服务器反对  /// [userId] 用户id  /// [finStoreConfigs] 多服务配置  /// [uiConfig] UI配置  /// [debug] 设置debug模式,影响调试和日志  /// [customWebViewUserAgent] 设置自定义webview ua  /// [appletIntervalUpdateLimit] 设置小程序批量更新周期  /// [maxRunningApplet] 设置最大同时运行小程序个数  ///  Future<Map> initialize(    String sdkkey,    String secret, {    String? apiServer,    String? apiPrefix,    String? cryptType,    bool encryptServerData = false,    bool disablePermission = false,    String? userId,    bool debug = false,    bool bindAppletWithMainProcess = false,    List<FinStoreConfig>? finStoreConfigs,    UIConfig? uiConfig,    String? customWebViewUserAgent,    int? appletIntervalUpdateLimit,    int? maxRunningApplet,  }) 

应用示例:

final res = await Mop.instance.initialize(    '22LyZEib0gLTQdU3MUauARlLry7JL/2fRpscC9kpGZQA', '1c11d7252c53e0b6',    apiServer: 'https://api.finclip.com', apiPrefix: '/api/v1/mop');

 

2,关上小程序

  /// open the miniprogram [appId] from the  mop server.  /// 关上小程序  /// [appId] is required.  /// [path] is miniprogram open path. example /pages/index/index  /// [query] is miniprogram query parameters. example key1=value1&key2=value2  /// [sequence] is miniprogram sequence. example 0,1.2.3,4,5...  /// [apiServer] is optional. the mop server address. default is https://mp.finogeek.com  /// [apiPrefix] is optional. the mop server prefix. default is /api/v1/mop  /// [fingerprint] is optional. the mop sdk fingerprint. is nullable  /// [cryptType] is optional. cryptType, should be MD5/SM  Future<Map> openApplet(    final String appId, {    final String? path,    final String? query,    final int? sequence,    final String? apiServer,    final String? scene,  }) 

3,获取小程序信息

以后小程序信息,包含的字段有appId、name、icon、description、version、thumbnail等。

  ///  ///  get current using applet  ///  获取以后正在应用的小程序信息  ///  {appId,name,icon,description,version,thumbnail}  ///  ///  Future<Map<String, dynamic>> currentApplet()

4,敞开小程序

  ///  /// close all running applets  /// 敞开以后关上的所有小程序  ///  Future closeAllApplets()

5,清理小程序

革除缓存的小程序,当再次关上时,会从新下载小程序。

  ///  /// clear applets cache  /// 革除缓存的小程序  ///  Future clearApplets() 

3.4 官网示例

上面是官网提供的一段示例代码。

import 'package:flutter/material.dart';import 'dart:async';import 'dart:io';import 'package:mop/mop.dart';void main() => runApp(MyApp());class MyApp extends StatefulWidget {  @override  _MyAppState createState() => _MyAppState();}class _MyAppState extends State<MyApp> {  @override  void initState() {    super.initState();    init();  }  // Platform messages are asynchronous, so we initialize in an async method.  Future<void> init() async {    if (Platform.isiOS) {      //com.finogeeks.mopExample      final res = await Mop.instance.initialize(          '22LyZEib0gLTQdU3MUauARlLry7JL/2fRpscC9kpGZQA', '1c11d7252c53e0b6',          apiServer: 'https://api.finclip.com', apiPrefix: '/api/v1/mop');      print(res);    } else if (Platform.isAndroid) {      //com.finogeeks.mopexample      final res = await Mop.instance.initialize(          '22LyZEib0gLTQdU3MUauARjmmp6QmYgjGb3uHueys1oA', '98c49f97a031b555',          apiServer: 'https://api.finclip.com', apiPrefix: '/api/v1/mop');      print(res);    }    if (!mounted) return;  }  @override  Widget build(BuildContext context) {    return MaterialApp(      home: Scaffold(        appBar: AppBar(          title: const Text('凡泰极客小程序 Flutter 插件'),        ),        body: Center(          child: Container(            padding: EdgeInsets.only(              top: 20,            ),            child: Column(              children: <Widget>[                Container(                  decoration: BoxDecoration(                    borderRadius: BorderRadius.all(Radius.circular(5)),                    gradient: LinearGradient(                      colors: const [Color(0xFF12767e), Color(0xFF0dabb8)],                      stops: const [0.0, 1.0],                      begin: Alignment.topCenter,                      end: Alignment.bottomCenter,                    ),                  ),                  child: FlatButton(                    onPressed: () {                      Mop.instance.openApplet('5e3c147a188211000141e9b1');                    },                    child: Text(                      '关上示例小程序',                      style: TextStyle(color: Colors.white),                    ),                  ),                ),                SizedBox(height: 30),                Container(                  decoration: BoxDecoration(                    borderRadius: BorderRadius.all(Radius.circular(5)),                    gradient: LinearGradient(                      colors: const [Color(0xFF12767e), Color(0xFF0dabb8)],                      stops: const [0.0, 1.0],                      begin: Alignment.topCenter,                      end: Alignment.bottomCenter,                    ),                  ),                  child: FlatButton(                    onPressed: () {                      Mop.instance.openApplet('5e4d123647edd60001055df1',sequence: 1);                    },                    child: Text(                      '关上官网小程序',                      style: TextStyle(color: Colors.white),                    ),                  ),                ),              ],            ),          ),        ),      ),    );  }}

而后,我装置到真机上测试了一下,体验还不错。 

参考文档:https://pub-web.flutter-io.cn/packages/mop