Critchley Zhang collaborates with Chicago

Flutter performance analysis, engineering architecture, and some details

1. Why do the Flutter

There are many cross-end technologies, why to choose Flutter, what advantages it can bring and what disadvantages it has.

Take a look at the concrete engineering effect first

Web side links

Effect of flutter engineering

Flutter vs. native

However, native performance is arguably the most efficient, but from an engineering workload perspective, especially for rapid trial and error and business expansion phases, Flutter is by far the preferred tool.

Flutter VS Web

Any cross-end technology is based on a multi-end thinking to solve the problem of engineering efficiency, many of the previous cross-end technology, such asReact NativeHowever, as we all know, there is a huge gap between the running efficiency of Web on mobile terminal and that of PC, which results in that RN cannot effectively complete a variety of complex interactive operations (such as complex animation operations, interactive performance, etc.) on mobile terminal, even if it is introducedReality of LottieThe engine will still be very slow on low-end phones (of course you can use some of your own engine technology to solve this for each end, but it will lose cross-end significance).

Flutter performance

Lutter’s compilation method and production are the prerequisite for its efficient operation. Unlike cross-compilation on the Web (cross-compilation on the Web mostly uses the concept of “bridge” to invoke compilation products, usually using the native portal + the Bridge on the Web), Flutter almost takes dart source code and builds it on different platforms. This “debridge” build is exactly what we want to achieve with native performance. (Dart was originally designed with many front-end constructs in mind, of course. The front-end is particularly noticeable from the syntax, and the original dart2js was based on the same “bridge” concept).

For example, in the new Version of Flutter released by Google on 23 September, Windows builds are supported using tools like Visual Studio (if you want to build your Flutter project into a Windows build, you need to install some VS related build plug-ins in advance). SLN generates the engineering solution under Windows, and finally generates the call method of DLL, which runs smoothly, you can download the Release. Zip in the attachment to try to run:

(PS: All the compilation works are done with the same code, including the Web address, mobile case and Windows case above)

Performance comparison with RN:

The above is a comparison of some data of Flutter and RN under the same functional module, which is a representative group extracted from numerous data

Diversity of cross-platform

! []] (img – blog. Csdnimg. Cn / 20201022165…).

engine

Flare-flutter is an excellent animation engine for Flutter. The animation generated by Flutter has been tested on Windows, mobile and the Web.

Syntactic sugar

The comprehensive evaluation

Interactive applications

The interactions generated by Flutter can be embedded into any endpoint using a compact set of instructions, bringing great promise to interactive scenarios (teaching scenarios, etc.). The following is a demo scenario for live synchronous interactions

2.Flutter business architecture

There is no existing MVVM framework for Flutter, but we can use the Element tree feature to implement MVVM

ViewModel

abstract class BaseViewModel {
  bool _isFirst = true;
  BuildContext context;

  bool get isFirst => _isFirst;

  @mustCallSuper
  void init(BuildContext context) {
    this.context = context;
    if (_isFirst) {
      _isFirst = false;
      doInit(context);
    }
  }

  // the default load data method
  @protected
  Future refreshData(BuildContext context);

  @protected
  void doInit(BuildContext context);

  void dispose();
}
Copy the code
class ViewModelProvider<T extends BaseViewModel> extends StatefulWidget { final T viewModel; final Widget child; ViewModelProvider({ @required this.viewModel, @required this.child, }); static T of<T extends BaseViewModel>(BuildContext context) { final type = _typeOf<_ViewModelProviderInherited<T>>(); _ViewModelProviderInherited < T > Element provider = / / query tree InheritedElement cache context.ancestorInheritedElementForWidgetOfExactType(type)? .widget; return provider? .viewModel; } static Type _typeOf<T>() => T; @override _ViewModelProviderState<T> createState() => _ViewModelProviderState<T>(); } class _ViewModelProviderState<T extends BaseViewModel> extends State<ViewModelProvider<T>> { @override Widget build(BuildContext context) { return _ViewModelProviderInherited<T>( child: widget.child, viewModel: widget.viewModel, ); } @override void dispose() { widget.viewModel.dispose(); super.dispose(); }} / / InheritedWidget Element can be tree cache class _ViewModelProviderInherited < T extends BaseViewModel > extends InheritedWidget {  final T viewModel; _ViewModelProviderInherited({ Key key, @required this.viewModel, @required Widget child, }) : super(key: key, child: child); @override bool updateShouldNotify(InheritedWidget oldWidget) => false; }Copy the code

DataModel

