By the way, This is Flutter’s third year of debut. In the past two years, I have used it to write several applications as small as possible, such as calculators, flashlights and compasses. I also have a few humble opinions on Flutter. This article is pure nonsense after great fun, without too much code, just a brief talk about Flutter for those who are preparing for or thinking of sinking into the pit. Including its infrastructure, design principles, application scenarios and future development, etc.

What is Flutter?

What is a Flutter? To paraphrase the current website of Flutter:

Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.

There are a few key points: “UI Toolkit”, “Natively compiled” and “single codebase”.

First, Google defines Flutter as a UIToolkit, a tool biased towards the UI. Second, Flutter can provide a near-native effect, and most importantly, a piece of code that can be used on mobile, Web and Desktop. “This is something that almost every front-end developer dreams of. Imagine that you could write a single piece of code and run it on mobile, Web, and desktop. This would be like directly connecting to several popular front ends: Web, Android, IOS, Linux, MacOS, and Windows. As of this article, Flutter has been updated to 1.7. It is performing very well on both Android and IOS mobile platforms. Flutter is currently in beta on the Web and will be available soon, and on the desktop in the near future.

The official architecture of Flutter is as follows:

It is divided into three main parts. The bottom part is Platform Specific, which is used to accommodate different Platform features. In the foreseeable future, the logic of this part will change as more platforms are supported. The Engine in the middle, implemented by C/C++, has two main functions: one is used to communicate with the upper and lower layers; The second is the Render of graphic images. Finally, the Framenwork, the top layer, is implemented by Dart, mainly some UI-related encapsulation implementation, which is almost all the wheels for Dart Related.

The principles of Flutter design are not that complicated, especially if you have made mobile games or maps that require a lot of scene drawing. Android phones have a developer option called “Show layout boundaries” :

The boundary of the layout is the boundary of every View in the Android application interface layout. The first thing we need to know is that all views in Android are rectangular, even though they present different images on the interface, such as a circle or a star. You can think of Android’s UI as a wall, and the views are like picture frames on the wall. Each frame is rectangular, even though it’s framed with a different image.

Open “Show Layout Boundaries” and you can see the “borders” of each View on the screen:

Most of the time, we use this feature to look at the layout of the interface to find layout problems. We can also use this function to view the current interface layout of any app installed on the phone, such as Xianyu:

However, you can’t always see the layout boundaries of some app interfaces, like amap’s:

Even though there are many elements on the map interface, they are not stacked with many views like the idle Fish APP above. Another example is a game like King of Glory:

You don’t see any View boundaries except for SystemUI boundaries.

The above two types of applications are collectively referred to as “draw” applications. Why don’t these draw applications use View components provided by the system Framework? Because the requirements do not meet the technical requirements, and do not meet the requirements of the product. Technically you can try adding hundreds of buttons to a layout and updating them frequently to see what happens; As for the product, EMMMMMmm… You can imagine the effect of setting luban I sequence frames (actually in U3D, of course) from various angles on the map interface in ImageView. Therefore, these applications with high requirements for drawing will mostly use their own way to draw the interface. While UI controls provided natively on platforms like Android or IOS are essentially just a bunch of elements drawn and packaged for easy reuse, games like GL implementations have a large number of wrapped interface element components that can be directly ported. If we put a platform device interface as shown in a canvas, then the interface element is the image on the canvas, and if we provide a possible, will this piece of canvas equipment placed “tear down” from a platform to another device, so we can at least realize cross-platform in the user’s point of view. As we mentioned earlier, UI controls that are provided natively on platforms like Android or IOS are essentially just a bunch of elements drawn and packaged for easy reuse. Ideally, one of these elements would be ported to the other. Is it Android to IOS or IOS to Android? Obviously, neither is possible. Both have accumulated a large amount of native logic on their respective platforms, and are highly coupled with their respective platforms. Forced implementation is no different from rewriting the entire UI level.

