background
This happened recently. The crash data of the newly launched iOS version of our app (Gao Ding Design) has soared. According to crash logs and user feedback, most of the new crashes come from the same reason: insufficient memory. Some directly become OOM, which is difficult to troubleshoot. Others fail to apply for memory, resulting in subsequent logical errors. Considering the situation of "blooming everywhere and exploding at many points", it should be some kind of low-level memory management problem. This is a bit puzzling, because this version did not make any memory-related changes. So I took a dichotomy and spent two hours trying all the PRs in the version, and found that the culprit was the Flutter version upgrade: 2.5.3 → 2.10. So the question becomes: what changes did Flutter make in 2.5.3 → 2.10. that caused the memory crash problem? Analyze the problemBased on user feedback, we found an operation path that will inevitably cause a memory crash, so I tried to test the memory situation in Flutter 2.5.3 and 2.10.5: By comparing the memory situation, we can draw a conclusion: before the upgrade, the memory tolerance was higher, and there was no problem with a peak of 1.2G; after the upgrade, the memory tolerance was lower, and it would crash at a peak of 1.1G. This reminds me of "compressed memory": when the memory is tight, the iOS system will compress some unused memory to free up memory space. When you need to read this compressed memory, you also need to decompress it before reading it. Why does this mechanism, which sounds good, go wrong? Here is a classic example:
So, I followed the clues and searched for a few keywords in Flutter’s issues: iOS compress memory. The first post [2] confirmed my guess: Several key points are mentioned in the article:
Considering that we upgraded from 2.5.3 to 2.10.5, we can basically pinpoint the issue as compressed memory. Two optionsThere are currently two solutions:
The cost of modifying the Flutter Engine source code is actually very high. You need to understand the dependencies between Flutter Engine and Flutter, the construction method, the Flutter Engine code logic, and so on. We originally wanted to wait for solution 1, but as more and more backend user feedback became available, solving the memory-related crash issue became urgent, so we decided to adopt solution 2.
So I, who had never written a line of Flutter code, just took the plunge. After reading countless official/private documents, I spent three days and finally figured it out. I added custom printing to the Flutter Engine: How the specific solution 2 solves the problem is described in detail below. Coincidentally, just as we were solving the problem with Solution 2, Solution 1 also saw the light of day: Flutter urgently released version 3.0.5, in which Flutter Engine disabled memory compression. So we immediately upgraded and tried it, and it really didn’t crash. We made some adjustments and went online. According to online data feedback, the memory crash problem has been perfectly solved. Flutter Engine customization and source code debuggingNext, we will introduce the operation process of Solution 2 in detail. First, let’s take a look at the flowchart: Download source codeI checked the official Flutter documentation [3] and found that there is only one page of documentation when downloading the source code. You can imagine how deep this pit is. Fork the Flutter Engine repositoryOpen github.com/flutter/eng…[4] and fork a copy to your own repository. For example, mine is github.com/JPlay/engin…[5]. The purpose of fork is to have a place to store the modified code after modifying the source code. (Here, fork is enough, no need to clone) Install depot_toolsdepot_tools[6] is a tool set provided by Google to manage project code. It contains many packages. Here are the ones we will use:
Start installing depot_tools: $ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git After the installation is complete, add the following command to ~/.bashrc or ~/.zshrc to set depot_tools as an environment variable for subsequent use: export PATH = / path / to / depot_tools : $ PATH Pull source codeUnlike usual when we use git to pull directly, we must use gclient here because there are many dependencies that can only be pulled down by gclient. We first create a new folder called engine (the name is arbitrary), and the subsequent source code will be placed here. Create a new configuration file in engine, the name must be .gclient, and use a text editor to add the following content: solutions = [ Here is the value of url:
You can find it in /bin/internal/engine.version in your Flutter directory, for example, mine is: $ cat / Users / JPlay / development / flutter / bin / internal / engine.version After completing the configuration, attach the agent and you can pull the code in the engine folder: $ gclient sync - - verbose It must be emphasized here that the code here exceeds 10GB and the process is quite slow. If there are any errors or jams during the process, it is basically a network problem. It is recommended to carefully check the logs. Most of them are failures in cloning a repository or accessing an address. It is recommended to use git clone or curl to try whether the network is smooth. PS: My first agent was able to pull most of the code, but a small part of the code could not be pulled down, which wasted most of my time. Later, I changed an agent and it was successfully pulled down. After success, you will find that all the code is concentrated in the engine/src directory, similar to this: If you want to switch the engine branch later, you can first enter /src/flutter and then execute: $ git reset - - hard < commit id > CompileNext is compilation, which we will do in two steps:
It is worth mentioning that since Flutter's compiled products are platform-specific, we currently mainly need iOS and Android, which can be done on macOS. When compiling iOS/Android products, you also need to compile a host product. This is because we need to compile a Dark SDK corresponding to the current version. Because the code version, target platform, and target architecture are not unique, the iOS arm64 target is used as an example. Please imitate other situations as appropriate. Creating Compiled Materialsgn provides a bunch of parameters to help us create compilation materials: usage : gn [ - h ] [ - - unoptimized ] [ - - enable - unittests ] Here we will use a few:
For detailed instructions, enter: /path/to/gn --help. We create a compilation material for iOS debugging in the src/ directory: $ ./flutter/tools/gn--runtime-mode=debug--unoptimized The first line generates host materials, and the second line generates iOS materials (no input architecture, the default is arm64). So two new folders are added under src/out/ , which are the compilation materials: Execute CompilationNow that the materials are ready, we are going to start compiling. If you are using a Mac with Intel CPU (x64 architecture), everything will go smoothly. Just execute the command: $ ninja - C out / ios_debug_unopt & & ninja - C out / host_debug_unopt However, if you have an M series Mac (arm64 architecture), you need to do some work (I guess everyone is ¬_¬):
3. Modify /src/third_party/dart/runtime/BUILD.gn. The above modifications are all to solve the problem that "the build script considers the compiled host machine to be x64 architecture by default", and the modifications we made are to adapt to the arm64 architecture. Since the compilation script is frequently updated, the above modification scheme may only be effective for the current commit, but I have summarized some experience to facilitate everyone to modify the script:
Modify source codeIf everything goes well, we have passed the compilation stage. Now we can modify the source code. I will give an example here just to prove that we have successfully modified the source code: Add a print message to the Run method of /src/flutter/shell/common/engine.cc . This will cause the engine to print this message when it starts. Don't forget our original intention: turn off iOS memory compression in /src/flutter/tools/gn to solve memory problems: After the modification, recompile: (this is an incremental update, very fast): $ ninja - C out / ios_debug_unopt & & ninja - C out / host_debug_unopt Next, enter a Flutter project directory and execute: $ flutter run - - local - engine - src - path = / path / to / engine / src / - - local - engine = ios_debug_unopt You can see the console output: The application runs successfully and outputs our customized information. So far, we have achieved a phased success and have successfully run our modified code in the Flutter project. Source code debuggingThe Flutter official documentation [14] provides a very complete description of debugging. I will only give an example of Xcode source code debugging here. We open a Flutter project, for example, Runner.xcworkspace, since we just ran: $ flutter run - - local - engine - src - path = / path / to / engine / src / - - local - engine = ios_debug_unopt Therefore, the Generated.xcconfig file has already been set with the relevant parameters (if not, set them yourself): Then drag /src/out/ios_debug_unopt/flutter_engine.xcodeproj to the Runner project: Find a place where the program will run and set a breakpoint, such as the init method in FlutterAppDelegate.mm. Run the project: The breakpoint is successful, and then you can debug happily. SummarizeThis troubleshooting is really like a case-solving process, finding clues bit by bit based on clues, and finally solving the problem. Although there were many pitfalls in the process, I still felt a sense of satisfaction in reasoning and solving the case all the way to the end. I would like to share this with you, hoping that it can help you solve the same memory problem. References[1]SDWebImage: https://github.com/SDWebImage/SDWebImage. [2] First post: https://github.com/flutter/flutter/issues/105183. [3] Flutter official documentation: https://github.com/flutter/flutter/wiki/Setting-up-the-Engine-development-environment. [4]github.com/flutter/engine: https://github.com/flutter/engine. [5]github.com/JPlay/engine: https://github.com/JPlay/engine. [6]depot_tools: http://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up. [7][email protected]: mailto:[email protected]. [8]gn and ninja: https://zhuanlan.zhihu.com/p/136954435. [9]https://ninja-build.org/: https://ninja-build.org/. [10]https://github.com/flutter/flutter/wiki/Flutter%27s-modes: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fflutter%2Fflutter%2Fwiki%2FFlutter%2527s-modes. [11]gn: https://chromium.googlesource.com/chromium/src/tools/gn/+/48062805e19b4697c5fbd926dc649c78b6aaa138/docs/language.md#GN-Language-and-Operation. [12]Python: https://www.runoob.com/python/att-string-format.html. [13] flutter/issues/96745: https://github.com/flutter/flutter/issues/96745. [14]Official documentation: https://github.com/flutter/flutter/wiki/Debugging-the-engine. |
<<: Android development board serial communication - in-depth analysis and detailed use
>>: Attention! There are only seven days left for iOS 15.4.1. Will you choose to upgrade?
It’s mango season! Mango has a unique flavor and ...
"5 times" is a magical number. Users wh...
This article mainly introduces how to improve the...
If new energy vehicles start charging tolls, woul...
Regarding Sina Weibo , let me first show you thre...
Author: Wang Lu, Chinese registered dietitian, Ma...
[[287287]] Keep alive status We know that the And...
With the development of the Internet, traffic has...
The smartphone industry has always had a growth m...
After the Anshi Rebellion, people could only dream...
Large-bandwidth servers have occupied more and mo...
1) Good Project III) Course Introduction 01. The ...
What little-known facts about mobile phones do yo...
2022 College Entrance Examination Today officiall...
Keep-alive implementation principle This article ...