构建属于本身的Flutter混合开发框架

所谓混合开发,指的是 App 的总体架构以原生技术栈为基础,将 Flutter 运行环境嵌入到原生 App 工程中,而后由原生开发人员为 Flutter 运行提供宿主容器及基础能力支撑,而 Flutter 开发人员则负责应用层业务及 App 内大部分渲染工做。前端

在这种开发模式下,好处十分明显。对于工程师而言,跨平台的 Flutter 框架减小了对底层环境的依赖,使用完整的技术栈和工具链隔离了各个终端系统的差别,不管是 Android、iOS 甚至是前端工程师,均可以使用统一而标准化的能力进行业务开发,从而扩充了技能栈。而对于企业而言,这种方式不只具有了原生 App 良好的用户体验,以及丰富的底层能力,还同时拥有了跨平台技术开发低成本和多端体验一致性的优点,直接节省研发资源。android

那么,在原生工程中引入 Flutter 混合开发能力,咱们应该如何设计工程架构,原生开发与 Flutter 开发的工做模式又是怎样的呢?ios

混合开发架构

与纯 Flutter 工程可以以自治的方式去分拆软件功能、管理工程依赖不一样,Flutter 混合工程的功能分治须要原生工程与 Flutter 工程一块儿配合完成,即:在 Flutter 模块的视角看来,一部分与渲染相关的基础能力彻底由 Flutter 代码实现,而另外一部分涉及操做系统底层、业务通用能力部分,以及总体应用架构支撑,则须要借助于原生工程给予支持。git

咱们能够经过四象限分析法,把纯 Flutter 应用按照业务和 UI 分解成 4 类。一样,混合工程的功能单元也能够按照这个分治逻辑分为 4 个维度,即不具有业务属性的原生基础功能、不具有业务属性的原生 UI 控件、不具有 UI 属性的原生基础业务功能和带 UI 属性的独立业务模块,以下图所示。程序员

在这里插入图片描述
从图中能够看到,对于前 3 个维度(即原生 UI 控件、原生基础功能、原生基础业务功能)的定义,纯 Flutter 工程与混合工程并没有区别,只不过实现的方式由 Flutter 变成了原生;对于第四个维度(即独立业务模块)的功能归属,考虑到业务模块的最小单元是页面,而 Flutter 的最终呈现形式也是独立的页面,所以咱们把 Flutter 模块也归为此类,咱们的工程能够像依赖原生业务模块同样直接依赖它,为用户提供独立的业务功能。当咱们把这些组件及其依赖按照从上到下的方式进行划分,而后再总体看,就是一个完整的混合开发架构了,整个架构下图所示。

在这里插入图片描述

能够看到,原生工程和 Flutter 工程的边界定义清晰,双方均可以保持原有的分层管理依赖的开发模式不变。须要注意的是,做为一个内嵌在原生工程的插件,Flutter 模块的运行环境是由原生工程提供支持的,这也就意味着在渲染交互能力以外的部分基础功能(好比网络、存储),以及和原生业务共享的业务通用能力(好比支付、帐号)须要原生工程配合完成,即原生工程以分层的形式提供上层调用接口,Flutter 模块以插件的形式直接访问原生代码宿主对应功能实现。github

所以,不只不一样归属定义的原生组件以前存在着分层依赖的关系,Flutter 模块与原生组件以前也隐含着分层依赖的关系。好比,Flutter 模块中处于基础业务模块的帐号插件,依赖位于原生基础业务模块中的帐号功能;Flutter 模块中处于基础业务模块的网络插件,依赖位于原生基础功能的网络引擎库。编程

在混合工程架构中,像原生工程依赖 Flutter 模块、Flutter 模块又依赖原生工程这样跨技术栈的依赖管理行为,其实是经过将双方抽象为彼此对应技术栈的依赖,从而实现分层管理的:即将原生对 Flutter 的依赖抽象为依赖 Flutter 模块所封装的原生组件,而 Flutter 对原生的依赖则抽象为依赖插件所封装的原生行为。json

Flutter 混合开发流程