One of the main ideas of software architecture is to extract commonness. Commonness is the possibility of being designed. Take Button controls of different platforms as an example, can we find commonness in Button (Android), UIButton (Apple), < Button > (Web) and so on? The answer is yes, but not realistically. If the difficulty coefficient of Button extraction from one platform is 1 and transplanted to another platform, then the difficulty coefficient of Button extraction to another platform will become 2, and the difficulty coefficient will increase with the increase of different platforms. Moreover, the UI layer is a volatile layer, which can be modified minute by minute, and the difficulty will increase exponentially. Therefore, we need more low-level design. As mentioned above, the interface is like a canvas, and all you need to do is transplant the entire canvas, and you can paint whatever you want on that canvas. So, we need to find commonalities at the bottom of each platform. At the software level, we can see all kinds of different images on the display interface depending on the graphics rendering engine, For example, renderers on various browsers, OPengL-based CG, CA, CI frameworks on IOS, and recently launched much-ridiculed Metal, Skia for 2D rendering on Android, OpenGLES for 3D rendering and the newer Vulkan. If we can use the same rendering engine on all platforms, at least we can unify the canvas first, SO… Google chose Skia, which has long been used by Android. (PS: Comparing rendering engines to “canvases” is not accurate. A more accurate description of a canvas would be the Surface or Layer Container of each platform, but I can’t find a noun bird that is easier for ordinary people to understand…)

In addition to its steady use on Android for more than a decade, Skia is supported by all major browsers, and more importantly, Skia is Google’s own (albeit adopted) son. Therefore, we only need to use Skia on the IOS platform to achieve at least the universality of Web, Android and IOS platforms. As for why we don’t use OpenGL directly, I think the main reason is that no matter what platform it is, 2D rendering of ordinary applications is the main reason, and Skia’s 2D rendering is very mature and stable, so using OpenGL directly to achieve 2/3D rendering is convenient, but not necessary. Besides, One small step is less likely to fall than one big step, right? As for whether Or not Flutter will provide better application level support for GL in the future, the official answer is as follows:

Today we don’t support for 3D via OpenGL ES or similar. We have long-term plans to expose an optimized 3D API, but right now we’re focused on 2D.

Once the canvas is done, all that is left is basically to stack the various elements on top of the Flutter architecture, which is a manual task that I won’t cover in this article.

This is just a glimpse into the overall design of Flutter. In fact, there are many details that Flutter needs to be implemented in addition to graphics rendering. But what you can see is that Flutter, both in its design and in Google’s official description, is more of a UI Olkit than a framework that can directly invade native systems. So we can simply say that Flutter is a tool for making cross-platform UIs.

According to the is Flutter?

Why Flutter? Since the differentiation of software development platforms, the efforts of various giants on cross-platform have never stopped. Here, I will divide cross-platform into two types according to the implementation principle: the first type is to virtualize a running environment, such as Java running in virtual machines; The second category is secondary encapsulation using existing cross-platform environments, such as various frameworks that use browser Core and JavaScript encapsulation. The former not only can almost completely shield the influence of platform correlation and provide unified external interface, but also relatively close to the original operation efficiency; The latter makes the best use of existing cross-platform solutions, but the execution efficiency of implementation logic for different platforms still varies greatly, and in some cases, different apis need to be provided according to different platforms. Atom’s development framework ElectronJS, for example, provides different API implementation logic for different platforms in its API documentation:

The Flutter combines both. On the one hand, it needs to provide a virtualized environment for Dart code execution; On the other hand, it directly uses the graphical rendering engine scheme of the lower layer to implement the UI logic directly. Although it does not explicitly provide different API interfaces for different platforms like ElectronJS, its DEFINITION of UIToolkit provides great convenience for us to modify the native code of the platform. The organization of its Project directory is a good example of this:

In Android Studio, you can directly right-click the root directory or Android /ios directory to directly open native Android or ios projects from the new Android Studio window or Xcode to modify:

Therefore, you can see that Google’s current definition of Flutter is very clear: A UIToolkit, which is just A UI OlKit. Many friends will feel sorry for it, but I think it is a compromise, and it is properly handled. Why is it a compromise? In the case of ElectronJS or React Native, does it make sense to call different apis for different platforms? It makes no sense… Let me call the platform’s native API; Second, want to go straight to the native system? Android is easy, but other platforms are not so easy. I’d rather believe that a sow will grow a tree than have Cook or Nadella open up the OS source code to adapt Or have Cook or Nadella help adapt Flutter. Therefore, it is perfect to have Flutter only as a UIToolkit at this stage and give other platform-specific logic back to their respective platforms for implementation. As we can see from Google’s ongoing efforts to further integrate Flutter with their Own Android or Chrome, we will see a better solution for Flutter in the near future.

Unlike some other cross-platform solutions, Flutter’s near-native rendering is superior, and the interface implemented in Idle Fish, for example, is almost the same:

To sum up, Flutter is more of a compromise, as I said, that allows you to achieve a certain level of cross-platform code just right.

How to use Flutter.

