Flutter

Flutter 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 flutter.cn 编辑
编辑

Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动、Web、桌面和嵌入式平台。

个人动态

Flutter 发布了文章 · 9月16日

在 Flutter 中使用 TensorFlow Lite 插件实现文字分类

如果您希望能有一种简单、高效且灵活的方式把 TensorFlow 模型集成到 Flutter 应用里,那请您一定不要错过我们今天介绍的这个全新插件 tflite_flutter。这个插件的开发者是 Google Summer of Code(GSoC) 的一名实习生 Amish Garg,本文来自他在 Medium 上的一篇文章《在 Flutter 中使用 TensorFlow Lite 插件实现文字分类》。

tflite_flutter 插件的核心特性:

  • 它提供了与 TFLite Java 和 Swift API 相似的 Dart API,所以其灵活性和在这些平台上的效果是完全一样的
  • 通过 dart:ffi 直接与 TensorFlow Lite C API 相绑定,所以它比其它平台集成方式更加高效。

*无需编写特定平台的代码。

  • 通过 NNAPI 提供加速支持,在 Android 上使用 GPU Delegate,在 iOS 上使用 Metal Delegate。

本文中,我们将使用 tflite_flutter 构建一个 文字分类 Flutter 应用 带您体验 tflite_flutter 插件,首先从新建一个 Flutter 项目 text_classification_app 开始。

初始化配置

Linux 和 Mac 用户

install.sh 拷贝到您应用的根目录,然后在根目录执行 sh install.sh,本例中就是目录 text_classification_app/

Windows 用户

install.bat 文件拷贝到应用根目录,并在根目录运行批处理文件 install.bat,本例中就是目录 text_classification_app/。 

它会自动从 release assets 下载最新的二进制资源,然后把它放到指定的目录下。

请点击到 README 文件里查看更多 关于初始配置的信息

获取插件

pubspec.yaml 添加 tflite_flutter: ^<latest_version>详情)。

下载模型

要在移动端上运行 TensorFlow 训练模型,我们需要使用 .tflite 格式。如果需要了解如何将 TensorFlow 训练的模型转换为 .tflite 格式,请参阅官方指南。 

这里我们准备使用 TensorFlow 官方站点上预训练的文字分类模型,可从这里下载

该预训练的模型可以预测当前段落的情感是积极还是消极。它是基于来自 Mass 等人的  Large Movie Review Dataset v1.0 数据集进行训练的。数据集由基于 IMDB 电影评论所标记的积极或消极标签组成,点击查看更多信息

text_classification.tflitetext_classification_vocab.txt 文件拷贝到 text_classification_app/assets/ 目录下。

pubspec.yaml 文件中添加 assets/

assets:    
  - assets/

现在万事俱备,我们可以开始写代码了。 🚀

实现分类器

预处理

正如 文字分类模型页面 里所提到的。可以按照下面的步骤使用模型对段落进行分类:

  1. 对段落文本进行分词,然后使用预定义的词汇集将它转换为一组词汇 ID;
  2. 将生成的这组词汇 ID 输入 TensorFlow Lite 模型里;
  3. 从模型的输出里获取当前段落是积极或者是消极的概率值。

我们首先写一个方法对原始字符串进行分词,其中使用 text_classification_vocab.txt 作为词汇集。

在 lib/ 文件夹下创建一个新文件 classifier.dart。 

这里先写代码加载 text_classification_vocab.txt 到字典里。

import 'package:flutter/services.dart';

class Classifier {
  final _vocabFile = 'text_classification_vocab.txt';
  
  Map<String, int> _dict;

  Classifier() {
    _loadDictionary();
  }

  void _loadDictionary() async {
    final vocab = await rootBundle.loadString('assets/$_vocabFile');
    var dict = <String, int>{};
    final vocabList = vocab.split('\n');
    for (var i = 0; i < vocabList.length; i++) {
      var entry = vocabList[i].trim().split(' ');
      dict[entry[0]] = int.parse(entry[1]);
    }
    _dict = dict;
    print('Dictionary loaded successfully');
  }
  
}

加载字典

现在我们来编写一个函数对原始字符串进行分词。

import 'package:flutter/services.dart';

class Classifier {
  final _vocabFile = 'text_classification_vocab.txt';

  // 单句的最大长度
  final int _sentenceLen = 256;

  final String start = '<START>';
  final String pad = '<PAD>';
  final String unk = '<UNKNOWN>';

  Map<String, int> _dict;
  
  List<List<double>> tokenizeInputText(String text) {
    
    // 使用空格进行分词
    final toks = text.split(' ');
    
    // 创建一个列表,它的长度等于 _sentenceLen,并且使用 <pad> 的对应的字典值来填充
    var vec = List<double>.filled(_sentenceLen, _dict[pad].toDouble());

    var index = 0;
    if (_dict.containsKey(start)) {
      vec[index++] = _dict[start].toDouble();
    }

    // 对于句子里的每个单词在 dict 里找到相应的 index 值
    for (var tok in toks) {
      if (index > _sentenceLen) {
        break;
      }
      vec[index++] = _dict.containsKey(tok)
          ? _dict[tok].toDouble()
          : _dict[unk].toDouble();
    }

    // 按照我们的解释器输入 tensor 所需的形状 [1,256] 返回 List<List<double>>
    return [vec];
  }
}

使用 tflite_flutter 进行分析

这是本文的主体部分,这里我们会讨论 tflite_flutter 插件的用途。

这里的分析是指基于输入数据在设备上使用 TensorFlow Lite 模型的处理过程。要使用 TensorFlow Lite 模型进行分析,需要通过 解释器 来运行它,了解更多

创建解释器,加载模型

tflite_flutter 提供了一个方法直接通过资源创建解释器。

static Future<Interpreter> fromAsset(String assetName, {InterpreterOptions options})

由于我们的模型在 assets/ 文件夹下,需要使用上面的方法来创建解析器。对于 InterpreterOptions 的相关说明,请 参考这里

import 'package:flutter/services.dart';

// 引入 tflite_flutter
import 'package:tflite_flutter/tflite_flutter.dart';

class Classifier {
  // 模型文件的名称
  final _modelFile = 'text_classification.tflite';

  // TensorFlow Lite 解释器对象
  Interpreter _interpreter;

  Classifier() {
    // 当分类器初始化以后加载模型
    _loadModel();
  }

  void _loadModel() async {
    
    // 使用 Interpreter.fromAsset 创建解释器
    _interpreter = await Interpreter.fromAsset(_modelFile);
    print('Interpreter loaded successfully');
  }

}

创建解释器的代码

