Flutter核心技术与实战

来自Google的高性能跨平台开发框架


44 | 如何构建自己的Flutter混合开发框架(二)?

<p>陈航 2019-10-08</p> <p><img src="https://static001.geekbang.org/resource/image/c3/12/c3a6afc2f433fd72d3d5fe62cecf0912.jpg" alt="" /></p> <p>你好,我是陈航。</p> <p>在上一篇文章中,我从工程架构与工作模式两个层面,与你介绍了设计 Flutter 混合框架需要关注的基本设计原则,即确定分工边界。</p> <p>在工程架构维度,由于 Flutter 模块作为原生工程的一个业务依赖,其运行环境是由原生工程提供的,因此我们需要将它们各自抽象为对应技术栈的依赖管理方式,以分层依赖的方式确定二者的边界。</p> <p>而在工作模式维度,考虑到 Flutter 模块开发是原生开发的上游,因此我们只需要从其构建产物的过程入手,抽象出开发过程中的关键节点和高频节点,以命令行的形式进行统一管理。构建产物是 Flutter 模块的输出,同时也是原生工程的输入,一旦产物完成构建,我们就可以接入原生开发的工作流了。</p> <p>可以看到,在 Flutter 混合框架中,Flutter 模块与原生工程是相互依存、互利共赢的关系:</p> <ul> <li>Flutter 跨平台开发效率高,渲染性能和多端体验一致性好,因此在分工上主要专注于实现应用层的独立业务(页面)的渲染闭环;</li> <li>而原生开发稳定性高,精细化控制力强,底层基础能力丰富,因此在分工上主要专注于提供整体应用架构,为 Flutter 模块提供稳定的运行环境及对应的基础能力支持。</li> </ul> <p>那么,在原生工程中为 Flutter 模块提供基础能力支撑的过程中,面对跨技术栈的依赖管理,我们该遵循何种原则呢?对于 Flutter 模块及其依赖的原生插件们,我们又该如何以标准的原生工程依赖形式进行组件封装呢?</p> <!-- [[[read_end]]] --> <p>在今天的文章中,我就通过一个典型案例,与你讲述这两个问题的解决办法。</p> <h2>原生插件依赖管理原则</h2> <p>在前面<a href="https://time.geekbang.org/column/article/127601">第 26</a>和<a href="https://time.geekbang.org/column/article/132818">31 篇</a>文章里,我与你讲述了为 Flutter 应用中的 Dart 代码提供原生能力支持的两种方式,即:在原生工程中的 Flutter 应用入口注册原生代码宿主回调的轻量级方案,以及使用插件工程进行独立拆分封装的工程化解耦方案。</p> <p>无论使用哪种方式,Flutter 应用工程都为我们提供了一体化的标准解决方案,能够在集成构建时自动管理原生代码宿主及其相应的原生依赖,因此我们只需要在应用层使用 pubspec.yaml 文件去管理 Dart 的依赖。</p> <p>但<strong>对于混合工程而言,依赖关系的管理则会复杂一些</strong>。这是因为,与 Flutter 应用工程有着对原生组件简单清晰的单向依赖关系不同,混合工程对原生组件的依赖关系是多向的:Flutter 模块工程会依赖原生组件,而原生工程的组件之间也会互相依赖。</p> <p>如果继续让 Flutter 的工具链接管原生组件的依赖关系,那么整个工程就会陷入不稳定的状态之中。因此,对于混合工程的原生依赖,Flutter 模块并不做介入,完全交由原生工程进行统一管理。而 Flutter 模块工程对原生工程的依赖,体现在依赖原生代码宿主提供的底层基础能力的原生插件上。</p> <p>接下来,我就以网络通信这一基础能力为例,与你展开说明原生工程与 Flutter 模块工程之间应该如何管理依赖关系。</p> <h2>网络插件依赖管理实践</h2> <p>在第 24 篇文章“<a href="https://time.geekbang.org/column/article/121163">HTTP 网络编程与 JSON 解析</a>”中,我与你介绍了在 Flutter 中,我们可以通过 HttpClient、http 与 dio 这三种通信方式,实现与服务端的数据交换。</p> <p>但在混合工程中,考虑到其他原生组件也需要使用网络通信能力,所以通常是由原生工程来提供网络通信功能的。因为这样不仅可以在工程架构层面实现更合理的功能分治,还可以统一整个 App 内数据交换的行为。比如,在网络引擎中为接口请求增加通用参数,或者是集中拦截错误等。</p> <p>关于原生网络通信功能,目前市面上有很多优秀的第三方开源 SDK,比如 iOS 的 AFNetworking 和 Alamofire、Android 的 OkHttp 和 Retrofit 等。考虑到 AFNetworking 和 OkHttp 在各自平台的社区活跃度相对最高,因此我就以它俩为例,与你演示混合工程的原生插件管理方法。</p> <h2>网络插件接口封装</h2> <p>要想搞清楚如何管理原生插件,我们需要先使用方法通道来建立 Dart 层与原生代码宿主之间的联系。</p> <p>原生工程为 Flutter 模块提供原生代码能力,我们同样需要使用 Flutter 插件工程来进行封装。关于这部分内容,我在第<a href="https://time.geekbang.org/column/article/132818">31</a>和<a href="https://time.geekbang.org/column/article/141164">39</a>篇文章中,已经分别为你演示了推送插件和数据上报插件的封装方法,你也可以再回过头来复习下相关内容。所以,今天我就不再与你过多介绍通用的流程和固定的代码声明部分了,而是重点与你讲述与接口相关的实现细节。</p> <p><strong>首先,我们来看看 Dart 代码部分。</strong></p> <p>对于插件工程的 Dart 层代码而言,由于它仅仅是原生工程的代码宿主代理,所以这一层的接口设计比较简单,只需要提供一个可以接收请求 URL 和参数,并返回接口响应数据的方法 doRequest 即可:</p> <pre><code>class FlutterPluginNetwork { ... static Future&lt;String&gt; doRequest(url,params) async { // 使用方法通道调用原生接口 doRequest,传入 URL 和 param 两个参数 final String result = await _channel.invokeMethod('doRequest', { "url": url, "param": params, }); return result; } }</code></pre> <p>Dart 层接口封装搞定了,我们再来看看<strong>接管真实网络调用的 Android 和 iOS 代码宿主如何响应 Dart 层的接口调用</strong>。</p> <p>我刚刚与你提到过,原生代码宿主提供的基础通信能力是基于 AFNetworking(iOS)和 OkHttp(Android)做的封装,所以为了在原生代码中使用它们,我们<strong>首先</strong>需要分别在 flutter_plugin_network.podspec 和 build.gradle 文件中将工程对它们的依赖显式地声明出来:</p> <p>在 flutter_plugin_network.podspec 文件中,声明工程对 AFNetworking 的依赖:</p> <pre><code>Pod::Spec.new do |s| ... s.dependency 'AFNetworking' end</code></pre> <p>在 build.gradle 文件中,声明工程对 OkHttp 的依赖:</p> <pre><code>dependencies { implementation "com.squareup.okhttp3:okhttp:4.2.0" }</code></pre> <p><strong>然后</strong>,我们需要在原生接口 FlutterPluginNetworkPlugin 类中,完成例行的初始化插件实例、绑定方法通道工作。</p> <p>最后,我们还需要在方法通道中取出对应的 URL 和 query 参数,为 doRequest 分别提供 AFNetworking 和 OkHttp 的实现版本。</p> <p>对于 iOS 的调用而言,由于 AFNetworking 的网络调用对象是 AFHTTPSessionManager 类,所以我们需要这个类进行实例化,并定义其接口返回的序列化方式(本例中为字符串)。然后剩下的工作就是用它去发起网络请求,使用方法通道通知 Dart 层执行结果了:</p> <pre><code>@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</code></pre> <p>Android 的调用也类似,OkHttp 的网络调用对象是 OkHttpClient 类,所以我们同样需要这个类进行实例化。OkHttp 的默认序列化方式已经是字符串了,所以我们什么都不用做,只需要 URL 参数加工成 OkHttp 期望的格式,然后就是用它去发起网络请求,使用方法通道通知 Dart 层执行结果了:</p> <pre><code>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&lt;String, String&gt; 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); } }); } }); } }</code></pre> <p>需要注意的是,<strong>由于方法通道是非线程安全的,所以原生代码与 Flutter 之间所有的接口调用必须发生在主线程。</strong>而 OktHtp 在处理网络请求时,由于涉及非主线程切换,所以需要调用 runOnUiThread 方法以确保回调过程是在 UI 线程中执行的,否则应用可能会出现奇怪的 Bug,甚至是 Crash。</p> <p>有些同学可能会比较好奇,<strong>为什么 doRequest 的 Android 实现需要手动切回 UI 线程,而 iOS 实现则不需要呢?</strong>这其实是因为 doRequest 的 iOS 实现背后依赖的 AFNetworking,已经在数据回调接口时为我们主动切换了 UI 线程,所以我们自然不需要重复再做一次了。</p> <p>在完成了原生接口封装之后,Flutter 工程所需的网络通信功能的接口实现,就全部搞定了。</p> <h2>Flutter 模块工程依赖管理</h2> <p>通过上面这些步骤,我们以插件的形式提供了原生网络功能的封装。接下来,我们就需要在 Flutter 模块工程中使用这个插件,并提供对应的构建产物封装,提供给原生工程使用了。这部分内容主要包括以下 3 大部分:</p> <ul> <li>第一,如何使用 FlutterPluginNetworkPlugin 插件,也就是模块工程功能如何实现;</li> <li>第二,模块工程的 iOS 构建产物应该如何封装,也就是原生 iOS 工程如何管理 Flutter 模块工程的依赖;</li> <li>第三,模块工程的 Android 构建产物应该如何封装,也就是原生 Android 工程如何管理 Flutter 模块工程的依赖。</li> </ul> <p>接下来,我们具体看看每部分应该如何实现。</p> <h2>模块工程功能实现</h2> <p>为了使用 FlutterPluginNetworkPlugin 插件实现与服务端的数据交换能力,我们首先需要在 pubspec.yaml 文件中,将工程对它的依赖显示地声明出来:</p> <pre><code>flutter_plugin_network: git: url: https://github.com/cyndibaby905/44_flutter_plugin_network.git</code></pre> <p>然后,我们还得在 main.dart 文件中为它提供一个触发入口。在下面的代码中,我们在界面上展示了一个 RaisedButton 按钮,并在其点击回调函数时,使用 FlutterPluginNetwork 插件发起了一次网络接口调用,并把网络返回的数据打印到了控制台上:</p> <pre><code>RaisedButton( child: Text("doRequest"), // 点击按钮发起网络请求,打印数据 onPressed:()=&gt;FlutterPluginNetwork.doRequest("https://jsonplaceholder.typicode.com/posts", {'userId':'2'}).then((s)=&gt;print('Result:$s')), )</code></pre> <p>运行这段代码,点击 doRequest 按钮,观察控制台输出,可以看到,接口返回的数据信息能够被正常打印,证明 Flutter 模块的功能表现是完全符合预期的。</p> <p><img src="https://static001.geekbang.org/resource/image/68/83/6855481fc112697ff2cc03fdcc185883.png" alt="" /></p> <center><span class="reference">图 1 Flutter 模块工程运行示例</span></center> <h2>构建产物应该如何封装?</h2> <p>我们都知道,模块工程的 Android 构建产物是 aar,iOS 构建产物是 Framework。而在第<a href="https://time.geekbang.org/column/article/129754">28</a>和<a href="https://time.geekbang.org/column/article/144156">42</a>篇文章中,我与你介绍了不带插件依赖的模块工程构建产物的两种封装方案,即手动封装方案与自动化封装方案。这两种封装方案,最终都会输出同样的组织形式(Android 是 aar,iOS 则是带 podspec 的 Framework 封装组件)。</p> <p>如果你已经不熟悉这两种封装方式的具体操作步骤了,可以再复习下这两篇文章的相关内容。接下来,我重点与你讲述的问题是:<strong>如果我们的模块工程存在插件依赖,封装过程是否有区别呢?</strong></p> <p>答案是,对于模块工程本身而言,这个过程没有区别;但对于模块工程的插件依赖来说,我们需要主动告诉原生工程,哪些依赖是需要它去管理的。</p> <p>由于 Flutter 模块工程把所有原生的依赖都交给了原生工程去管理,因此其构建产物并不会携带任何原生插件的封装实现,所以我们需要遍历模块工程所使用的原生依赖组件们,为它们逐一生成插件代码对应的原生组件封装。</p> <p>在第 18 篇文章“<a href="https://time.geekbang.org/column/article/114180">依赖管理(二):第三方组件库在 Flutter 中要如何管理?</a>”中,我与你介绍了 Flutter 工程管理第三方依赖的实现机制,其中.packages 文件存储的是依赖的包名与系统缓存中的包文件路径。</p> <p>类似的,插件依赖也有一个类似的文件进行统一管理,即<strong>.flutter-plugins</strong>。我们可以通过这个文件,找到对应的插件名字(本例中即为 flutter_plugin_network)及缓存路径:</p> <pre><code>flutter_plugin_network=/Users/hangchen/Documents/flutter/.pub-cache/git/44_flutter_plugin_network-9b4472aa46cf20c318b088573a30bc32c6961777/</code></pre> <p>插件缓存本身也可以被视为一个 Flutter 模块工程,所以我们可以采用与模块工程类似的办法,为它生成对应的原生组件封装。</p> <p>对于 iOS 而言,这个过程相对简单些,所以我们先来看看模块工程的 iOS 构建产物封装过程。</p> <h3>iOS 构建产物应该如何封装?</h3> <p>在插件工程的 ios 目录下,为我们提供了带 podspec 文件的源码组件,podspec 文件提供了组件的声明(及其依赖),因此我们可以把这个目录下的文件拷贝出来,连同 Flutter 模块组件一起放到原生工程中的专用目录,并写到 Podfile 文件里。</p> <p>原生工程会识别出组件本身及其依赖,并按照声明的依赖关系依次遍历,自动安装:</p> <pre><code>#Podfile target 'iOSDemo' do pod 'Flutter', :path =&gt; 'Flutter' pod 'flutter_plugin_network', :path =&gt; 'flutter_plugin_network' end</code></pre> <p>然后,我们就可以像使用不带插件依赖的模块工程一样,把它引入到原生工程中,为其设置入口,在 FlutterViewController 中展示 Flutter 模块的页面了。</p> <p>不过需要注意的是,由于 FlutterViewController 并不感知这个过程,因此不会主动初始化项目中的插件,所以我们还需要在入口处手动将工程里所有的插件依次声明出来:</p> <pre><code>//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; }</code></pre> <p>在 Xcode 中运行这段代码,点击 doRequest 按钮,可以看到,接口返回的数据信息能够被正常打印,证明我们已经可以在原生 iOS 工程中顺利的使用 Flutter 模块了。</p> <p><img src="https://static001.geekbang.org/resource/image/32/c8/329866c452354bd0524fc3de798b4fc8.png" alt="" /></p> <center><span class="reference">图 2 原生 iOS 工程运行示例</span></center> <p>我们再来看看模块工程的 Android 构建产物应该如何封装。</p> <h3>Android 构建产物应该如何封装?</h3> <p>与 iOS 的插件工程组件在 ios 目录类似,Android 的插件工程组件在 android 目录。对于 iOS 的插件工程,我们可以直接将源码组件提供给原生工程,但对于 Andriod 的插件工程来说,我们只能将 aar 组件提供给原生工程,所以我们不仅需要像 iOS 操作步骤那样进入插件的组件目录,还需要借助构建命令,为插件工程生成 aar:</p> <pre><code>cd android ./gradlew flutter_plugin_network:assRel</code></pre> <p>命令执行完成之后,aar 就生成好了。aar 位于 android/build/outputs/aar 目录下,我们打开插件缓存对应的路径,提取出对应的 aar(本例中为 flutter_plugin_network-debug.aar)就可以了。</p> <p>我们把生成的插件 aar,连同 Flutter 模块 aar 一起放到原生工程的 libs 目录下,最后在 build.gradle 文件里将它显式地声明出来,就完成了插件工程的引入。</p> <pre><code>//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" ... }</code></pre> <p>然后,我们就可以在原生工程中为其设置入口,在 FlutterView 中展示 Flutter 页面,愉快地使用 Flutter 模块带来的高效开发和高性能渲染能力了:</p> <pre><code>//MainActivity.java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); View FlutterView = Flutter.createView(this, getLifecycle(), "defaultRoute"); setContentView(FlutterView); } }</code></pre> <p>不过<strong>需要注意的是</strong>,与 iOS 插件工程的 podspec 能够携带组件依赖不同,Android 插件工程的封装产物 aar 本身不携带任何配置信息。所以,如果插件工程本身存在原生依赖(像 flutter_plugin_network 依赖 OkHttp 这样),我们是无法通过 aar 去告诉原生工程其所需的原生依赖的。</p> <p>面对这种情况,我们需要在原生工程中的 build.gradle 文件里手动地将插件工程的依赖(即 OkHttp)显示地声明出来。</p> <pre><code>//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" ... }</code></pre> <p><strong>至此,将模块工程及其插件依赖封装成原生组件的全部工作就完成了,原生工程可以像使用一个普通的原生组件一样,去使用 Flutter 模块组件的功能了。</strong></p> <p>在 Android Studio 中运行这段代码,并点击 doRequest 按钮,可以看到,我们可以在原生 Android 工程中正常使用 Flutter 封装的页面组件了。</p> <p><img src="https://static001.geekbang.org/resource/image/54/f3/543a78c6839639a28b2eb9246c0196f3.png" alt="" /></p> <center><span class="reference">图 3 原生 Android 工程运行示例</span></center> <p>当然,考虑到手动封装模块工程及其构建产物的过程,繁琐且容易出错,我们可以把这些步骤抽象成命令行脚本,并把它部署到 Travis 上。这样在 Travis 检测到代码变更之后,就会自动将 Flutter 模块的构建产物封装成原生工程期望的组件格式了。</p> <p>关于这部分内容,你可以参考我在<a href="https://github.com/cyndibaby905/44_flutter_module_demo">flutter_module_demo</a>里的<a href="https://github.com/cyndibaby905/44_flutter_module_demo/blob/master/generate_aars.sh">generate_aars.sh</a>与<a href="https://github.com/cyndibaby905/44_flutter_module_demo/blob/master/generate_pods.sh">generate_pods.sh</a>实现。如果关于这部分内容有任何问题,都可以直接留言给我。</p> <h2>总结</h2> <p>好了,关于 Flutter 混合开发框架的依赖管理部分我们就讲到这里。接下来,我们一起总结下今天的主要内容吧。</p> <p>Flutter 模块工程的原生组件封装形式是 aar(Android)和 Framework(Pod)。与纯 Flutter 应用工程能够自动管理插件的原生依赖不同,这部分工作在模块工程中是完全交给原生工程去管理的。因此,我们需要查找记录了插件名称及缓存路径映射关系的.flutter-plugins 文件,提取出每个插件所对应的原生组件封装,集成到原生工程中。</p> <p>从今天的分享可以看出,对于有着插件依赖的 Android 组件封装来说,由于 aar 本身并不携带任何配置信息,因此其操作以手工为主:我们不仅要执行构建命令依次生成插件对应的 aar,还需要将插件自身的原生依赖拷贝至原生工程,其步骤相对 iOS 组件封装来说要繁琐一些。</p> <p>为了解决这一问题,业界出现了一种名为<a href="https://github.com/adwiv/android-fat-aar">fat-aar</a>的打包手段,它能够将模块工程本身,及其相关的插件依赖统一打包成一个大的 aar,从而省去了依赖遍历和依赖声明的过程,实现了更好的功能自治性。但这种解决方案存在一些较为明显的不足:</p> <ul> <li>依赖冲突问题。如果原生工程与插件工程都引用了同样的原生依赖组件(OkHttp),则原生工程的组件引用其依赖时会产生合并冲突,因此在发布时必须手动去掉原生工程的组件依赖。</li> <li>嵌套依赖问题。fat-aar 只会处理 embedded 关键字指向的这层一级依赖,而不会处理再下一层的依赖。因此,对于依赖关系复杂的插件支持,我们仍需要手动处理依赖问题。</li> <li>Gradle 版本限制问题。fat-aar 方案对 Gradle 插件版本有限制,且实现方式并不是官方设计考虑的点,加之 Gradle API 变更较快,所以存在后续难以维护的问题。</li> <li>其他未知问题。fat-aar 项目已经不再维护了,最近一次更新还是 2 年前,在实际项目中使用“年久失修”的项目存在较大的风险。</li> </ul> <p>考虑到这些因素,fat-aar 并不是管理插件工程依赖的好的解决方案,所以<strong>我们最好还是得老老实实地去遍历插件依赖,以持续交付的方式自动化生成 aar。</strong></p> <p>我把今天分享涉及知识点打包上传到了 GitHub 中,你可以把<a href="https://github.com/cyndibaby905/44_flutter_plugin_network">插件工程</a>、<a href="https://github.com/cyndibaby905/44_flutter_module_demo">Flutter 模块工程</a>、<a href="https://github.com/cyndibaby905/44_AndroidDemo">原生 Android</a>和<a href="https://github.com/cyndibaby905/44_iOSDemo">iOS 工程</a>下载下来,查看其 Travis 持续交付配置文件的构建执行命令,体会在混合框架中如何管理跨技术栈的组件依赖。</p> <h2>思考题</h2> <p>最后,我给你留一道思考题吧。</p> <p>原生插件的开发是一个需要 Dart 层代码封装,以及原生 Android、iOS 代码层实现的长链路过程。如果需要支持的基础能力较多,开发插件的过程就会变得繁琐且容易出错。我们都知道 Dart 是不支持反射的,但是原生代码可以。我们是否可以利用原生的反射去实现插件定义的标准化呢?</p> <p>提示:在 Dart 层调用不存在的接口(或未实现的接口),可以通过 noSuchMethod 方法进行统一处理。</p> <pre><code>class FlutterPluginDemo { // 方法通道 static const MethodChannel _channel = const MethodChannel('flutter_plugin_demo'); // 当调用不存在接口时,Dart 会交由该方法进行统一处理 @override Future&lt;dynamic&gt; noSuchMethod(Invocation invocation) { // 从字符串 Symbol("methodName") 中取出方法名 String methodName = invocation.memberName.toString().substring(8, string.length - 2); // 参数 dynamic args = invocation.positionalArguments; print('methodName:$methodName'); print('args:$args'); return methodTemplate(methodName, args); } // 某未实现的方法 Future&lt;dynamic&gt; someMethodNotImplemented(); // 某未实现的带参数方法 Future&lt;dynamic&gt; someMethodNotImplementedWithParameter(param); }</code></pre>

页面列表

ITEM_HTML