import 'dart:convert'; import 'package:pupilmath/datamodel/base_network_response.dart'; import 'package:pupilmath/datamodel/challenge/challenge_ranking_list_item_data.dart'; import 'package:pupilmath/utils/text_utils.dart'; / / / history list class ChallengeHistoryRankingListResponse extends BaseNetworkResponse < ChallengeHistoryRankingData > { ChallengeHistoryRankingListResponse.fromJson(Map<String, dynamic> json) : super.fromJson(json); @override ChallengeHistoryRankingData decodeData(jsonData) { if (jsonData is Map) { return ChallengeHistoryRankingData.fromJson(jsonData); } return null; } } class ChallengeHistoryRankingData { String props; int bestRank; Int onlistTimes; Int total; / / the total number challenge List < ChallengeHistoryRankingItemData > ranks; // qrCode String get qrcode => textutils.isempty (props)? '' : json.decode(props)['qrcode'] ?? ''; ChallengeHistoryRankingData.fromJson(Map<String, dynamic> json) { props = json['props']; bestRank = json['bestRank']; onlistTimes = json['onlistTimes']; total = json['total']; if (json['ranks'] is List) { ranks = []; (json['ranks'] as List).forEach( (v) => ranks.add(ChallengeHistoryRankingItemData.fromJson(v))); }}} / / / historical record of the item class ChallengeHistoryRankingItemData {ChallengeRankingListItemData champion; / / the best ChallengeRankingListItemData user; ChallengeHistoryRankingItemData.fromJson(Map<String, dynamic> json) { if (json['champion'] is Map) champion = ChallengeRankingListItemData.fromJson(json['champion']); if (json['user'] is Map) user = ChallengeRankingListItemData.fromJson(json['user']); }}Copy the code

View

import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:pupilmath/datamodel/challenge/challenge_history_ranking_list_data.dart'; import 'package:pupilmath/entity_factory.dart'; import 'package:pupilmath/network/constant.dart'; import 'package:pupilmath/network/network.dart'; import 'package:pupilmath/utils/print_helper.dart'; import 'package:pupilmath/viewmodel/base/abstract_base_viewmodel.dart'; import 'package:rxdart/rxdart.dart'; / / the daily challenges history record class ChallengeHistoryListViewModel extends BaseViewModel {BehaviorSubject < ChallengeHistoryRankingData > _challengeObservable = BehaviorSubject(); Stream<ChallengeHistoryRankingData> get challengeRankingListStream => _challengeObservable.stream; @override void dispose() { _challengeObservable.close(); } @override void doInit(BuildContext context) { refreshData(context); } @override Future refreshData(BuildContext context) { return _loadHistoryListData(); } _loadHistoryListData() async { Map<String, dynamic> parametersMap = {}; parametersMap["pageNum"] = 1; parametersMap["pageSize"] = 10; HandleDioRequest (() => networkHelper.instance.getdio ().get(challengeHistoryListUrl, queryParameters: parametersMap), onResponse: (Response response) { ChallengeHistoryRankingListResponse rankingListResponse = EntityFactory.generateOBJ(json.decode(response.toString())); if (rankingListResponse.isSuccessful) { _challengeObservable.add(rankingListResponse.data); } else { _challengeObservable.addError(null); } }, onError: (error) => _challengeObservable.addError(error), ); } Future<ChallengeHistoryRankingData> syncLoadHistoryListData( int pageNum, int pageSize, ) async { Map<String, dynamic> parametersMap = {}; parametersMap["pageNum"] = pageNum; parametersMap["pageSize"] = pageSize; try { Response response = await NetWorkHelper.instance .getDio() .get(challengeHistoryListUrl, queryParameters: parametersMap); ChallengeHistoryRankingListResponse rankingListResponse = EntityFactory.generateOBJ(json.decode(response.toString())); if (rankingListResponse.isSuccessful) { return rankingListResponse.data; } else { return null; } } catch (e) { printHelper(e); } return null; }}Copy the code

Some infrastructure

How to initialize and interact with views and ViewModels:

The Business architecture of Flutter is pulled away

If it is a unified series of product business form, a set of core architecture can be removed and reused in the same production product line. For example, the current product line is mainly education, and the multi-terminal property of FLUTTER can be usedTemplate manufacturing plant, rendering template engine, adaptation framework, and cross-end interfaceWe can quickly form templates that can be promoted and reused. We can get twice the result with half the effort to solve the problem of trial and error cost in the business. Of course, other product lines can be the same.

3. The Flutter adaptation

UI adaptation within any framework, especially cross-end adaptation, is extremely heavy work. Therefore, the conversion process between platforms within the same layout is very important. Initially, flutter did not provide any adaptation such as DP or SP. In addition, directly changing the underlying matrix conversion ratio may make the original hd resolution of mobile phone display not so clear. The width and height of flutter are all num units, which will be adjusted to the unit size of each platform only when the flutter is compiled. To reduce the designer’s design burden, a set of ios designs can be used. For example, the universal design of 375 x 667 is converted to Android 360 x 640 (corresponding to 1080 x 1920). The units of the Flutter are also related to the pixel density of the corresponding mobile phone.

Construct a conversion utility class:

Import 'dart: IO '; import 'dart:ui'; import 'dart:math'; import 'package:pupilmath/utils/print_helper.dart'; bool initScale = false; // Double iosScaleRatio = 0; AndroidScaleRatio = 0; androidScaleRatio = 0; androidScaleRatio = 0; // Double textScaleRatio = 0; const double baseIosWidth = 375; const double baseIosHeight = 667; const double baseIosHeightX = 812; const double baseAndroidWidth = 360; const double baseAndroidHeight = 640; void _calResizeRatio() { if (Platform.isIOS) { final width = window.physicalSize.width; final height = window.physicalSize.height; final ratio = window.devicePixelRatio; final widthScale = (width / ratio) / baseIosWidth; final heightScale = (height / ratio) / baseIosHeight; iosScaleRatio = min(widthScale, heightScale); } else if (Platform.isAndroid) { double widthScale = (baseAndroidWidth / baseIosWidth); double heightScale = (baseAndroidHeight / baseIosHeight); double scaleRatio = min(widthScale, heightScale); AndroidScaleRatio = double. Parse (scaleratio.toString ().substring(0, 4)); } } bool isFullScreen() { return false; } // double resizeUtil(double value) {if (! initScale) { _calResizeRatio(); initScale = true; } if (Platform.isIOS) { return value * iosScaleRatio; } else if (Platform.isAndroid) { return value * androidScaleRatio; } else { return value; }} // Zoom restore // Each screen has a different zoom ratio. If the question is set on ios devices, the question coordinate values need to be converted to the original coordinates. Double unResizeUtil(double Value) {if (iosScaleRatio == 0) {_calResizeRatio(); } if (Platform.isIOS) { return value / iosScaleRatio; } else { return value / androidScaleRatio; }} / / text zooming _calResizeTextRatio () {final width = window. PhysicalSize. The width; final height = window.physicalSize.height; final ratio = window.devicePixelRatio; double heightRatio = (height / ratio) / baseIosHeight / window.textScaleFactor; double widthRatio = (width / ratio) / baseIosWidth / window.textScaleFactor; textScaleRatio = min(heightRatio, widthRatio); } double resizeTextSize(double value) { if (textScaleRatio == 0) { _calResizeTextRatio(); } return value * textScaleRatio; } double resizePadTextSize(double value) { if (Platform.isIOS) { final width = window.physicalSize.width; final ratio = window.devicePixelRatio; final realWidth = width / ratio; If (realWidth > 450) {return value * 1.5; } else { return value; } } else { return value; } } double autoSize(double percent, bool isHeight) { final width = window.physicalSize.width; final height = window.physicalSize.height; final ratio = window.devicePixelRatio; if (isHeight) { return height / ratio * percent; } else { return width / ratio * percent; }}Copy the code

Specific use:

So every time if you have or adaptation scheme of resolution, modify resizeUtil can directly, but the problem with this is that in the process of writing unit becomes very long, and not familiar with the engineering team will be easy to forget to write, lead to error-detection time longer, code invasive is higher, so the use of the dart language extension function characteristics, Make some improvements for resizeUtil.

Low intrusive resizeUtil

Construct the desired units by extending dart’s NUM, using DP and SP as examples, and add extensions to resizeUtil:

DimensionsNum on resizeUtil(this.todouble ()); Sp double get sp => resizeTextSize(this.todouble ()); PadSp => resizePadTextSize(this.todouble ()); }Copy the code