在常规的软件开发流程中,工程师的职责涉及从需求到上线的整个生命周期,包含需求阶段 -> 方案阶段 -> 开发阶段 -> 发布阶段 -> 线上运维阶段,这其实就是一种抽象的工做流程。缓存

其中,和工程化关联最为紧密的是开发阶段和发布阶段。咱们能够将工做流中和工程开发相关的部分抽离定义为开发工做流,根据生命周期中关键节点和高频节点,能够将整个工做流划分为以下七个阶段,即初始化 -> 开发 / 调试 -> 构建 -> 测试 -> 发布 -> 集成 -> 原生工具链。下图演示了Flutter和原生开发的工做流。安全

在这里插入图片描述
其中,前 6 个阶段是 Flutter 的标准工做流,最后一个阶段是原生开发的标准工做流。能够看到,在混合开发工做模式中,Flutter 的开发模式与原生开发模式之间有着清晰的分工边界:Flutter 模块是原生工程的上游,其最终产物是原生工程的依赖对象。从原生工程视角看,其开发模式与普通原生应用并没有区别。

对于 Flutter 标准工做流的 6 个阶段而言,每一个阶段都会涉及业务或产品特性提出的特异性要求,技术方案的选型,各阶段工做成本可用性、可靠性的衡量,以及监控相关基础服务的接入和配置等。每件事儿都是一个固定的步骤,而当开发规模随着文档、代码、需求增长时,咱们会发现重复的步骤愈来愈多。此时,若是咱们把这些步骤像抽象代码同样,抽象出一些相同操做,就能够大大提高开发效率。

优秀的程序员会发掘工做中的问题,从中探索提升生产力的办法,而转变思惟模式就是一个不错的起点。以持续交付的指导思想来看待这些问题,咱们但愿总体方案可以以可重复、可配置化的形式,来保障整个工做流的开发体验、效率、稳定性和可靠性,而这些都离不开 Flutter 对命令行工具支持。

好比,对于测试阶段的 Dart 代码分析,咱们可使用 flutter analyze 命令对代码中可能存在的语法或语义问题进行检查;又好比,在发布期的 package 发布环节,咱们可使用 flutter packages pub publish --dry-run 命令对待发布的包进行发布前检查,确认无误后使用去掉 dry-run 参数的 publish 命令将包提交至 Pub 站点。

这些基本命令对各个开发节点的输入、输出以及执行过程进行了抽象,熟练掌握它们及对应的扩展参数用法,咱们不只能够在本地开发时打造一个易用便捷的工程开发环境,还能够将这些命令部署到云端,实现工程构建及部署的自动化。在Flutter 标准工做流中,经常使用的命令以下所示。

在这里插入图片描述

混合开发的基本设计原则

在混合开发中,咱们须要重点关注的是项目的基本设计原则,即肯定分工边界。下面从工程架构维度和工做模式维度来进行拆分。

在工程架构维度,因为 Flutter 模块做为原生工程的一个业务依赖,其运行环境是由原生工程提供的,所以咱们须要将它们各自抽象为对应技术栈的依赖管理方式,以分层依赖的方式肯定两者的边界。

而在工做模式维度,考虑到 Flutter 模块开发是原生开发的上游,所以咱们只须要从其构建产物的过程入手,抽象出开发过程当中的关键节点和高频节点,以命令行的形式进行统一管理。构建产物是 Flutter 模块的输出,同时也是原生工程的输入,一旦产物完成构建,咱们就能够接入原生开发的工做流了。

在 Flutter 混合框架中,Flutter 模块与原生工程是相互依存、互利双赢的关系。

  • Flutter 跨平台开发效率高,渲染性能和多端体验一致性好,所以在分工上主要专一于实现应用层的独立业务(页面)的渲染闭环;
  • 原生开发稳定性高,精细化控制力强,底层基础能力丰富,所以在分工上主要专一于提供总体应用架构,为 Flutter 模块提供稳定的运行环境及对应的基础能力支持。