How to use Flutter? I’m not going to teach you how to develop Flutter. The official documentation for Flutter is complete in both English and Chinese, and the web is full of tutorials on everything from getting started to giving up. The “how to use” I describe here is more likely to be used in what circumstances. To explain this, we need to know how Flutter really works, not just in terms of the performance and difficulty that developers care about, but also in terms of the user’s first feel. To this end, I wrote two demos with six codes on Flutter, Android and IOS platforms for comparison:

  1. Timer for Android

  2. Timer for Flutter

  3. Timer for IOS

  4. Player for Android

  5. Player for Flutter

  6. Player for IOS

A Timer is a Timer modified from the default code used to create a Flutter project. This code implements the logic of adding a count when a button is clicked:

The reason for using this default project code is to control fewer variables. Since Flutter already has a default, we just need to create an identical Demo for Android and IOS. This way we can ensure that the Flutter performance does not degrade because I make a few random code changes. However, in order to simplify the operation, Ai modified it to click the button once and then add the number every 100 milliseconds until the number is 100. This process theoretically takes 100ms x 100 = 10000ms = 10s:

This simply changes the _incrementCounter implementation logic of the _MyHomePageState class in the default project code to Stream periodically setState:

void _incrementCounter() {
// setState(() {
// _counter++;
/ /});
  Stream<int>.periodic(Duration(milliseconds: 100), (x) => x + 1)
      .take(100)
      .forEach((int count) async {
    setState(() {
      _counter = count;
    });
  });
}
Copy the code

Note that the periodic implementations of Flutter do not use the Timer API of their respective platforms, but instead use a lower level Stream within the Flutter, in order to shield the platform API logic from the feathery effects. On Android and IOS, Thread is used on their respective platforms:

var tick = 0
Thread(Runnable {
    while (tick < 100) {
        tick++
        this@MainActivity.runOnUiThread { count.text = tick.toString() }
        Thread.sleep(100)
    }
}).start()
Copy the code
@objc func createThread(a) {
    Thread(target: self, selector: #selector(doCount), object: nil).start()
}

@objc func doCount(a){
    for i in 1.100 {
        DispatchQueue.main.async {
            self.count.text = i.description
        }
        usleep(100000)}}Copy the code

Finally, Dart distinguishes different modes. In debug mode, codes such as assert will have a certain impact on the execution efficiency. Therefore, Aige will generate Release packages for all projects on the Android platform for testing, while IOS… Because I was too poor to pay $99 and didn’t want to play with triad platforms like XX Man, I used debug mode as a reference for android platform in the whole IOS platform.

The other Player is a Player, because Timer is mainly to test the default project of Flutter against Android and IOS platforms. In order to further amplify the performance test, we introduced this Player Demo, which plays a 4K video. Take a look at the performance differences across the three platforms. Similarly, to reduce the impact of each platform, I used the built-in VideoPlayer framework for all three platforms, including Flutter, AVPlayer for IOS, and VideoView for Android.

In the device area, given the lack of money, I went back and forth to find two similar old phones: an iPhone 6S Plus and a Google Pixel. The former has been with me for two or three years and is still in service, while the latter has just acquired a new plane.

OK! Everything is all right, now we’re starting to generate various packages, for IOS we’re running directly on the phone; For Android, we will release the official package of Flutter for Android. We refer to the official document Build and release for Android.

Note that the first Build of Flutter on The Android platform will take a lot of time. This is not the same as the original second Build. This is mainly because Flutter is a three-party dependent Build that takes a lot more time:

First let’s look at the Timer:

From left to right there are Android native, Flutter for Android, IOS native, and Flutter for IOS (unless otherwise specified, the following legends are arranged in this order). Although Flutter provides us with a Flutter Performance to monitor Performance:

However, the tool does not work on native Android and IOS platforms and lacks comparability. Because Flutter is essentially an Android or IOS app after being compiled and run, The Android Profiler and XCode Instruments are used to measure Flutter performance. It simply records the CPU and memory consumption of applications running on each platform.

The IOS platform will debut first, including a native IOS Timer and a Flutter for IOS Timer. In order to shield the performance impact caused by the newly launched application, the Flutter should wait for a period of time after the application starts to stabilize CPU and memory before the test:

First let’s take a look at the rendering of native and Flutter on iPhone. The image above is taken directly from the screen without any color correction. By default, the background color of Flutter is slightly magenta. I suspect that this is because the default Flutter project uses a Material blue color:

primarySwatch: Colors.blue
Copy the code

To be more precise, sky blue (see the title bar and FAB colors), the part of the color ring near green that is blue, probably because Material designers felt that adding a complementary white color to the background would enhance the visual impact (because That’s how I like to design my UI). So the default background color of the Flutter project has a slight magenta tinge. Developers may think this is a bit of farting, but in the designer’s mind, any slight change in color values can affect the user’s subjective experience, and it does. But if you don’t feel the need, you can change the background to white by yourself:

backgroundColor: Colors.white
Copy the code

But here’s what I find puzzling: Even so, Flutter renders a slightly grayer “white” color than the original IOS version:

Maybe the color space is different, or maybe the Rendering engine of Flutter doesn’t feel comfortable on the iPhone with its own son’s CA/CG rendering framework… Additionally, Even when idle, Flutter requires much more memory than native IOS and starts up 13 threads on the iPhone by default. Click the FAB button to start counting:

For 10 seconds or so, the CPU usage of Flutter was approaching 40%, while the IOS native was only approaching 3%. The IOS native had little change in memory, while the Flutter used nearly 20M more memory. Fortunately, thanks to Dart’s excellent asynchronous framework design, the Stream does not generate new threads during this process. But in the end, because the IOS package generated by Flutter is not an official package, it will affect the performance of Flutter. Therefore, we will only use Flutter as a reference on IOS.

Happily, Android doesn’t have the same chromatic aberration as IOS, and both render the same, probably due to Skia:

In terms of performance, Android’s native CPU usage fluctuates widely, peaking at 15%, while Flutter is relatively good, peaking at 10%. In terms of memory usage, Flutter is about twice as expensive as the native one. As we mentioned above, Flutter is a tripartite dependent component, so there may not be a good solution to memory usage for a long time.

This is the end of the test of The Timer Demo. We can see that Flutter’s performance on Android platform is almost the same as that of native IOS. On IOS, although there is no money for a large Apple membership, my experience in Debug mode is at least the same as that of native IOS. But performance-wise, Emmmmmm… I will retest another Release after I recharge my Apple membership. However, regardless of IOS or Android platforms, the memory footprint of Flutter generated apps is much higher than that of native apps. As I said, this will probably be around for a long time, so you need to make a bit of a trade-off when introducing Flutter into a formal project. The reason why The Flutter platform does not use Dart’s true thread Isolate for counting is that Dart’s asynchronous framework supports Future and Stream rather than directly Create Isolate. Finally, let’s look at the performance of 4K playback.

The video is about 250MB in size and less than 2min in length. Let’s take the 50s video and compare it to see its performance in 4K.

The first is Android:

Thanks to EXOPlayer, the Flutter experience is quite good when playing. Android native because of the direct use of VideoView, the Flutter has a bit of a lag at the beginning of playing, but the subsequent performance is ok. The performance of Flutter is still a problem. The CPU and memory of Flutter are very high. Android native because the MediaPlayer in VideoView relies on calls to the underlying PlayerDriver, the CPU and memory usage of Flutter may be skewed. I do not discuss it in this article. However, in terms of the user experience, the flow of Flutter application is as good as or even better than native application.

Finally, let’s take a look at IOS:

conclusion

Generally speaking, Flutter, Google’s “UIToolkit”, has perfected its intended function well over the past two years. But there are several problems:

First, the range of applications to the production environment is not wide enough. As Flutter currently behaves, it is fully capable of most UI-level scenarios. However, it is not suitable to use Flutter when frequent interactions with platform-specific features are required. Although Flutter provides channels to interact with native code, it is currently inconvenient. Therefore, if you plan to apply Flutter to your production environment, Ai prefers a hybrid development solution like Idle Fish, that is, keep native development for important or platform-specific interfaces, and use Flutter development for minor purely UI-rendered interfaces. Additionally, games, especially those that require 3D rendering, do not support Flutter and do not need to use Flutter.

Second, as we have seen above, the main performance difference between Flutter and native is in memory consumption. Flutter uses more or even much more memory than native. Therefore, if your application is sensitive to this area, such as for low-end One devices, The use of Flutter is not suitable for the time being. Also, as a tripartite component, Flutter inevitably increases the package size:

This is a comparison of the package sizes generated by Android native and Flutter. Even if you remove the SO of different CPU architectures, the package size will be larger than that of native.

Finally, there is the ecological question, which has two parts. One is the technology ecosystem. Flutter does not have as many mature framework components as the native platform, and there are certain efficiency barriers to development. The second is the product ecosystem. There are few mature products developed using Flutter, and as a result, some of the AD revenue SDKS that developers rely on for their livelihood provide little direct support for Flutter.