如果您不希望将模型放在 assets/ 目录下,tflite_flutter 还提供了工厂构造函数创建解释器,更多信息

我们开始进行分析!

现在用下面方法启动分析:

void run(Object input, Object output);

注意这里的方法和 Java API 中的是一样的。

Object inputObject output 必须是和 Input Tensor 与 Output Tensor 维度相同的列表。

要查看  input tensors 和 output tensors 的维度,可以使用如下代码:

_interpreter.allocateTensors();
// 打印 input tensor 列表
print(_interpreter.getInputTensors());
// 打印 output tensor 列表
print(_interpreter.getOutputTensors());

在本例中 text_classification 模型的输出如下: 

InputTensorList:
[Tensor{_tensor: Pointer<TfLiteTensor>: address=0xbffcf280, name: embedding_input, type: TfLiteType.float32, shape: [1, 256], data:  1024]
OutputTensorList:
[Tensor{_tensor: Pointer<TfLiteTensor>: address=0xbffcf140, name: dense_1/Softmax, type: TfLiteType.float32, shape: [1, 2], data:  8]

现在,我们实现分类方法,该方法返回值为 1 表示积极,返回值为 0 表示消极。

int classify(String rawText) {
    
    //  tokenizeInputText 返回形状为 [1, 256] 的 List<List<double>>
    List<List<double>> input = tokenizeInputText(rawText);
   
    // [1,2] 形状的输出
    var output = List<double>(2).reshape([1, 2]);
    
    // run 方法会运行分析并且存储输出的值
    _interpreter.run(input, output);

    var result = 0;
    // 如果输出中第一个元素的值比第二个大,那么句子就是消极的
    
    if ((output[0][0] as double) > (output[0][1] as double)) {
      result = 0;
    } else {
      result = 1;
    }
    return result;
  }

用于分析的代码

在 tflite_flutter 的 extension ListShape on List 下面定义了一些使用的扩展:

// 将提供的列表进行矩阵变形,输入参数为元素总数 // 保持相等 
// 用法:List(400).reshape([2,10,20]) 
// 返回  List<dynamic>

List reshape(List<int> shape)
// 返回列表的形状
List<int> get shape
// 返回列表任意形状的元素数量
int get computeNumElements

最终的 classifier.dart 应该是这样的:

import 'package:flutter/services.dart';

// 引入 tflite_flutter
import 'package:tflite_flutter/tflite_flutter.dart';

class Classifier {
  // 模型文件的名称
  final _modelFile = 'text_classification.tflite';
  final _vocabFile = 'text_classification_vocab.txt';

  // 语句的最大长度
  final int _sentenceLen = 256;

  final String start = '<START>';
  final String pad = '<PAD>';
  final String unk = '<UNKNOWN>';

  Map<String, int> _dict;

  // TensorFlow Lite 解释器对象
  Interpreter _interpreter;

  Classifier() {
    // 当分类器初始化的时候加载模型
    _loadModel();
    _loadDictionary();
  }

  void _loadModel() async {
    // 使用 Intepreter.fromAsset 创建解析器
    _interpreter = await Interpreter.fromAsset(_modelFile);
    print('Interpreter loaded successfully');
  }

  void _loadDictionary() async {
    final vocab = await rootBundle.loadString('assets/$_vocabFile');
    var dict = <String, int>{};
    final vocabList = vocab.split('\n');
    for (var i = 0; i < vocabList.length; i++) {
      var entry = vocabList[i].trim().split(' ');
      dict[entry[0]] = int.parse(entry[1]);
    }
    _dict = dict;
    print('Dictionary loaded successfully');
  }

  int classify(String rawText) {
    // tokenizeInputText  返回形状为 [1, 256] 的 List<List<double>>
    List<List<double>> input = tokenizeInputText(rawText);

    //输出形状为 [1, 2] 的矩阵
    var output = List<double>(2).reshape([1, 2]);

    // run 方法会运行分析并且将结果存储在 output 中。
    _interpreter.run(input, output);

    var result = 0;
    // 如果第一个元素的输出比第二个大,那么当前语句是消极的

    if ((output[0][0] as double) > (output[0][1] as double)) {
      result = 0;
    } else {
      result = 1;
    }
    return result;
  }

  List<List<double>> tokenizeInputText(String text) {
    // 用空格分词
    final toks = text.split(' ');

    // 创建一个列表,它的长度等于 _sentenceLen,并且使用 <pad> 对应的字典值来填充
    var vec = List<double>.filled(_sentenceLen, _dict[pad].toDouble());

    var index = 0;
    if (_dict.containsKey(start)) {
      vec[index++] = _dict[start].toDouble();
    }

    // 对于句子中的每个单词,在 dict 中找到相应的 index 值
    for (var tok in toks) {
      if (index > _sentenceLen) {
        break;
      }
      vec[index++] = _dict.containsKey(tok)
          ? _dict[tok].toDouble()
          : _dict[unk].toDouble();
    }

    // 按照我们的解释器输入 tensor 所需的形状 [1,256] 返回 List<List<double>>
    return [vec];
  }
}

现在,可以根据您的喜好实现 UI 的代码,分类器的用法比较简单。

// 创建 Classifier 对象
Classifer _classifier = Classifier();
// 将目标语句作为参数,调用 classify 方法
_classifier.classify("I liked the movie");
// 返回 1 (积极的)
_classifier.classify("I didn't liked the movie");
// 返回 0 (消极的)

请在这里查阅完整代码:Text Classification Example app with UI

Text Classification Example App

文字分类示例应用

了解更多关于 tflite_flutter 插件的信息,请访问 GitHub repo: am15h/tflite_flutter_plugin

答疑

问:tflite_flutter 和 tflite v1.0.5 有哪些区别?

tflite v1.0.5 侧重于为特定用途的应用场景提供高级特性,比如图片分类、物体检测等等。而新的 tflite_flutter 则提供了与 Java API 相同的特性和灵活性,而且可以用于任何 tflite 模型中,它还支持 delegate。

由于使用 dart:ffi (dart ↔️ (ffi) ↔️ C),tflite_flutter 非常快 (拥有低延时)。而 tflite 使用平台集成 (dart ↔️ platform-channel ↔️ (Java/Swift) ↔️ JNI ↔️ C)。

问:如何使用 tflite_flutter 创建图片分类应用?有没有类似 TensorFlow Lite Android Support Library 的依赖包?

更新(07/01/2020): TFLite Flutter Helper 开发库已发布。

TensorFlow Lite Flutter Helper Library 为处理和控制输入及输出的 TFLite 模型提供了易用的架构。它的 API 设计和文档与 TensorFlow Lite Android Support Library 是一样的。更多信息请 参考这里

以上是本文的全部内容,欢迎大家对 tflite_flutter 插件进行反馈,请在这里 上报 bug 或提出功能需求

谢谢关注。

感谢 Michael Thomsen。

致谢

  • 译者:Yuan,谷创字幕组
  • 审校:Xinlei、Lynn Wang、Alex,CFUG 社区。

本文联合发布在 TensorFlow 线上讨论区101.devFlutter 中文文档,以及 Flutter 社区线上渠道。

查看原文

赞 1 收藏 0 评论 0

Flutter 关注了用户 · 8月13日

Android开发者 @androiddevs

关注 476

Flutter 发布了文章 · 6月17日

[实战] Flutter 上的内存泄漏监控

一、前言

Flutter 所使用的 Dart 语言具有垃圾回收机制,有垃圾回收就避免不了会内存泄漏。
在 Android 平台上有个内存泄漏检测工具 LeakCanary
它可以方便地在 debug 环境下检测当前页面是否泄漏。
本文将会带你实现一个 Flutter 可用的 LeakCanary,
并讲述我是怎么用该工具检测出了 1.9.1 Framework 上的两个泄漏。

二、Dart 中的弱引用

在具有垃圾回收的语言中,弱引用是检测对象是否泄漏的一个好方式。
我们只需弱引用观测对象,等待下次 Full GC
如果 GC 之后对象为 null,说明被回收了,
如果不为 null 就可能是泄漏了。

Dart 语言中也有着弱引用,它叫 Expando<T>
看下它的 API:

<!-- skip -->

class Expando<T> {
  external T operator [](Object object "");
  external void operator []=(Object object, T value);
}

你可能会好奇上述代码弱引用体现在哪里呢?
其实是在 expando[key]=value 这个赋值语句上。
Expando 会以弱引用的方式持有 key,这里就是弱引用的地方。

那么问题来了,这个 Expando 弱引用持有的是 key
但是本身又没有提供 getKey() 这样的 API,
我们就无从下手去得知 key 这个对象是否被回收了。

为了解决这个问题,我们来看下 Expando 的具体实现,
具体的代码在 expando_path.dart

<!-- skip -->

@path
class Expando<T> {
  // ...
  T operator [](Objet object "") {
    var mask = _size - 1;
    var idx = object._identityHashCode & mask;
    // sdk 是把 key 放到了一个 _data 数组内,这个 wp 是个 _WeakProperty
    var wp = _data[idx];

    // ... 省略部分代码
    return wp.value;
    // ... 省略部分代码
  }
}
注意: 此 patch 代码不适用于 Web 平台

我们可以发现这个 key 对象是放到了 _data 数组内,
用了一个 _WeakProperty 来包裹,那么这个 _WeakProperty 就是关键类了,
看下它实现,代码在 weak_property.dart

<!-- skip -->

@pragma("vm:entry-point")
class _WeakProperty {

  get key => _getKey();
  // ... 省略部分代码
  _getKey() native "WeakProperty_getKey";
  // ... 省略部分代码
}

这个类有我们想要的 key,可以用于判断对象是否还在。

怎么获取这种私有属性和变量呢?
Flutter 中的 Dart 是不支持反射的(为了优化打包体积,关闭了反射),
有没有其他办法来获取到这种私有属性呢?

答案肯定是 “有”,为了解决上述问题,
我来向大家介绍一个 Dart 自带的服务——
Dart VM Service

三、Dart vm_service

Dart VM Service (后面简称 vm_service
是 Dart 虚拟机内部提供的一套 Web 服务,数据传输协议是 JSON-RPC 2.0。
不过我们并不需要要自己去实现数据请求解析,
官方已经写好了一个可用的 Dart SDK 给我们用:vm_service

ObjRef, Objid 的作用

先介绍 vm_service 中的核心内容:ObjRefObjid

vm_service 返回的数据主要分为两大类,
ObjRef(引用类型) 和 Obj(对象实例类型)。
其中 Obj 完整的包含了 ObjRef 的数据,
并在其基础上增加了额外信息
ObjRef 只包含了一些基本信息,例如:idname 等)。

基本所有的 API 返回的数据都是 ObjRef
ObjRef 里面的信息满足不了你的时候,
再调用 getObject(,,,)来获取 Obj

关于 idObjObjRef 都含有 id
这个 id 是对象实例在 vm_service 里面的一个标识符,
vm_service 几乎所有的 API 都需要通过 id 来操作,
比如:getInstance(isolateId, classId, ...)
getIsolate(isolateId)
getObject(isolateId, objectId, ...)

如何使用 vm_service 服务

vm_service 在启动的时候会在本地开启一个 WebSocket 服务,
服务 URI 可以在对应的平台中获得:

  • Android 在 FlutterJNI.getObservatoryUri() 中;
  • iOS 在 FlutterEngine.observatoryUrl 中。

有了 URI 之后我们就可以使用 vm_service 的服务了,
官方有一个帮我们写好的 SDK: vm_service
直接使用内部的 vmServiceConnectUri 就可以获得一个可用的 VmService 对象。

vmServiceConnectUri 的参数需要是一个 ws:// 协议的 URI,默认获取的是 http 协议,需要借助 convertToWebSocketUrl方法转化下

四、泄漏检测实现

有了 vm_service 之后,我们就可以用它来弥补 Expando 的不足了。
按照之前的分析,我们要获 Expando 的私有字段 _data
这里可以使用 getObject(isolateId, objectId)") API,
它的返回值是 Instance
内部的 fields 字段保存了当前对象的所有属性。
这样我们就可以遍历属性获取到 _data,来达到反射的效果。

现在的问题是 API 参数中的 isoateIdobjectId 是什么呢?
根据我前面介绍的 id 相关内容,它是对象在 vm_serive 中的标识符。
也就是我们只有通过 vm_service 才可以获取到这两个参数。

IsolateId 的获取

Isolate(隔离区)是 Dart 里面的一个非常重要的概念,
基本上一个 isolate 相当于一个线程,
但是和我们平常接触的线程不同的是:不同 isolate 之间的内存不共享。

因为有了上述特性,我们在查找对象的时候也要带上 isolateId
通过 vm_servicegetVM() API 可以获取到虚拟机对象数据,
再通过 isolates 字段可以获取到当前虚拟机所有的 isolate

那么怎么筛选出我们想要的 isolate 呢?
这里简单起见只筛选主 isolate
这部分的筛选可以查看 dev_tools
的源码: service_manager.dart#_initSelectedIsolate 函数。

ObjectId 的获取

我们要获取的 objectId 就是 expandovm_service 中的 id
这里可以把问题扩展下:

如何获取指定对象在 vm_service 中的 id

这个问题比较麻烦,vm_service 中没有实例对象和 id 转换的 API,
有个 getInstance(isolateId, classId, limit) 的 API,
可以获取某个 classId 的所有子类实例,
先不说如何获取到想要的 classId
此 API 的性能和 limit 都让人担忧。

没有好办法了吗?其实我们可以
借助 Library 的 顶级函数(直接写在当前文件,不在类中,例如 main 函数)
来实现该功能。

简单说明下 Library 是什么东西,Dart 中的分包管理是根据 Library 来的,同一个 Library 内的类名不能重复,一般情况下一个 .dart 文件就是一个 Library,当然也有例外,比如:part of 和 export。

vm_service 有个 invoke(isolateId, targetId, selector, argumentIds)") API,
可以用来执行某个常规函数
gettersetter、构造函数、私有函数属于非常规函数),
其中如果 targetId 是 Library 的 id,那么 invoke
执行的就是 Library 的顶级函数。

有了 invoke Library 顶级函数的路径,
就可以用它实现对象转 id 了,代码如下:

<!-- skip -->

int _key = 0;
/// 顶级函数,必须常规方法,生成 key 用
String generateNewKey() {
  return "${++_key}";
}

Map<String, dynamic> _objCache = Map();
/// 顶级函数,根据 key 返回指定对象
dynamic keyToObj(String key) {
  return _objCache[key];
}

/// 对象转 id
String obj2Id(VMService service, dynamic obj) async {

  // 找到 isolateId。这里的方法就是前面讲的 isolateId 获取方法
  String isolateId = findMainIsolateId();
  // 找到当前 Library。这里可以遍历 isolate 的 libraries 字段
  // 根据 uri 筛选出当前 Library 即可,具体不展开了
  String libraryId = findLibraryId();

  // 用 vm service 执行 generateNewKey 函数
  InstanceRef keyRef = await service.invoke(
    isolateId,
    libraryId,
    "generateNewKey",
    // 无参数,所以是空数组
    []
  );
  // 获取 keyRef 的 String 值
  // 这是唯一一个能把 ObjRef 类型转为数值的 api
  String key = keyRef.valueAsString;

  _objCache[key] = obj;
  try {
    // 调用 keyToObj 顶级函数,传入 key,获取 obj
    InstanceRef valueRef = await service.invoke(
      isolateId,
      libraryId,
      "keyToObj",
      // 这里注意,vm_service 需要的是 id,不是值
      [keyRef.id]
    )
    // 这里的 id 就是 obj 对应的 id
    return valueRef.id;
  } finally {
    _objCache.remove(key);
  }
  return null;
}

对象泄漏判断

现在我们已经可以获取到 expando 实例在
vm_service 中的 id 了,接下来就简单了。

先通过 vm_service 获取到 Instance
遍历里面的 fields 属性,找到 _data 字段
(注意 _dataObjRef 类型),
用同样的办法把 _data 字段转成 Instance 类型
_data 是个数组,Obj 里面有数组的 child 信息)。

