The main content of this paper is collated from “Cross-platform Practice of wechat Client Based on Small Program Technology Stack” shared by GMTC 2019
https://gmtc2019.geekbang.org/presentation/1711
One, foreword
Since its birth, after more than two years of development, small program has become the most vital part of the wechat developer ecosystem, opening up a new imagination space for external developers. However, the changes brought by small programs are by no means limited to wechat. How does the establishment of small program technology stack affect the development of wechat client?
Ii. Cross-platform practice of wechat client
As early as 2012, the wechat client team began to use cross-platform technology for research and development. In order to deal with the problem of inconsistent code logic of multi-platform client at the beginning, it has been trying to develop cross-platform solutions for subsequent business and UI development.
The earliest cross-platform component is MMNet developed on the basis of C99. In October 2012, the basic network component was created in order to solve a series of inconsistent problems in multi-platform clients. After continuous iterative optimization, especially in dealing with weak networks, the in-depth optimization was made. In addition, various network policies such as security and disaster recovery are added. The common partial logic of MMNet was open sourced on Github in 2016 under the Mars name and has gained wide acceptance in the industry, completing an internal experiment of a cross-platform component and ultimately sublimating it into an open source project available to all. Similar cross-platform components like WCDB and MMKV are also popular on Github.
After cross-platform for the base components, cross-platform efforts for business and UI development followed. In order to face the rapidly changing internal innovative business, the wechat client team has to seek the development mode of rapid iteration on multiple terminals. In the process of business development, is it possible to write the code once and get a consistent experience of THE UI function on multiple ends like using basic cross-platform components?
After trying different solutions, we turned our attention to small programs. Within two years of the rapid development of wechat mini program, each internal business team began to develop innovative business based on the mini program. With the help of wechat small program framework, these businesses can obtain the advantages of short development cycle and fast online compared with pure native client, and at the same time can meet strong operational requirements. This business development mode based on wechat small program is gradually recognized internally.
We believe that a good cross-platform development model has four goals:
-
Reduce platform differences: the development differences on different platforms should be minimized to reduce the development burden of each platform as much as possible;
-
Improve R & D efficiency: From the perspective of R & D efficiency, the efficiency of developers in the development process should be improved as much as possible, including coding, debugging, operation, testing and other links;
-
Native performance and experience: The final research and development products should have the same performance and user experience as the development of sub-platform native technology, so that users can not perceive the gap;
-
Easy to learn controllable technology stack: the cross-platform technology stack should have a good learning curve, so that more students can learn and master it quickly. Moreover, it should be controllable and safe technology stack from both technical and business perspective.
Does the applets stack meet these requirements?
Three, small programs and wechat client
Wechat small program uses the former end of the technology stack based scheme, the framework above the flat many platform differences, at the same time the business can also be dynamically deployed and updated at any time, and the experience and performance is relatively close to the original. With the development of the applets ecosystem, more and more rich plug-in extension mechanisms, custom component mechanisms and third-party development frameworks have emerged. At the same time, small program as the wechat team internal independent research and development framework, small program has been a very good cross-platform framework, to meet the general business development is no problem.
However, when we start with the topic of “small program technology stack as client cross-platform development technology” and focus on some of the details, we also find problems.
The nearby restaurant is a similar native experience business developed by the wechat team based on small programs. Through the small program to achieve a development run on iOS, Android two client functions. The whole development process is mainly based on wechat small program development tools and development standards, with the client to achieve some additional capabilities, after the completion of the basic function we also found some problems on the Android platform, here are two typical examples.
The first is font consistency. Wechat applet uses WebView rendering, which has two different view rendering systems from the native client. On The Android platform, there is a problem that the fonts cannot be kept consistent with the system, resulting in an obvious sense of fragmentation in the experience.
Second, in a large number of pictures and videos mixed scene, there will be some frame drop phenomenon, in the Android medium and low-end phones more obvious. As shown in the figure below, LAG occasionally occurs in continuous processes such as picture sliding. And limited by the current small program framework, video, picture full screen display effect is not ideal.
It is because there are still some unsatisfactory experiences and performance of wechat small program framework in the face of complex business scenarios. Although the performance and experience are close to native, they still cannot achieve the effect of native experience. We decided to try to optimize these details step by step.
Let’s take a look at the current system architecture of applets.
4. Cross-platform development based on small program technology stack
The system architecture of wechat small program is believed to be familiar to most readers today. Generally speaking, it is divided into two parts:
-
The View side processes the UI information described by users using WXML and WXSS into H5 elements through the framework of small program, and finally gives it to WebView to render;
-
The App Service side runs JavaScript logic written by the user, and can call JSAPI with wechat open capability. Logic and view are separated and related to each other through events and data.
Going back to our question above, it is difficult to achieve the simple design experience of native view architecture on low – and mid-range computers and more complex businesses due to the large and complex architecture of the Web. Is it possible to use the platform’s native view rendering system to solve this problem?
1. Optimization based on native rendering
In principle, we can convert the UI described by the user into the system’s native components, which has been practiced in the industry for a long time. Inspired by frameworks like ReactNative, we made some modifications to the view side of the small program. On the Android platform, we dump the Virtual DOM information and all CSS styles in the applets framework, and parse the native components one by one in the Java layer. However, the native architecture is not fully capable of expressing overly complex CSS styles, so only partial WXSS features were supported in the early stage.
2. LV-CPP
In our preliminary scheme, too many implementations were made in Java at the beginning. Considering platform compatibility, we separated the original implementation of PARSING DOM and CSS styles into an independent cross-platform module in order to facilitate porting to other platforms and replace the rendering module at a lower cost. Finally, the lv-cpp module implemented by C++ is selected. Lv-cpp is used to do the cross-platform UI system processor for small programs, complete DOM and CSS parsing and layout calculation, and perform JS functions by V8 or JSCore.
When the UI described by WXML/WXSS changes, the small program Front End common library (WXA Framework) computes internally and submits the results of the Virtual DOM tree Diff to LV-CPP in the form of operation instructions. After receiving the instruction, LV-CPP updates the corresponding nodes and performs CSS matching, CSS attribute conversion and layout calculation. After the calculation, Native View is called for interface rendering.
The CSS currently supports ID selectors (# ID), tag selectors (button), class selectors (.class), and combination selectors (A,B, A B, A>B, A+B, A~B). In order to improve performance, the combination selector matching uses WebKit’s reverse resolution scheme.
The reason why CONVERSION of CSS attributes and layout calculation are carried out in LV-CPP is to try to smooth out the differences in attributes and layout caused by different rendering modules. The most typical is color conversion. There are various ways to represent colors in CSS. The most common ones are:
-
Hexadecimal color, for example: #0000ff
-
RGB colors, such as RGB (0,0,255)
-
RGBA colors, such as RGBA (255,0,0,0.5)
-
HSL colors, e.g., HSL (120,65%,75%)
-
HSLA colors, e.g. HSLA (120,65%,75%,0.3)
-
Color name, for example, black
These different kinds of color representation methods, after LV-CPP calculation output is all decimal color values, and then handed to the rendering module for rendering.
Using native components does provide a decent improvement in experience and performance. On the Pixel 2 XL, we measured a 27.5% improvement in frame rate over WebView and a 14% to 23% reduction in memory. However, as we were about to extend this solution to all platforms, we realized that it would be a huge workload to adapt to each platform, and the subsequent maintenance costs would be unpredictable.
Web-based rendering cannot meet the performance and experience requirements, and native rendering will bring high maintenance costs, so we need a cross-platform rendering solution to solve the problem. Flutter came into view again while studying various possible solutions.
3. Flutter
Flutter is a high performance application framework created by Google for cross-platform, which has attracted the attention of many peers. However, according to our goal of wechat cross-platform development, Flutter does not fully meet the requirements. Using Dart development will cause additional learning costs for existing developers. So Flutter was not initially considered a candidate for client cross-platform development.
But when our question was reset to “looking for a cross-platform high-performance rendering framework”, Flutter began to show its advantages. As can be seen from some classic Benchmarks, the Flutter has a very good level of performance.
This set of data is the comparison data of Java, Dart JIT and Dart AOT measured on the ARM platform. The higher the value, the better the performance. Another interesting fact is that the performance of Flutter gets better and better as Flutter versions improve, indicating that Flutter developers are constantly optimizing Flutter performance.
It is possible to conclude, however, that Dart outperforms JavaScript and is comparable to Java, too, by Benchmarks Game.
Read the official description of Flutter:
-
Rapid development: The thermal overload of Flutter allows you to quickly test, build UI, add features, and fix bugs faster.
-
Expressive and beautiful user interface: Material Design and Cupertino (iOS style) widgets, rich Motion API, smooth and natural sliding effects.
-
Responsive Framework: Easily build your user interface using Flutter’s modern, responsive framework and a series of basic widgets.
-
Access to native features and SDKS: Flutter can reuse existing Java, Swift or ObjC code to access native system features and system SDKS on iOS and Android.
-
Unified app development experience: Flutter has a wealth of tools and libraries that help developers easily implement ideas and ideas on both iOS and Android systems.
-
Native performance: Flutter contains many core widgets, such as scrolling, navigation, ICONS and fonts, that can perform as well as native apps on iOS and Android.
Based on a series of evaluations, we felt we could give Flutter a try. Therefore, we propose a small program framework rendering optimization scheme based on Flutter.
4. Optimization based on Flutter rendering
We replaced the rendering part of Flutter with the native components of the platform, still supporting only streamlined WXML and WXSS.
Under this architecture, lV-CPP of Layout layer is specially used as the UI system processor of small program, and the Layout of UI information is calculated and submitted to the abstract back end for rendering. Lv-cpp is used as the frame of small program and the middle layer of renderer. Focus on addressing complex web-related features at the C++ layer. The renderer can then focus on converting elements into UI components based on specific protocols and interfaces, and eventually draw them out.
By combining Flutter and lv-cpp, we converged the implementation code on C++ and Dart, further simplifying the maintenance cost of the framework for cross-platform business development based on the small program technology stack.
However, there is a lot more thought and optimization to actually implement.
5. Communication problems
The applets framework is implemented using JavaScript plus some platform-injected interfaces that run in the JS Engine environment. The Layout layer is implemented by C++, how to solve the communication problem between JavaScript and C++? After the lv-cpp has calculated the layout at the C++ layer, how does it pass this information to the Dart environment that renders the back-end Flutter? To ensure the performance of the framework, we must solve two problems.
A. JS communication
Based on Android WebView system can inject a JavaScriptInterface in Java layer through the interface provided by WebView, JS can get an extended API, when called by V8 finally reflected to Java above.
It’s a similar implementation on iOS. First, it brings platform relevance. The second is the long call path. In this regard, JS Binding was finally adopted, and the original platform-dependent implementation was directly lowered to C++ to realize the extension of JS objects, which could not only solve the cross-platform problem but also improve the performance.
B. Flutter communication
The Flutter official provides a Platform Channel solution for Dart and Platform communication. The main idea is to encode the passed data as a message, send it across threads to the platform interface layer, process it and then route the returned data back in the same way. The communication efficiency of this method is not high due to message-based and cross-thread processing. We tested a group of data on snapdragon 845 machine and only completed about 4000 mutual calls through Platform Channel in one second.
Therefore, we modified the Flutter Engine by adding a dart2cpp module that exposes some C++ interfaces so that external dynamic libraries can call dart interfaces based on these interfaces via DartVM. Dart’s environment allows C++ and Dart to call each other’s interfaces as if they were their own. Dart is compiled to machine code in AOT mode, so C++ and Dart calls are very efficient. Instead of encoding the data into messages and a complex series of processes across threads, you manipulate the data directly on the memory stack.
How much is dart2CPP better than Platform Channel’s solution? In the same test case, dart2CPP can complete more than 300,000 calls to each other in one second, which can be said to greatly improve communication efficiency.
C. Dart2CPP implementation principle
DartVM provides a mechanism to use native keywords in Dart code to indicate that a C/C++ interface is being called.
// Dart sample code
bool systemSrand(int seed) native "SystemSrand";
Copy the code
However, the C/C++ interface must be registered with DartVM first, otherwise symbols cannot be found.
DART_EXPORT Dart_Handle
Dart_SetNativeResolver(Dart_Handle library,
Dart_NativeEntryResolver resolver,
Dart_NativeEntrySymbol symbol);
Copy the code
Dart can be registered using Dart_SetNativeResolver. During Dart, Dart_NativeEntryResolver will use the registered Dart_NativeEntryResolver to find the C/C++ function address based on the function information.
You can call an extended C/C++ function directly from Dart, but that’s not all. Dart’s memory model is different from C/C++. Dart calls to C/C++ use Dart_NativeArguments to describe the arguments and function returns. The Dart_GetNativeArgument/Dart_SetReturnValue interfaces are used to get arguments and set return values from Dart_NativeArguments.
// C++ sample code
void SystemSrand(Dart_NativeArguments arguments) {
Dart_EnterScope();
bool success = false;
Dart_Handle seed_object = HandleError(Dart_GetNativeArgument(arguments, 0));
if (Dart_IsInteger(seed_object)) {
bool fits;
HandleError(Dart_IntegerFitsIntoInt64(seed_object, &fits));
if (fits) {
int64_t seed;
HandleError(Dart_IntegerToInt64(seed_object, &seed));
srand(static_cast<unsigned>(seed));
success = true;
}
}
Dart_SetReturnValue(arguments, HandleError(Dart_NewBoolean(success)));
Dart_ExitScope();
}
Copy the code
D. Cpp2DART implementation principle
Dart: Dart: Dart: Dart: Dart: Dart: Dart: Dart: Dart: Dart: Dart: Dart: Dart: Dart: Dart: Dart
DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
Dart_Invoke(Dart_Handle target,
Dart_Handle name,
int number_of_arguments,
Dart_Handle* arguments);
Copy the code
You can find a set of apis in dart_api.h that allow you to manipulate Dart interfaces and even variables at the C/C++ layer.
With these basic apis, Dart and C/C++ can basically call each other, but you probably still need to know some of the execution mechanisms of DartVM to make your code work.
The Dart_EnterScope/Dart_ExitScope APIS are used in the C/C++ example above. Dart objects held in C/C++ are actually described by Dart_Handle. Many of the variables we create inside a function are local variables that should be freed when they leave Scope, so the concept of Scope tells DartVM that what is currently being created is local variables and that the memory used here should be reclaimed after ExitScope.
Of course, another important concept is the Isolate. The Dart code runs within an independent Isolate. The main Isolate is usually parasitic on the UI Runner thread within the Flutter system. Dart must be called on the INTERFACE of THE Isolate in C/C++, otherwise exceptions will occur.
There was a lot of detail involved and a lot of tedious API calls, and for the average developer who was just going to call an external interface and probably didn’t know the technical details, that’s why we developed something like Dart2CPP, Allows developers to write Dart and C/C++ code without having to worry about the details of how data is passed, Scope, and Isolate.
We also do not want the business dynamic library to be bound to the Flutter Engine dynamic library. They can be independent of each other. When needed, only the Dart interface can load the dynamic library. Dart and the external dynamic library can then make C/C++ calls to each other by registering their information with the Flutter Engine.
e. js2dart
As for these two solutions, there is more room for imagination. Since JS and C++ can call each other, and C++ and Dart can call each other, together they can indirectly get through JavaScript and Dart. While JavaScript and Dart have their own execution environments and mechanisms, it’s still possible to build an efficient channel through the C++ bridge, with references and some transformations (similar to JNI) to do most of the calling and data passing.
Additionally, Flutter does not provide official support for Hot Patch deployment, but there are a lot of things that Flutter can do with jS2DART, which is beyond the scope of this article.
At this point, there is a relatively efficient solution to the call communication problem in different language environments.
6. The overall architecture of a small application optimized for Flutter rendering
Take a look at the overall architecture of the applets so far. App Service side still maintains the original structure, processing the JavaScript logic written by the user; The view side (PageView) is re-divided into four levels, in addition to the original UI DSL description (WXML/WXSS), small program front-end public library (WXA Framework), There is also a UI Layout+ that is dominated by LV-CPP and a Renderer that is implemented by Flutter.
UI information described by simplified WXML/WXSS is processed into DOM description through common library of small program front end, and passed to LV-CPP through JS Binding interface to parse CSS and DOM nodes (Layout+). After completing the layout calculation, lV-CPP sends the element information to the Flutter end via dart2CPP interface. The Flutter Framework layer directly describes the calculated elements as rendering nodes and gives them to the Flutter Engine to draw.
Overall we have convergent code to JavaScript, C++, and Dart, so there is a significant reduction in additional overhead on the cross-platform side. The developers of small programs will not bring any change, for developers is still the original small program technology system.
7. From RN-like to Flutter rendering
From the initial RN-like solution to the research on Flutter solution, they are essentially just constantly solving the problems we encounter. Compared with Web, the solution experience and performance are improved, and the platform maintenance is also solved.
Summarize the problems that Flutter renders solve, and basically meet our performance and experience requirements:
-
Font inconsistency problem: Implement a custom Flutter Engine to follow the system’s native view fonts.
-
Render videos, maps, etc. : The official Flutter provides a mechanism to synchronize the Native platform’s Texture to the Rendering system of the Flutter via Texture Widgets, ensuring that there is only one view system on the Flutter interface at any one time.
-
Text input field: The Official Flutter provides a complete input field control.
-
Performance improvement: Compared with WebView, there is a visible performance index improvement on low-end machines;
-
Reduced duplication of resources, multi-platform maintenance: basically only Dart and C++ code is maintained, and platform-dependent code can be minimized.
Of course, there is still a lot of room for improvement in performance at the present stage. Compared with the performance indexes of RN-like scheme, the characteristics of Flutter need to be fully utilized to improve the overall usability of this framework.
Note: Due to the rapid change of the plan in the development stage, the comparative data here are not measured under the same equipment, and only the relative WebView rendering improvement is taken as an example to illustrate.
V. Summary and prospect
Review context, WeChat on the client side cross-platform development plan to explore from the earliest to build the basis of high quality, open source components, to now try to explore big business cross-platform development scheme of front-end technology stack, always from improving team effectiveness and the final product user experience two perspective, to think about how to continuously improve the level of research and development of mobile technology. Taking our eye back to this fundamental starting point, the rendering solution we share today is not necessarily the only optimization option for applets as cross-platform development. Is there really no breakthrough in WebView rendering? Cross-platform development only has big front-end options? With the continuous development and deepening of big front-end technology, we believe that new technical solutions will continue to emerge in the future to solve the problems in the existing RESEARCH and development process, and welcome you to continue to pay attention to our latest progress.
Q & A
After the sharing of GMTC 2019, we have received many questions from students, and we have sorted out some representative questions here.
Q1. Will there be any changes in the product for applets? Will WebView rendering be abandoned in favor of Flutter rendering?
A1. Wechat applet is an independent ecology and product. WebView rendering has great flexibility and front-end compatibility, and will not give up WebView rendering. At present, our attempt is limited to some internal scenarios of wechat client, which will not have any impact on external developers of wechat applets.
Q2. How does this scheme using Flutter render perform when it encounters complex CSS properties?
A2. We do not support CSS properties that are too complex. The CSS currently supported on LV-CPP is a small subset (internally we call it “WXSS-Lite”) and does not support full CSS properties from a performance and complexity perspective. For internally developed services, wXSS-Lite support will be determined based on performance, complexity, and necessity, which may significantly limit the CSS properties that can be used by internally developed students.
Q3. Does the JS2DART module support passing objects and custom data, and is it open source or open for everyone to use?
Dart and A3. JS have their own execution mechanism and object model, so it is not possible to pass objects directly, and in fact it is not necessary. However, object mapping can be solved by reference or other data structures, and custom data structures can also be done over certain protocols. You can even transfer large chunks of data without a problem based on shared memory schemes. In terms of open use, we are also considering it, but the specific way is still under discussion. We hope our solution can bring broader imagination to the majority of developers.
Q4. Access to iOS will bring about changes in package size and the inability to Hot Patch. How do you access iOS?
A4. We started from the Android platform at first, and the access of iOS will be a little late. According to our actual research, iOS students are concerned about development tools, package size and dynamics, etc., and we plan to do some research in these aspects later. To discuss some solutions with iOS students, and also hope that everyone actively embrace the new technology and share their solutions in the community. But anyway, the purpose of using new technology is to solve our problems, as long as it is good for us, we will continue to follow up.