那么,在原生工程中为 Flutter 模块提供基础能力支撑的过程当中,面对跨技术栈的依赖管理,咱们该遵循何种原则呢?对于 Flutter 模块及其依赖的原生插件们,咱们又该如何以标准的原生工程依赖形式进行组件封装呢?下面重点看一下原生工程是如何进行插件管理的。

能够看到,在原生 App 工程中引入 Flutter 运行环境,由原生开发主作应用架构和基础能力赋能、Flutter 开发主作应用层业务的混合开发协做方式,可以综合原生 App 与 Flutter 框架双方的特色和优点,不只能够直接节省研发资源,也符合目前行业人才能力模型的发展趋势。

原生插件管理

在Flutter 应用中,Dart 代码提供原生能力支持主要有两种方式,即在原生工程中的 Flutter 应用入口注册原生代码宿主回调的轻量级方案,以及使用插件工程进行独立拆分封装的工程化解耦方案。

不过,不管使用哪一种方式,Flutter 应用工程提供的标准解决方案,都可以在集成构建时自动管理原生代码宿主及其相应的原生依赖,而后只须要在应用层使用 pubspec.yaml 文件去管理 Dart 的依赖便可。

但对于混合工程而言,依赖关系的管理则会复杂一些。这是由于与 Flutter 应用工程有着对原生组件简单清晰的单向依赖关系不一样,混合工程对原生组件的依赖关系是多向的,即Flutter 模块工程会依赖原生组件,而原生工程的组件之间也会互相依赖。

若是继续使用Flutter 的工具链管理原生组件的依赖关系,那么整个工程就会陷入不稳定的状态之中。所以,对于混合工程的原生依赖,Flutter 模块并不须要介入,彻底交由原生工程进行统一管理才是正确的作法。而 Flutter 模块工程对原生工程的依赖,体如今依赖原生代码宿主提供的底层基础能力的原生插件上。

下面咱们就以网络通讯这一基础能力为例,展开说明原生工程与 Flutter 模块工程之间应该如何管理依赖关系。

网络插件依赖管理实践

众所周知,在 Flutter开发中,咱们可使用 HttpClient、http 与 dio 这三种通讯方式来实现与服务端的数据交换。不过,在混合工程中,考虑到原生组件也须要使用网络通讯能力,因此一般是由原生工程来提供网络通讯功能,而后封装后提供给Flutter使用。这样,不只能够在工程架构层面实现更合理的功能分治,还能够统一整个 App 内数据交换的行为。好比,在网络引擎中为接口请求增长通用参数,或者是集中拦截错误等。

在原生网络通讯方面,目前市面上有不少优秀的第三方开源 SDK,好比 iOS 的 AFNetworking 和 Alamofire、Android 的 OkHttp 和 Retrofit 等。考虑到 AFNetworking 和 OkHttp 在各自平台的社区活跃度相对最高,所以下面就以它俩为例演示混合工程的原生插件管理方法。

网络插件封装

要想搞清楚如何管理原生插件,咱们须要先使用方法通道来创建 Dart 层与原生代码宿主之间的联系。

1,Dart代码封装

对于插件工程的 Dart 层代码而言,因为它仅仅是原生工程的代码宿主代理,因此这一层的接口设计比较简单,只须要提供一个能够接收请求 URL 和参数,并返回接口响应数据的方法便可 ,以下所示。

class FlutterPluginNetwork {
  ...
  static Future<String> doRequest(url,params)  async {
    //使用方法通道调用原生接口doRequest,传入URL和param两个参数
    final String result = await _channel.invokeMethod('doRequest', {
      "url": url,
      "param": params,
    });
    return result;
  }
}
复制代码

关于Flutter如何与原生进行交互,能够查看我以前的文章:混合开发简介

完成Dart 层接口封装后,接下来再看一下 Android 和 iOS 代码宿主是如何响应 Dart 层的接口调用的。

2,原生端封装