遍历 _data 字段,如果都是 null
表明我们观测的 key 对象已经被释放了。
如果 item 不为 null
再次把 item 转为 Instance 对象,
取它的 propertyKey
(因为 item 是 _WeakProperty 类型,
Instance 里面特地为 _WeakProperty 开了这个字段)。

强制 GC

文章开头说到,如果要判断对象是否泄漏,
需要在 Full GC 之后判断弱引用是否还在。
有没有办法手动触发 GC 呢?

答案是有的,vm_service 虽然没有强制 GC 的 API,
但是 Dev Tools 的内存图标右上角有个 GC 的按钮,
我们仿照着它来操作就行!
Dev Tools 是调用了 vm_service
getAllocationProfile(isolateId, gc: true)") API
来实现手动 GC 的。

至于这个 API 触发的是不是 FULL GC,
并没有说明,我测试触发的都是 FULL GC,
如果要确定在 FULL GC 之后检测泄漏,
可以监听 gc 事件流,
vm_service 提供了该功能。

至此为止,我们已经可以实现泄漏的监控,
而且可以获取到泄漏目标在 vm_serive 中的 id 了,
下面就开始获取分析泄漏路径。

五、获取泄漏路径

关于泄漏路径的获取,
vm_service 提供了一个 API 叫 getRetainingPath(isolateId, objectId, limit)")。
直接使用此 API 就可以获取到泄漏对象到 GC Roots 的引用链信息,
是不是感觉很简单?不过光这样可不行,
因为它有以下几个坑点:

Expando 持有问题

如果在执行 getRetainingPath 的时候,
泄漏对象被 expando 持有的话会产生以下两个问题

  • 因为该 API 返回的引用链只有一条,
    返回的引用链会经过 expando,导致无法获取真正的泄漏节点信息;
  • 在 ARM 设备上会出现 native crash,
    具体错误出现在 utf8 字符解码上。

此问题很好解决,注意下在前面泄漏检测完之后,
释放掉 expando 就行。

id 过期问题

Instance 类型的 idClassLibraryIsolate
这种 id 不一样,是会过期的。
vm_service 中对于此类临时 id 的缓存容量默认大小是 8192
是一个循环队列。

因为此问题的存在,我们在检测到泄漏的时候,
不能只保存泄漏对象的 id,需要保存原对象,
而且不能强引用持有对象。
所以这里我们还是需要使用 expando
来保存我们检测到的泄漏对象,
等到需要分析泄漏路径的时候,
再把对象专为 id

六、1.9.1 Framework 上的内存泄漏

完成了泄漏检测和路径获取之后,
得到了一个简陋的 leakcanary 工具。
当我在 1.9.1 版本的 Framework 下测试此工具的时候发现,
我观测一个页面它就泄漏一个页面!!!

通过 dev_tools dump 出来的对象来看,的确泄漏了!

也就是 1.9.1 Framework 里面存在着泄漏,
而且此泄漏会泄漏整个页面。

接下来开始排查泄漏原因,这里就碰到一个问题:
泄漏路径太长:getRetainingPath 返回的链路长度有 300+,
排查了一下午也没有找到问题根源。

结论:直接根据 vm_service 返回的数据是很难分析问题来源的,
需要对泄漏路径的信息二次处理下。

如何缩短引用链

首先看下泄漏路径为什么会这么长,
通过观测返回的链路后发现,
绝大部分的节点都是 Flutter UI 组件节点
(例如:widgetelementstaterenderObject)。