Then write the units directly in the layout:

4. Some pits in Flutter

A pit on a generic

When we first started using generics on mobile for automatic parsing of data, we used t.tostring to determine the type, but when we compiled into the Web release, the program that worked on mobile didn’t work on the Web:

At the beginning, the target was always on the compilation mode. Because there were three compilation modes dev Profile Release, only the compilation mode could not run on Release, mistakenly thinking that there was a bug in the compilation under Release. After in-depth discussion with the Flutter team, In the Release mode of the Web version, everything is compressed (including the definition of the type), so in the release mode, t.tostring () returns null, so the generic characteristics are not recognized. Github.com/flutter/flu…

In release mode everything is minified, the (T.toString() == “Construction2DEntity”) comparison fails and you get entity null returned.

If you change the code to (T ==Construction2DEntity) it will fix your app.

Finally, it is suggested that it is safest to directly write T== in any mode

class EntityFactory { static T generateOBJ<T>(json) { if (1 == 0) { return null; } else if (T = = "ChallengeRankingListDataEntity") {/ / / the daily challenges list return ChallengeHomeRankingListResponse. FromJson (json) as T; } else if (T == "KnowledgeEntity") { return KnowledgeEntity.fromJson(json) as T; }}}Copy the code

How do I use iframe to load other web pages after I’ve written the web product