前面说过,原生代码的基础通讯能力是基于 AFNetworking(iOS)和 OkHttp(Android)作的封装,因此为了在原生代码中使用它们,咱们首先须要分别在 flutter_plugin_network.podspec 和 build.gradle 文件中添加插件的依赖。对于iOS工程来讲,在 flutter_plugin_network.podspec 文件中,声明工程对 AFNetworking 的依赖。

Pod::Spec.new do |s|
  ...
  s.dependency 'AFNetworking'
end
复制代码

对于Android原生工程来讲, 在 build.gradle 文件中添加对 OkHttp 的依赖,以下所示。

dependencies {
    implementation "com.squareup.okhttp3:okhttp:4.2.0"
}
复制代码

而后,咱们须要在原生接口 FlutterPluginNetworkPlugin 类中,完成例行的初始化插件实例、绑定方法通道工做。最后,咱们还须要在方法通道中取出对应的 URL 和 请求 参数,为 doRequest 方法分别提供 AFNetworking 和 OkHttp 的实现版本。

对于 iOS 的调用而言,因为 AFNetworking 的网络调用对象是 AFHTTPSessionManager 类,因此咱们须要对这个类进行实例化,并定义其接口返回的序列化方式(本例中为字符串),而后剩下的工做就是用它去发起网络请求,使用方法通道通知 Dart 层执行结果。

@implementation FlutterPluginNetworkPlugin
...
//方法通道回调
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    //响应doRequest方法调用
    if ([@"doRequest" isEqualToString:call.method]) {
        //取出query参数和URL
        NSDictionary *arguments = call.arguments[@"param"];
        NSString *url = call.arguments[@"url"];
        [self doRequest:url withParams:arguments andResult:result];
    } else {
        //其余方法未实现
        result(FlutterMethodNotImplemented);
    }
}
//处理网络调用
- (void)doRequest:(NSString *)url withParams:(NSDictionary *)params andResult:(FlutterResult)result {
    //初始化网络调用实例
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    //定义数据序列化方式为字符串
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    NSMutableDictionary *newParams = [params mutableCopy];
    //增长自定义参数
    newParams[@"ppp"] = @"yyyy";
    //发起网络调用
    [manager GET:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        //取出响应数据,响应Dart调用
        NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
        result(string);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        //通知Dart调用失败
        result([FlutterError errorWithCode:@"Error" message:error.localizedDescription details:nil]);
    }];
}
@end
复制代码

Android 的调用相似,OkHttp的网络调用对象是 OkHttpClient 类,因此咱们一样须要对这个类进行实例化。OkHttp的默认序列化方式就是字符串,因此咱们什么都不用作,只须要 URL 参数加工成 OkHttp 指望的格式,而后就是用它去发起网络请求,使用方法通道通知 Dart 层执行结果便可。

public class FlutterPluginNetworkPlugin implements MethodCallHandler {
  ...
  @Override
  //方法通道回调
  public void onMethodCall(MethodCall call, Result result) {
    //响应doRequest方法调用
    if (call.method.equals("doRequest")) {
      //取出query参数和URL
      HashMap param = call.argument("param");
      String url = call.argument("url");
      doRequest(url,param,result);
    } else {
      //其余方法未实现
      result.notImplemented();
    }
  }
  //处理网络调用
  void doRequest(String url, HashMap<String, String> param, final Result result) {
    //初始化网络调用实例
    OkHttpClient client = new OkHttpClient();
    //加工URL及query参数
    HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
    for (String key : param.keySet()) {
      String value = param.get(key);
      urlBuilder.addQueryParameter(key,value);
    }
    //加入自定义通用参数
    urlBuilder.addQueryParameter("ppp", "yyyy");
    String requestUrl = urlBuilder.build().toString();

    //发起网络调用
    final Request request = new Request.Builder().url(requestUrl).build();
    client.newCall(request).enqueue(new Callback() {
      @Override
      public void onFailure(Call call, final IOException e) {
        //切换至主线程,通知Dart调用失败
        registrar.activity().runOnUiThread(new Runnable() {
          @Override
          public void run() {
            result.error("Error", e.toString(), null);
          }
        });
      }
      
      @Override
      public void onResponse(Call call, final Response response) throws IOException {
        //取出响应数据
        final String content = response.body().string();
        //切换至主线程,响应Dart调用
        registrar.activity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
              result.success(content);
            }
        });
      }
    });
  }
}
复制代码