也就是说引用链经过了 Flutter 的 widget tree,
熟悉 Flutter 的开发者应该都知道,
Flutter 的 widget tree 的层次是非常深的。
既然引用链长的原因是因为包含了 widget tree,
而且 widget tree 基本都是成块出现的,
那我们只要把引用链中的节点根据类型来分类、聚合,
就可以大幅缩短泄漏路径了。

分类

根据 Flutter 的组件类型,将节点分为以下几种类型:

  • element:对应 Element 节点;
  • widget:对应 Widget 节点;
  • renderObject:对应 RenderObject节点;
  • state:对应 State<T extends StatefulWdget> 节点;
  • collection:对应集合类型节点,例如:ListMapSet
  • other:对应其他节点。

聚合

节点的分类做好了之后,就可以把相同类型的节点聚合一下。
这里提下我的聚合方式:

collection 类型的节点看成了连接节点,
相邻的相同节点合并到一个集合内,
如果两个相同类型的集合中间是通过 collection 节点相连的,
就继续把这两个集合合并成一个集合,递归进行。

通过 分类-聚合 的处理后,原先 300+ 的链路长度,可以缩短为 100+。

继续排查 1.9.1 Framework 的泄漏问题,
路径虽然缩短了,可以找到问题大致出现在 FocusManager 节点上!
但是具体问题还是难以定位,主要有以下两点:

  • 引用链节点缺少代码位置:因为 RetainingObject 数据中只有 parentFieldparentIndexparentKey 三个字段来表示当前对象引用下一个对象的信息,通过该信息找代码位置效率低下;
  • 无法知道当前 Flutter 组件节点的信息:比如 Text 的文本信息,element 所在的 widget 是啥,state 的生命周期状态,当前组件属于哪个页面,等等。

介于上述两个痛点,还需要对泄漏节点的信息做扩展处理:

  • 代码位置:节点的引用代码位置其实只需要解析 parentField 就行,通过 vm_serive 解析 class,取内部的 field,找到对应的 script 等信息。此方法可以获取到源码;
  • 组件节点信息:Flutter 的 UI 组件都是继承自 Diagnosticable,也就是只要是 Diagnosticable 类型的节点都可获取到非常详细的信息(dev_tools 调试时候,组件树信息就是通过 Diagnosticable.debugFillProperties 方法获取的)。除了这个还需要扩展当前组件所在 route 的信息,这个很重要,判断组件所在页面用。

排查 1.9.1 Framework 泄漏根源

通过上述的种种优化后,我得到了下面这个工具,
在两个 _InkResponseState 节点中发现了问题:

泄漏路径中有两个 _InkResponseState 节点所属的 route 信息不同,
表明这两个节点在两个不同的页面中。
顶部 _InkResponseState 的描述信息显示 lifecycle not mounted
说明组件已经销毁了,但是还是被 FocusManager 引用着!
问题出现在这,来看下这部分代码

代码中可以明显的看到 addListener 时候
StatefulWidget 的生命周期理解错误。
didChangeDependencies 是会多次调用的,
dispose 只会调用一次,
所以这里就会出现 listener 移除不干净的情况。

修复了上述泄漏之后,发现还有一处泄漏。
排查后发现泄漏源在 TransitionRoute 中:

当打开一个新页面的时候,
该页面的 Route(也就是代码中的 nextRoute
会被前一个页面的 animation 所持有,
如果页面跳转都是 TransitionRoute
那么所有的 Route 都会泄漏!

好消息是以上泄漏都在 1.12 版本之后修复了。

修复完上述两个泄漏之后,
再次测试,RouteWidget 都可以回收了,
至此 1.9.1 Framework 排查完毕。


本文作者: 戚耿鑫

现就职于快手应用研发平台组 Flutter 团队,负责 APM 方向开发研究。从 2018 年开始接触 Flutter,在 Flutter 混合栈、工程化落地、UI 组件等方面有大量经验。

联系方式:qigengxin@kuaishou.com


「Flutter 中文社区教程」由社区的开发者投稿,内容同步发布到 flutter.cn 网站以及 「Flutter 社区」的各个社交平台。本项目内部测试中,筹备完成后会开放投稿。

在 flutter.cn 阅读本文:https://flutter.cn/community/...

查看原文

赞 4 收藏 3 评论 0

Flutter 关注了用户 · 3月19日

xiangzhihong @xiangzhihong

著有《React Native移动开发实战》、《Kotlin入门与实战》和《Weex跨平台开发实战》,即将出版《React Native移动开发进阶》,正努力完成《Flutter跨平台开发实战》

关注 4799

Flutter 发布了文章 · 2019-09-19

Flutter 中文文档网站 flutter.cn 正式发布!

在通常的对 Flutter 介绍中,最耳熟能详的是下面四个特点:

file

  1. 精美 (Beautiful):充分的赋予和发挥设计师的创造力和想象力,让你真正掌控屏幕上的每一个像素。
  2. 极速 (Fast):基于 Skia 的硬件加速图形引擎,帮助你媲美原生应用的速度。
  3. 高效 (Productive):Flutter 的 Stateful Hot Reload (热重载) 特性帮助你实时看到应用修改的结果。
  4. 开放 (Open):不管是 Flutter 引擎还是 Dart 开发语言,甚至是工程团队的工作空间,Flutter 都在 GitHub 开源协作、与社区透明分享。

Flutter 由 Google 主导,与全球社区的开发者共同协作开发。中国使用 Flutter 的开发者数量位居全球第一,同时也为 Flutter 的生态提供了很大的帮助和贡献。本文从 Open 的角度,为大家介绍在中国的 Flutter 社区里有哪些 最新的资源和更新

Flutter 和 Dart 中文文档发布

文档对任何技术项目的本地化都至关重要,维护一份高质量的 Flutter / Dart 文档,是过去几个月来我们一直在努力的工作。我们非常高兴的宣布:Flutter 社区中文资源网站 (flutter.cn)和 Flutter 中文文档 正式发布,欢迎大家的访问!我们同样为 Flutter 的 codelabs 制作了一个单独的二级页面在 codelabs.flutter-io.cn,欢迎大家访问。

同样在本地化的是 Dart 的中文文档网站 dart.cn,我们也欢迎大家贡献自己的时间,影响全球的中国开发者!

企业应用和实践

Flutter 在 2018 年的成功并非偶然,原因不仅是 Flutter 产品本身的优秀,最重要的还是来自国内一线互联网公司以及广大开源技术爱好者对 Flutter 的支持,Flutter 团队也非常重视中国开发者市场,2018 年里两次重大的版本发布都是在中国的活动上宣布,1.0 正式版的发布,也在谷歌北京办公室举办了一场正式的活动。

我们看到很多头部公司的官方社交平台都发布了他们对 Flutter 的研究和实践,包括但不仅限于:

仍有很多文章没有列出,我们感谢所有所有为 Flutter 贡献中文教程内容的开发者 / 合作伙伴们!

线上社区 & 线下社区活动

线上社区

有很多开发者在网上撰写 Flutter 相关的技术博客,这里特别要提到的是 阿里巴巴闲鱼团队 对 Flutter 内容的不断输出和社区贡献,闲鱼团队的技术博客 也成为了开发者们深入学习Flutter 的必备材料。

掘金和思否也分别有专门的 Flutter 专栏内容:

线下活动

2019 年以来,Flutter 团队分别参加了年初的 D2 和年中的 GMTC 大会,并拜访了当地的 Flutter 开发者。

同时在今年年初的饿了么技术开放日,以及 3 月 5 日和 22 日的由 RTC 开发社区举办的 Flutter Meetup,7月底,360 大学举办了 360 互联网技术训练营 Flutter 专场,人数爆满。

开源项目

FlutterBoost
FlutterBoost 是一个 Flutter 插件,它可以轻松地为现有原生应用程序提供 Flutter 混合集成方案。

Fish Redux
Fish Redux 是一个基于 Redux 数据管理的组装式 flutter 应用框架, 它特别适用于构建中大型的复杂应用(摘自官方 repo 介绍)。

Flutter Go
Flutter Go 是一个汇总各种 Flutter 资源为一体的应用,可以帮助开发者快速上手 Flutter。

RealRichText
RealRichText 由字节跳动开源,实现图文混排。

有中国开发者开源的适合国内需求的类似中国城市三级联动选择器 city_pickers,也有国内开发者贡献的全球项目 flutter_datetime_picker,以及最初在国内维护 Flutter 中文文档的 Wen Du 开发的 Dart HTTP 请求库 dio 等。

这些由中国的头部企业以及众多开发者开发的 Flutter 插件,为国内的 Flutter 生态打下了非常好的基础。

新的 Flutter 镜像发布

清华 TUNA 协会
定时与 Flutter 社区 Storage 镜像同步,Pub API 采取定时主动抓取策略,镜像配置了完善的失败回源策略(推荐)。

PUB_HOSTED_URL:
https://mirrors.tuna.tsinghua.edu.cn/dart-pub
FLUTTER_STORAGE_BASE_URL:
https://mirrors.tuna.tsinghua.edu.cn/flutter

CNNIC
基于 TUNA 协会的镜像服务,数据策略与 TUNA 一致,通过非教育网的域名访问。

PUB_HOSTED_URL:
http://mirrors.cnnic.cn/dart-pub
FLUTTER_STORAGE_BASE_URL:
http://mirrors.cnnic.cn/flutter

腾讯云开源镜像站
定时(每天凌晨)与 TUNA 协会镜像同步,数据有延迟,访问速度有待反馈。

PUB_HOSTED_URL:
https://mirrors.cloud.tencent.com/dart-pub
FLUTTER_STORAGE_BASE_URL:
https://mirrors.cloud.tencent.com/flutter

谷歌开发者大会 GDD China 2019

在今年的 GDD 上,Flutter 发布了 1.9 正式版
同时,Google Flutter 团队的工程师和 PM 们以及 Flutter 中国社区的志愿者们也在谷歌开发者大会的 02 - Flutter 展区为大家回答问题:

感谢名单

Flutter 中国社区的活跃壮大离不开每一位为社区贡献的人,在这些对社区充满热情的人们共同努力下,中国的社区蓬勃发展,谢谢大家!

非常感谢下述提到的 Leadership 团队、公司 / 社区合作伙伴、中文文档译者团队、Google 团队等人:

中文文档译者团队

  1. 感谢 Xinlei Wang 对 Flutter 中文文档的翻译、维护和在社区中活跃解答大家的问题,感谢中文文档翻译项目的运营志愿者 @jin-zz 和 @hayley1643,感谢上海 GDG 的 Fei Feng 和 Ping Ma。
  2. 感谢掘金翻译计划协调资源帮助完成 Flutter 文档本地化项目的启动 (2019/4/16);
  3. 感谢阅文前端团队协调资源帮助完成 Flutter 文档本地化项目第一阶段的收尾 (2019/6/21);
  4. 感谢持续维护 Dart 中文文档的 Haijun Gu (@amisare),感谢他允许并帮忙将已经翻译的内容贡献到 dart.cn;
  5. 感谢参与 Flutter / Dart 文档本地化项目的志愿者们:@5ibinbin, @AigeStudio, @amisare, @ASkyBig, @bestony, @changer0, @chenglu, @chunlea, @Dong09, @DongXYZ, @Dosimz, @duxinfeng, @ElderJames, @EvilLee6, @gejiaheng, @git-patrickliu, @guanxf, @Hao3o5, @hijiangtao, @Iamnotromantic, @iCell, @inferjay, @JasonLinkinBright, @Jenniferyingni, @jin-zz, @krave1986, @lennonover, @linguowu, @linhou, @liuzhen0218, @LyndonChin, @MzoneCL, @nanjingboy, @nervouself, @nesger, @noahziheng, @panda8z, @phxnirvana, @qiuyuezhong, @reachzhai, @Realank, @realcarlos, @Robinhaizhou, @shengxinyuan, @songfei, @SouthernBox, @talisk, @tyisme614, @Vadaski, @vinciarts, @wonderful89, @wswdevil, @x531tanxl, @XatMassacrE, @xilihuasi, @xiongcai, @yantao13145210, @yanxin, @yanyixin, @yeqiling, @youngyou, @Yuan, @Zhangdroid, @zhangjiana, @Zhiw, @zhuangtao97, @ziank 等。

CFUG 团队

  1. 感谢 Adam Yi, Zhongdong Yang, Xiaoyu Li, Shena Bian, Bing Gao, Steven Tian 以及 Study Jams 学员和谷创字幕组译者在 18 年 5 月份加班加点完成初版 Flutter Codelabs 和 Flutter 中文社区资源网站的设计;
  2. Shena Bian, Yihui Miao 对 Flutter 中文社区资源网址的设计;
  3. 感谢 Zhongdong Yang 对 Flutter 社区中文资源网站 flutter.cn, codelabs.flutter-io.cn 的前端技术实现;
  4. 感谢雪狼帮助适配、调试和制作双语对照的翻译工具以及 debug server 的 CI 调试部署,感谢 @Zheaoli 帮忙搭建和调试 CI 以及文档部署方案;
  5. 感谢 @eatmiya 维护 Flutter 社区微信公众号。

Google 公司和合作伙伴团队

  1. 特别感谢 Google Flutter 团队里一直对中国社区支持并作出卓越贡献的 Tao Dong, Xiao Yu 和 Yuqian Li,感谢前 DevRel 团队 Lynn Wang 在 Flutter 社区里的活跃和积极贡献,感谢谷歌开发者运营团队的 Anna;
  2. 感谢 360 前端委员会的 Shuo He 和 360 大学的 Weiyang Zhang 举办的 360 互联网技术训练营 Flutter 专场,感谢声网的 Xingxing Qin 和 Olivia,感谢 RTC 开发者社区和 GDG 社区积极举办多次与 Flutter 相关的活动(2019 年);
  3. 感谢各大公司 / 团队的社区联系人帮助在内部分享 Flutter 和推动 Flutter 的落地,及时有效的安排 Googler 的拜访,内部的技术问题的收集和优先级排序,以及在各种大会上出席做演讲认可和推广 Flutter;
  4. 感谢阿里巴巴闲鱼的宗心、KyleWong 和树彬,感谢一直对 Flutter 的代码、文章等资源贡献,感谢开源 Fish Redux 和 FlutterBoost,感谢在阿里内部推动 Flutter;
  5. 感谢阿里拍卖前端团队开源的 flutter-go;
  6. 感谢快手的 Kai Sun 引荐公司团队与 Flutter 团队积极沟通,并多次参与社区演讲。

谷歌活动 / 社区活动志愿者

  1. 感谢 GMTC 的活动组织者和主办方给予 Flutter 如此大量的曝光和内容展示,以及在 InfoQ 旗下众多开发者公众号里大力宣传 Flutter (2018 - 2019);
  2. 感谢 Xinlei Wang 组织了 Flutter 成都高校系列活动(2018 年 12 月);
  3. 感谢 Xinlei Wang, Congli Ma, Yanbo Liu (Flutter GDE), Bill Fu (TikTok 团队) 在 GDD 大会现场的 Flutter 展位站台并回答大家的问题(2019/9/11)。

社区贡献者(暂未分类)

  1. 感谢 Qinglian Zhang 和 Wen Du 在最初 Flutter 社区和资源匮乏的情况下,建立了 Flutter 交流论坛、翻译了 Flutter 中文文档(2018 年初);
  2. 感谢掘金社区的创始人 Glow Chiang 对 Flutter 社区的无限大力支持,包括 2018 年 8 月份的 Flutter 征文大赛,Flutter 相关的掘金小册,以及掘金翻译计划负责人 Xuewen Ding;
  3. 感谢 Sijie Cheng 引荐全国各大院校的开源协会 (2019 年 1 月),建立领导 Flutter x 高校团队,为 Flutter 在清华、北大、中科大、上海大学、重庆大学等建立镜像提供了可能,感谢清华大学 TUNA 协会的 Yuxiang Zhang, Yiqun Hui 帮助在清华大学开源镜像站加入 Flutter 镜像 & 实现 Flutter Pub site API 的同步策略;
  4. 感谢思否社区的创始人 Sunny Gao 帮助我们建立了一个「直达」Flutter 专题页面的 短链接 sifou.com/flutter(2019 年 9 月);
  5. 感谢前 GDG 组织者,现郑州玩码科技负责人 @inferjay 帮助维护 flutter-io.cn 域名和基建资源,感谢为谷歌活动构建 AI 体验馆报名系统的千跃优意 (Cheerue) 创始人大树,帮助维护 flutter.cn / dart.cn / material-io.cn 等域名和基建资源;
  6. 感谢 OpenWrite 团队开发出多平台文章同步系统。

特别感谢的社区名单

  1. 北京、上海、广州、深圳 GDG 社区;
  2. RTC 开发者社区;
  3. 360 大学;
  4. 掘金社区;
  5. 思否社区;
  6. 奇舞团;
  7. 上海交通大学 Linux 用户组;
  8. 清华大学 TUNA 协会;
  9. 上海大学 Linux 用户组;
  10. 重庆大学蓝盟团队。

以上内容多数以贡献开始时间排序,感谢名单、机构顺序不分前后,欢迎到 Flutter.CN 网站上在这里查看 最新的感谢名单

查看原文

赞 8 收藏 5 评论 1

Flutter 发布了文章 · 2019-08-14

最新 Flutter 团队工程师中文演讲 | Flutter 的性能测试和理论

006Xmmmggy1g5z2wwrlrlj31hd0u0wzu.jp

本视频为 Google Flutter 团队的软件工程师 Xiao Yu 在 2018 谷歌开发者大会做的演讲,演讲题目是《Flutter 的性能测试和理论》。

这个视频里将会通过近半个小时的视频和演示带大家了解 Flutter 应用渲染时的时间消耗,了解这些之后会更好的帮助开发者们发现应用的性能问题,同时介绍了性能测试工具的使用和问题排查方法,非常值得收藏和分享。

视频信息

  • 时长:27:11
  • 难度:中级
  • 语言:中文
  • 内容分类:演讲,性能优化,谷歌官方

视频地址

请点击进入思否编程学院的「Flutter 社区技术视频」课程 观看本视频。

YouTube 频道原始视频链接:
https://www.youtube.com/watch...

欢迎转载,也欢迎根据视频内容在 思否社区 讲述分享和讲述自己的性能优化经验,感谢你为 Flutter 贡献中文内容。

演示文稿 PDF 下载
https://go2url.cn/gdd-flutter...

相关视频

来自 Google Flutter 团队的工程师 Yuqian Li:深入了解 Flutter 的高性能图形渲染

致谢

  • Google Flutter 团队 Xiao Yu:内容、PPT、后期屏幕录像
  • Flutter 社区 Lynn 爱茶叶蛋:视频剪辑、后期处理
查看原文

赞 5 收藏 2 评论 2

Flutter 发布了文章 · 2019-07-11

Flutter 1.7 正式版发布

今天,我们非常高兴地向大家宣布又一个正式版本的发布 —— Flutter 1.7,这是继上次 I/O 时众多重要功能发布以来的一次小更新。Flutter 1.7 包含了对 AndroidX 的支持,满足了 Play 商店近期对应用提出的要求,包含了一些新的和增强过的组件,修复了开发者们提出的 bug 等。

如果你已经安装,并使用默认稳定构建渠道 (stable channel) 的 Flutter,要升级到 1.7 版本,只需要运行 flutter upgrade 即可。同时,你可以在 这个文档里 查看如何新安装 Flutter。

支持 AndroidX

AndroidX 是 Android 团队用于在 Jetpack 中开发、测试、打包和发布库以及对其进行版本控制的开源项目,帮助 Android 应用通过最新的组件保持更新而无需牺牲向后兼容性。目前 AndroidX 已经稳定,很多 Flutter packages 已经更新和支持它,Flutter 现在可以支持 创建一个 AndroidX 项目 (new Flutter project with AndroidX) 了,这也减少了与 Android 生态系统集成所你需要做的工作。

当创建 Flutter 项目的时候,你可以通过添加 --androidx 来确保生成的项目文件支持 AndroidX,更多关于将项目迁移到 AndroidX 的相关信息,请访问 官方文档 上的说明。我们也在积极努力为使用了 AndroidX 和 Android 混合库的应用带去 AndroidX 或 Jetifier 的支持,也会将其作为 add-to-app 的中的一项来支持,接下来的文章中会为大家带来更多相关的内容。

支持 Android App Bundles 和 64 位的 Android 应用

从 2019 年 8 月 1 日开始,为了 target 到 Android Pie 版本,开发者们在 Google Play 上发布的应用 必须支持 64 位架构。Flutter 一直都支持生成 64 位的 Android 应用,在 1.7 版本里,我们加入了对 Android App Bundles 的支持,开发者们可以在一次提交里同时 target 到 64 位和 32 位。可通过阅读 这篇文档 了解到如何分别生成 32 位和 64 位到应用等更多内容。

新一批的 widget 和框架的功能增强

我们希望你的应用在任何平台上都可以看起来平滑自然,我们会持续在平台相关的 widgets 上投入。

如下所示了一个名为 [RangeSlider](https://github.com/flutter/flutter/pull/31681) 的 widget,帮助你在单个滑块儿上选择一组值:

RangeSlider widget 支持连续或者分散的效果

更新之后SnackBar 支持了最新的 Material 规范,文档里增加了许多 样例代码

Cupertino 是用来构建精美的 iOS 体验的 widgets 库,我们对其进行了大量的更新。特别提出的是,我们提高了 CupertinoPickerCupertinoDateTimePicker widget 的保真度,并增加了对非英语语言本地化的支持。

我们提升了 iOS 上的 文本选择和编辑体验。此外,我们新增了一个 示例,关于如何使用同一份代码库,调整不同平台的操作体验和适配。

文本渲染有了很大的提升,支持了丰富的 排版样式:包括数字表格式对齐、旧式风格数字 (tabular and old-style numbers)、斜线零 (slashed zeros)、样式集 (stylistic sets),如这个示例应用截图所示:

有了 OpenType 的字体支持,你可以用 Flutter 进行复杂的文字排版了

最后,我们加入了对 游戏控制器 的支持,会有更好玩的应用出现吗?

初心不忘

整个团队付出很多努力推出了 Flutter 1.7 正式版,我们解决了开发者们在 GitHub 上提出的 1250 多个问题

随着 Flutter 的快速增长,我们看到大家向我们报告了很多新的问题。为了保证项目过程的透明,我们一直在通过 GitHub 运行着这一套错误报告系统,但一些相对较小的项目,目前这个流程工作的并不是非常顺利。虽然我们在不想关 issue 关闭上有一些新的进展,但是过去几个月我们的 issue 还是增长的非常明显。我们也在努力增加这方面的资源配置,可以帮助我们更快的区分 bug,关闭及合并相同的 issue,以及将一些提问引导到 StackOverflow

在近期的开发者调查里,很多开发者希望我们在文档和错误信息方面有更持续的投入。一个关键部分是能够在 VSCode 和 Android Studio 里更结构化的输出错误信息,我们已经在着手 这方面的工作

我们也修复了崩溃率最高的 bug,Flutter 工具的写权限问题。Flutter 现在可以更优雅的处理写权限导致的崩溃问题,会又一个明晰的指示关于如何解决。

文档方面,我们会持续增加示例代码。与此同时,你也可以通过 Flutter create 命令直接创建示例文档,如下是命令:

flutter create --sample=widgets.Form.1 mysample

如果通过这种方式创建示例,你将在文档中的 Sample in the App 这一栏看到:

我们也会持续把每周 Flutter widgets 视频嵌入到文档中,在开发者们浏览各种 widget 的时候可以得到更全面的理解。

还有一些幕后的设施建设工作正在进行,以便 Flutter app 更好的在 macOS 和 Windows 平台运行。比如支持一些较为重要的平台操作,比如右键和一些特别的平台基建工作(比如 MSBuild 等)。不过,这些非移动平台的支持目前还没有在稳定构建渠道 (stable channel) 发布。

最后,当你在苹果电脑上开发 Flutter 应用的时候,我们支持了 新的 Xcode 构建系统,这个对新的应用是默认开启的,也同时方便 支持现有的应用

不断壮大的 Flutter 社区

一如既往,我们非常高兴看到 Flutter 在受众群体和应用场景上继续持续增长,同时我们也欣赏各种不同的 Flutter 使用方式。自 I/O 以来,Flutter 团队致力于全球范围内的各项活动:从中国的 GMTC 到纽约和墨西哥的交流会和演讲等,面对面对大家交流 Flutter 应用开发是一件特别棒的事情。

之前我们提到过Reflectly,它是一个丹麦的公司,他们在 iOS 和 Android 平台开发了非常有吸引力的应用程序。他们的应用程序被美国 iPhone 应用商店评为当日最佳应用。这也证明了 Flutter 的真正潜力远远超过实现体验流畅的应用(同时可以帮助开发者获得成功)。

查看 Reflectly 的开发者成功故事:https://www.bilibili.com/vide...

在柏林的 WeAreDevelopers 大会中,BMW 发布了他们基于 Flutter 的应用,目前已经在开发中。下面这段描述来自 Guy Duncan,他是 BMW 集团互联公司的 CTO:

通过结合 Dart 和 Flutter,我们实现了第一个真正跨平台的移动工具包;我们认为它打破了原有的游戏规则,可以平衡数字交互和物联网的功能特性。

通过使用主流的工具链、自动化工具和现代化的编程模式,我们可以优化循环时延、安全性、商业应用特性的推送成本。

除了应用程序,整个开源社区所涉及的众多 资源插件Flutter 社区活动Meetup 也使得 Flutter 变得格外生机勃勃。我们会持续关注大家基于 Flutter 所实现的各种有趣的应用,同时也非常荣幸和大家一起分享其中的乐趣。

图片来源: @damian2048

查看原文

赞 10 收藏 3 评论 0

认证与成就

  • SegmentFault 讲师
  • 获得 68 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-10-15
个人主页被 2.1k 人浏览