For mobile applications, webview_flutter can solve the problem of loading the Web, but when compiled into a Web product, it cannot be directly loaded using the WebView plugin. In this case, you need to use some of the methods dart originally designed to write web pages, namely HtmlElmentView:

import 'package:flutter/material.dart'; import 'dart:ui' as ui; import 'dart:html' as html; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Iframe() ), floatingActionButton: FloatingActionButton( onPressed: (){}, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } class Iframe extends StatelessWidget { Iframe(){ ui.platformViewRegistry.registerViewFactory('iframe', (int viewId) { var iframe = html.IFrameElement(); iframe.src='https://flutter.dev'; return iframe; }); } @override Widget build(BuildContext context) { return Container( width:400, height:300, child:HtmlElementView(viewType: 'iframe') ); }}Copy the code

However, this approach introduces a new underlying refresh rendering problem (when the mouse moves over an element, it will constantly flash refresh), which has been fixed in the new version. If you are interested, you can check out: github.com/flutter/flu…

How does Flutter load local HTML and communicate

Built-in HTML is a lot of engineering requirements, a lot of online data are made by the local HTML data stream and then loaded in, this approach is not good compatibility, and the process of writing is prone to a lot of file flow can not read the problem, in fact, these methods are not very comfortable, We should use IFrameElement to load and communicate, much like the front end:

Webview gestures don’t work properly on ios13.4

The official Webview_flutter gesture was blocked and could not be used normally after ios 13.4 was updated. This problem was temporarily resolved by switching to Flutter_webview_plugin (currently webview has made specific fixes, but has not been verified yet). However, flutter_webview_plugin cannot write user-agent on ios, so it can be solved by modifying the local plug-in code:

File location is

Flutter /. Pub – cache/hosted/pub. The flutter – IO. Cn/flutter_webview_plugin – 0.3.11 / ios/Classes/FlutterWebviewPlugin. M Add the following code to line 146 (after initializing WKWebViewConfiguration in initWebview) {if (@available(iOS 9.0, *)) {if (userAgent! = (id)[NSNull null]) { self.webview.customUserAgent = userAgent; }}

There are ongoing discussions about webview_flutter gestures: github.com/flutter/flu…

5. Layout and computation

Container widgets and render widgets

GlobalKey

RenderBox is used to obtain the size and position of the rendered control:

Floating point arithmetic

Dart will automatically randomize the last decimal bit if the operation length is too long. This will cause some floating-point operations to be uncertain each time, requiring manual precision conversions, such as whether two line segments are collinear:

Translation and rotation of Matrix

In the process of matrix conversion, if ordinary matrix. Translate is used, the rotate will be followed by translate, which will superimpose coefficients on the rotated base. In this way, the result is not the desired result. LeftTranslate should be used to keep each operation independent:

6. Project optimization

Avoid time-consuming build() methods:

Redraw area optimization:

Avoid using Opacity as much as possible

The single threaded model of Flutter

All events in the Microtask Queue are executed before the Microtask Queue is empty

Time consuming methods are placed on isolate

7. Miscellaneous summary

After a long period of research and project verification on FLUTTER, I have my own summary of flutter:

(1) Flutter performs well on mobile terminals and runs smoothly. The optimized App with a lot of graphics calculation still runs smoothly on old Android phones in 2013, and the smoothness of ios is comparable to native.

(2) Flutter is still being improved for Web applications. There are still many problems with FLUTTER, including mobile WebViews and programmed Web applications. Flutter is not suitable for large-scale web production environment.

(3) As for the mixing with Native, in order to avoid memory and rendering problems in the application of hybrid stack, it is suggested to design the Native flutter nodes on leaf nodes as far as possible, that is, the business stack jumps to the flutter and then returns to the Native stack as soon as possible after completion;

(4) Based on the native compilation method of “debridge”, there are high expectations for Flutter to run on all platforms in the future. The mobile apps that have been verified so far perform well when packaged as Windows apps. Of course, some larger applications need time to explore and improve.

(5) In terms of syntax, dart in Flutter is becoming simpler and is also learning from some of the best front-end frameworks, such as React. There are also many similarities in Kotlin. It feels like the Flutter team is working hard to promote the development of the big front-end era.

All in all, Flutter does offer many surprises that previous cross-end solutions could not, and it is believed that in the near future multiple applications will become more and more important, especially in terms of the cost of new business exploration.

These are some superficial summaries of Flutter, welcome to discuss with interested friends.

Netease technology lover team continues to recruit teammates! Netease Youdao. We choose youdao because we love you. We look forward to your joining us. Your resume can be sent to [email protected]

Attachment: Link: pan.baidu.com/s/1_JjnD1q5… Extraction code: 7R4I