须要注意的是,因为方法通道是非线程安全的,因此原生代码与 Flutter 之间全部的接口调用必须发生在主线程。而 OktHtp 在处理网络请求时,因为涉及非主线程切换,因此须要调用 runOnUiThread 方法以确保回调过程是在 UI 线程中执行的,不然应用可能会出现奇怪的 Bug,甚至是 Crash。

有些同窗可能会有疑问,为何 doRequest 的 Android 实现须要手动切回 UI 线程,而 iOS 实现则不须要呢?这实际上是由于 doRequest 的 iOS 实现背后依赖的 AFNetworking,已经在数据回调接口时为咱们主动切换了 UI 线程,因此咱们天然不须要重复再作一次了。

在完成了原生接口封装以后,Flutter 工程所需的网络通讯功能的接口实现,就所有搞定了。

Flutter 模块工程依赖管理

经过上面这些步骤,咱们以插件的形式提供了原生网络功能的封装。接下来,咱们就须要在 Flutter 模块工程中使用这个插件,并提供对应的构建产物封装,提供给原生工程使用了。

  • 第一,如何使用 FlutterPluginNetworkPlugin 插件,也就是模块工程功能如何实现;
  • 第二,模块工程的 iOS 构建产物应该如何封装,也就是原生 iOS 工程如何管理 Flutter 模块工程的依赖;
  • 第三,模块工程的 Android 构建产物应该如何封装,也就是原生 Android 工程如何管理 Flutter 模块工程的依赖。

1,模块工程功能实现

为了使用 FlutterPluginNetworkPlugin 插件实现与服务端的数据交换能力,咱们首先须要在 pubspec.yaml 文件中,将工程对它的依赖显示地声明出来,以下所示。

flutter_plugin_network:
    git:
      url: https://github.com/cyndibaby905/flutter_plugin_network.git
复制代码

而后,咱们还得在 main.dart 文件中为它提供一个触发入口。在下面的示例代码中,咱们在界面上显示一个 RaisedButton 按钮,在其点击回调函数时使用 FlutterPluginNetwork 插件发起了一次网络接口调用,并把网络返回的数据打印到了控制台上,代码以下。

RaisedButton(
  child: Text("doRequest"),
  onPressed:()=>FlutterPluginNetwork.doRequest("https://jsonplaceholder.typicode.com/posts", {'userId':'2'}).then((s)=>print('Result:$s')),
)
复制代码

运行这段代码,点击 doRequest 按钮时会观察控制台输出,证实 Flutter 模块的功能表现是彻底符合预期的。

在这里插入图片描述

构建产物封装

咱们都知道,模块工程的 Android 构建产物是 aar,iOS 构建产物是 Framework。Flutter插件依赖的模块工程构建产物的两种封装方案,即手动封装方案与自动化封装方案。这两种封装方案,最终都会输出一样的组织形式(Android 是 aar,iOS 则是带 podspec 的 Framework 封装组件)。

若是咱们的模块工程存在插件依赖,又该如何进行封装,它的封装过程是否有区别呢?简单的说,对于模块工程自己而言,这个过程没有区别;但对于模块工程的插件依赖来讲,咱们须要主动告诉原生工程,哪些依赖是须要它去管理的。

因为 Flutter 模块工程把全部原生的依赖都交给了原生工程去管理,所以其构建产物并不会携带任何原生插件的封装实现,因此咱们须要遍历模块工程所使用的原生依赖组件们,为它们逐一辈子成插件代码对应的原生组件封装。

在纯Flutter 工程中,管理第三方依赖库使用的是.packages 文件存储,它使用的是依赖包名与系统缓存中的包文件路径。相似的,插件依赖也可使用相似的文件进行统一管理,即.flutter-plugins。咱们能够经过这个文件,找到对应的插件名字(本例中即为 flutter_plugin_network)及缓存路径,以下所示。

flutter_plugin_network=/Users/hangchen/Documents/flutter/.pub-cache/git/flutter_plugin_network-9b4472aa46cf20c318b088573a30bc32c6961777/
复制代码

同时,插件缓存自己也能够被视为一个 Flutter 模块工程,因此咱们能够采用与模块工程相似的办法,为它生成对应的原生组件封装。

iOS 构建产物封装

对于 iOS 而言,这个过程相对简单些,因此咱们先来看看模块工程的 iOS 构建产物封装过程。

首先,在插件工程的 iOS 目录下,模块工程提供了带 podspec 文件的源码组件,podspec 文件提供了组件的声明(及其依赖),所以咱们能够把这个目录下的文件拷贝出来,连同 Flutter 模块组件一块儿放到原生工程中的专用目录,并写到 Podfile 文件中。

#Podfile
target 'iOSDemo' do
  pod 'Flutter', :path => 'Flutter'
  pod 'flutter_plugin_network', :path => 'flutter_plugin_network'
end
复制代码

原生工程会识别出组件自己及其依赖,并按照声明的依赖关系依次遍历,自动安装。而后,咱们就能够像使用不带插件依赖的模块工程同样,把它引入到原生工程中,为其设置入口,并在 FlutterViewController 中展现 Flutter 模块的页面了。

不过须要注意的是,因为 FlutterViewController 并不感知这个过程,所以不会主动初始化项目中的插件,因此咱们还须要在入口处手动将工程里全部的插件依次声明出来,以下所示。

//AppDelegate.m:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    //初始化Flutter入口
    FlutterViewController *vc = [[FlutterViewController alloc]init];
    //初始化插件
    [FlutterPluginNetworkPlugin registerWithRegistrar:[vc registrarForPlugin:@"FlutterPluginNetworkPlugin"]];
    //设置路由标识符
    [vc setInitialRoute:@"defaultRoute"]; 
    self.window.rootViewController = vc;
    [self.window makeKeyAndVisible];
    return YES;
}
复制代码

而后,使用Xcode 运行这段代码,点击 doRequest 按钮,若是能够看到接口返回的数据信息可以被正常打印,证实咱们已经能够在原生 iOS 工程中顺利的使用 Flutter 模块了。

在这里插入图片描述

Android 构建产物封装

与 iOS 的插件工程组件在 ios 目录相似,Android 的插件工程组件在 android 目录下。对于 iOS 的插件工程,咱们能够直接将源码组件提供给原生工程,但对于 Andriod 的插件工程来讲,咱们只能将 aar 组件提供给原生工程,因此咱们不只须要像 iOS 操做步骤那样进入插件的组件目录,还须要借助构建命令,为插件工程生成 aar。使用下面的命令便可生成插件工程的aar包。

cd android
./gradlew flutter_plugin_network:assRel
复制代码

命令执行完成以后,aar 就生成好了,aar 包位于 android/build/outputs/aar 目录下,咱们打开插件缓存对应的路径,提取出对应的 aar便可。咱们把生成的插件 aar,连同 Flutter 模块 的aar 一块儿放到原生工程的 libs 目录下,最后在 build.gradle 文件里引入插件工程,以下所示。

//build.gradle
dependencies {
    ...
    implementation(name: 'flutter-debug', ext: 'aar')
    implementation(name: 'flutter_plugin_network-debug', ext: 'aar')
    implementation "com.squareup.okhttp3:okhttp:4.2.0"
    ...
}
复制代码

而后,咱们就能够在原生工程中为其设置入口,在 FlutterView 中展现 Flutter 页面,接下来就可使用 Flutter 模块带来的高效开发和高性能渲染能力了。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View FlutterView = Flutter.createView(this, getLifecycle(), "defaultRoute"); 
        setContentView(FlutterView);
    }
}
复制代码

须要注意的是,与 iOS 插件工程的 podspec 可以携带组件依赖不一样,Android 插件工程的封装产物 aar 自己不携带任何配置信息。因此,若是插件工程自己存在原生依赖(如 flutter_plugin_network 依赖 OkHttp ),咱们是没法经过 aar 去告诉原生工程其所需的原生依赖的。对于这种状况,咱们只须要在原生工程中的 build.gradle 文件里手动地将插件工程的依赖的插件(即 OkHttp)显示地声明出来便可,以下所示。

//build.gradle
dependencies {
    ...
    implementation(name: 'flutter-debug', ext: 'aar')
    implementation(name: 'flutter_plugin_network-debug', ext: 'aar')
    implementation "com.squareup.okhttp3:okhttp:4.2.0"
    ...
}
复制代码

至此,混合模块工程及其插件依赖封装成原生组件的所有工做就完成了,接下来原生工程能够像使用一个普通的原生组件同样去使用 Flutter 模块组件的功能了。在 Android Studio 中运行这段代码,并点击 doRequest 按钮,能够看到,咱们能够在原生 Android 工程中正常使用 Flutter 封装的页面组件了。

在这里插入图片描述
固然,考虑到手动封装模块工程及其构建产物的过程,繁琐且容易出错,咱们能够把这些步骤抽象成命令行脚本,并把它部署到 Travis 上。这样在 Travis 检测到代码变动以后,就会自动将 Flutter 模块的构建产物封装成原生工程指望的组件格式了。

总结

众所周知,Flutter 模块工程的原生组件封装形式是 aar(Android)和 Framework(Pod)。与纯 Flutter 应用工程可以自动管理插件的原生依赖不一样,混合工程的这部分工做在模块工程中是彻底交给原生工程去管理的。所以,咱们须要查找记录了插件名称及缓存路径映射关系的.flutter-plugins 文件,提取出每一个插件所对应的原生组件封装,集成到原生工程中。

相比iOS插件管理来讲,Android的插件管理比较繁琐。对于有着插件依赖的 Android 组件封装来讲,因为 aar 自己并不携带任何配置信息,所以其操做以手工为主:咱们不只要执行构建命令依次生成插件对应的 aar,还须要将插件自身的原生依赖拷贝至原生工程。

为了解决这一问题,业界出现了一种名为fat-aar的打包手段,它可以将模块工程自己,及其相关的插件依赖统一打包成一个大的 aar,从而省去了依赖遍历和依赖声明的过程,实现了更好的功能自治性。但这种解决方案存在一些较为明显的不足,如下是使用中存在的一些问题:

  • 依赖冲突问题:若是原生工程与插件工程都引用了一样的原生依赖组件(OkHttp),则原生工程的组件引用其依赖时会产生合并冲突,所以在发布时必须手动去掉原生工程的组件依赖。
  • 嵌套依赖问题:fat-aar 只会处理 embedded 关键字指向的这层一级依赖,而不会处理再下一层的依赖。所以,对于依赖关系复杂的插件支持,咱们仍须要手动处理依赖问题。
  • Gradle 版本限制问题:fat-aar 方案对 Gradle 插件版本有限制,且实现方式并非官方设计考虑的点,加之 Gradle API 变动较快,因此存在后续难以维护的问题。
  • 不更新。fat-aar 项目已经再也不维护了,最近一次更新仍是 2 年前,对Android的新版本存在较大的风险。

所以,fat-aar 并非管理插件工程依赖的好的解决方案,因此最好仍是得老老实实地去遍历插件依赖,以持续交付的方式自动化生成 aar。

参考资料

1,Flutter 应用程序调试
2,Flutter For Web入门实战
3,Flutter开发之路由与导航
4,Flutter 必备开源项目
5,Flutter混合开发
6,Flutter的Hot Reload是如何作到的
7,《Flutter in action》开源
8,Flutter开发之JSON解析
9,Flutter开发之基础Widgets
10,Flutter开发之导航与路由管理
11,Flutter开发之网络请求
12,Flutter基础知识
13,Flutter开发之Dart语言基础
14,Flutter入门与环境搭建
15,移动跨平台方案对比:WEEX、React Native、Flutter和PWA
16,Flutter开发之异步编程

相关文章
相关标签/搜索