One, a brief introduction
For the construction of Flutter applications, the development language Dart, VIRTUAL machine and construction tools are different from those of our Native applications, and the platform virtual machine does not support them. Therefore, Flutter SDK is needed to support Flutter applications, just as Android SDK is needed to build Android applications. Download the Flutter SDK in one of two ways:
- Download the built ZIP package from the official website that contains the complete Flutter base Api, Dart VM, Dart SDK, and more
- Manually build, Clone the Flutter source and run it
flutter --packages get
Or other commands with detection types such asbuild
,doctor
The Dart SDK and the Flutter engine artifacts are automatically built and downloaded
Depending on the local download of the Flutter SDK by each developer, this method does not guarantee the consistency and automatic management of the Flutter SDK versions. If the Flutter SDK versions are inconsistent during development, Dart layer Api compatibility or Flutter virtual machine inconsistencies occur because each version of a Flutter has a corresponding Flutter VIRTUAL machine, and the build product contains the corresponding build virtual machine. The construction of a Flutter project requires a directory of the standard Flutter engineering structures and depends on the local Flutter environment. Each corresponding Flutter project has a corresponding Flutter SDK path. In the Generated. Xcconfig file, this path is read when the Native project relies on the build of the Flutter project to obtain the engine, resources and build the Flutter project. When a flutter command is invoked to build the flutter project, the flutter SDK path where the current flutter command is located will be obtained, from which the engine, resources and build the flutter project will be obtained. Therefore, the build environment of the FLUTTER command must be consistent with the environment variables of the platform project of the FLUTTER project, and this environment variable must change dynamically with the flutter execution. There should also be a corresponding Build of Flutter for each release of the packaged Flutter project, either locally or on the packaged platform
We know that the engineering structure of Flutter application is different from that of Native application engineering. The main difference is that Native engineering is a subproject of Flutter engineering, and the outer layer is managed by Pub. In this way, the dependencies of the Flutter Plugin/Package code can be shared with multiple platforms. When the Native sub-project is packaged, only the engineering code and the platform code of the library that Pub depends on can be packaged. The Flutter project uses Flutter_tools to package Dart code in lib directories and libraries that Pub depends on. To return to the topic, due to the differences in engineering structures, if one of the functional modules of Flutter is developed based on the existing Native engineering, generally the hybrid development must ensure at least the following characteristics:
- No intrusion to Native engineering
- Zero coupling to Native engineering
- It does not affect the development process and packaging process of Native engineering
- Easy local debugging
Obviously, the plan to change the engineering structure can be directly ignored. The official government also provides a plan to rely on Flutter locally to the existing Native. However, if this plan relies on Flutter directly without changing optimization, the development of other students without Flutter environment will be directly affected and the development process will be affected. And the packaging platform does not support this dependent way of packaging
When we talk about the Flutter SDK, it is inevitable to change or customize the Flutter SDK directly due to the Bug of the Flutter SDK or the script code linked to the platform in the Flutter SDK. Although this method can solve the problem or customize the Flutter SDK, However, this approach is not recommended as it is not friendly to subsequent smooth upgrade of the Flutter SDK and incurs more maintenance costs later
Next, this paper mainly introduces how to solve and realize the above problems:
- Flutter SDK version consistency and automated management
- Without invading the Flutter SDK source code for BugFix or customization
- The Flutter hybrid develops a componentized architecture
- The Flutter hybrid development engineering architecture
Ii. Four engineering types of Flutter
Flutter engineering usually includes the following engineering types, which are briefly summarized as follows: The Application of the Flutter Module includes the Dart layer and the Native platform layer. The Application of the Flutter Module includes the Dart layer and the Native platform layer. The subengineering of the Native platform layer is the hidden engineering automatically generated by the Flutter. The Dart Package contains only the implementation of the Dart layer, often defining some common widgets
3, Flutter project Pub dependency management
The dependencies between Flutter projects are managed by Pub. The product of the dependencies is a direct source dependency, which is similar to Pod in IOS. Both dependencies can be interval qualified with library versions and Git remote dependencies. The dependencies are based on the syntax of YAML, a language specifically designed to write file configurations. Here is an example of remote dependencies via Git addresses:
dependencies:
uuid:
git:
url: git://github.com/Daegalus/dart-uuid.git
ref: master
Copy the code
After declaring dependencies, run the flutter Packages get name to pull the dependencies from remote or local sources and generate a pubspec.lock file. This file is very similar to IOS podfile. lock. The update will only happen when the flutter packages upgrade is executed. Also, the pubspec.lock file should be submitted to Git as a version management file, not gitignore
1. Pub relies on conflict handling
For Pub and Pod, this dependency management tool for conflict handling ability is much worse than Android Gradle dependency management, so when the same library version conflict, we can only handle manually, and with the expansion of the development scale, there will certainly be conflicts between the dependency transfer libraries
There are two main types of Pub dependency conflicts:
- The current version of the dependent library conflicts with the current Dart SDK environment version
- A library version inconsistency conflict occurred while passing dependencies
The first type of flutter packages get will report errors and indicate why the conflict occurred and what is the minimum required version as follows:
The current Dart SDK version is 2.1.0-dev.5.0.flutter-a2eb050044.
Because flutter_app depends on xml >=0.1.0 <3.0.1 which requires SDK version <2.0.0, version solving failed.
pub get failed (1)
Copy the code
This can be resolved directly by following the prompts to upgrade the version of the dependent library
The second one is A bit more complicated. If there are three libraries A, B and C, and both A and B depend on C library, if A version of A depends on C and B depend on C version is inconsistent, there will be A conflict. How to resolve this conflict? There are two ways
Mysql > alter database A; mysql > alter database B;
dependencies:
A: any
B: any
Copy the code
When the flutter packages get is passed, there will not be A conflict-free version error because Pub has automatically selected the version numbers of libraries A and B that make the C library version consistent. Open the pubspec.lock file in the corresponding directory to search for libraries A and B, and there will be corresponding conflict-free version numbers. Finally, replace the any version with the any version, and the version conflict is resolved
2. Solve the problem through version coverage
2. Pub depends on version coverage
In the Pub dependency management, since it supports passing dependencies, it also provides a version override method, which means to specify a version forcibly. This is similar to the Android Gradle Force. The same version override method can also be used to resolve conflicts, if you know a certain version will not conflict. Can be directly solved by version coverage:
Dependency_overrides: A: 2.0.0Copy the code
4. Flutter links to Native engineering principles
There is a way to rely on the existing Native project. Flutter local dependence, this method is too dependent on the local environment and invasion of Native engineering will affect other developers, and the packaging platform does not support this way of packaging, so it must be optimized and modified based on this method. I will talk about this later. First, the principle of local dependence at both ends of Native
1. Android
In Android, the local dependency mode is:
- in
settings.gradle
In the injectioninclude_flutter.groovy
The script- In the dependent module
build.gradle
addproject(':flutter')
Rely on
For Android local dependencies, include_flutter. Groovy and flutter. Gradle scripts are responsible for local dependencies and product construction of Flutter
1. include_flutter.groovy
When injected into settings.gradle, the include_flutter. Groovy script is bound to the context in which gradle is currently executed and does three things:
- In the include FlutterModule
.android/Flutter
engineering- In the include FlutterModule
.flutter-plugins
Contains the Android Module file in the Flutter project path- Configured for all projects
build.gradle
The configuration execution phase depends on:flutter
The project, that is, it performs the configuration phase first
The flutter-plugins file is automatically generated according to the current dependencies. It contains the k-V relationship between the absolute path of the flutter subprojects that the current flutter engineering depends on (both direct and transfer dependencies). A subproject may be a Flutter Plugin or a Flutter Package. The following is an example of the contents of a. Flutter -plugins:
Url_launcher = / Users/Sunzxyong /. Pub - cache/hosted/pub. The flutter - IO. Cn/url_launcher 4.0.2 /Copy the code
2. flutter.gradle
This script, located in the Flutter SDK, looks very long and does three things:
- Select the Flutter engine that matches the corresponding architecture (Flutter. So)
- Parsing the
.flutter-plugins
Add the corresponding Android Module to the Native project dependencies.- Hook mergeAssets/processResources Task, advance execution FlutterTask, call
flutter
Command to compile the Dart layer codeflutter_assets
Product, and copy it toassets
directory
With the above three steps, you can automatically build the code in the Flutter project and automatically copy the products into Native by running the build directly in the Native project
2. IOS
In IOS, the local dependency mode is as follows:
- Pass in the Podfile
eval binding
Characteristics of injectionpodhelper.rb
Script that is executed during POD install/update- In the IOS construction phase
Build Phases
That needs to be executed when the build is injected intoxcode_backend.sh
The script
For IOS local dependencies, podhelper.rb and xcode_backend.sh are responsible for the Pod local dependencies and product construction of Flutter
1. podhelper.rb
Since the Podfile is written in Ruby, this script is also a Ruby script, which does three things when pod install/update occurs:
- Pod native dependencies on the Flutter engine and the Flutter plugin registry
- Pod native source code dependencies
.flutter-plugins
The file contains ios projects in the Flutter project path- After pod Install completes
post_install
Get the current target project object and import itGenerated.xcconfig
Configuration, all of which are environment variable configurations, mainly for the construction phasexcode_backend.sh
Preparing for script execution
These things ensure that the Flutter engineering and the dependencies that transfer them into the Native engineering via POD. The next step is construction
2. xcode_backend.sh
This Shell script, which is located in the Flutter SDK, does two things:
- Build products (app. framework, Flutter_assets) by compiling the flutter command
- Copy the products (*. Framework, Flutter_assets) into the corresponding XCode build products directory as follows:
$HOME/Library/Developer/Xcode/DerivedData/${AppName}
Frameworks are copied to ${BUILT_PRODUCTS_DIR}”/”${PRODUCT_NAME}”. App /Frameworks”
Copy flutter_assets to ${BUILT_PRODUCTS_DIR}”/”${PRODUCT_NAME}”.app”
${AppName}/Products/${AppName}.app
5. Flutter communicates with Native
There are three ways that Flutter can communicate with Native, which are briefly described here:
- MethodChannel: method invocation
- EventChannel: Event listener
- BasicMessageChannel: message passing
Flutter and Native communication are bidirectional channels that can call and message each other
The following is the main content of this paper. The above content is mainly to introduce the important content of Flutter engineering and prepare for the following lecture. Of course, there are also packaging mode, construction process, etc., which can be discussed in a separate article later
Vi. Consistency and automated management of Flutter versions
There is a problem that must be solved with the build consistency and automatic management of the Flutter SDK in team multiplayer development mode. This problem can be solved by looking at the version management mode of The Flutter SDK.
Gradle builds Gradle projects in gradle-wrapper.properties configuration file. Gradle builds Gradle projects in gradle-wrapper.properties If you do not have a local Gradle version of the current project at the time of the build, the required Gradle version is automatically downloaded and the build is executed in./gradlew wrapper mode, so that the local Gradle environment is isolated from the project environment. The corresponding project is always built with the same Gradle version
This wrapper approach to versioning allows for isolation from globally configured environments on each machine, and for maintaining the same build for the same project under multi-team collaboration
Therefore, we add three files to the root directory of each Flutter project using Gradle version management:
wrapper/flutter-wrapper.properties
flutterw
flutterw.bat
Copy the code
After the addition of the project structure, there are three more files, as follows:
The above flutter-wrapper.properties configuration file of the current project Flutter SDK is as follows:
distributionUrl=https://github.com/flutter/flutter flutterVersion = 1.0.0Copy the code
Of course, some additional configurations can be added if necessary. Currently, these two configurations are sufficient. The remote address and version number of Flutter are specified
While Flutterw is a Shell script, internal version management mainly does the following:
- Read the configured version number of the Flutter SDK to verify that the Flutter SDK version does not exist
- Update the Android
local.properties
And in the IOSGenerated.xcconfig
File containing the Flutter SDK address- Finally, the parameters passed from the command line are linked to Flutter in the Flutter SDK for execution
Then build the Flutter project with the flutterw command:
./flutterw build bundle
Copy the code
The local global configuration of the FLUTTER command is not needed to avoid the problem of the different versions of the flutter development students. In this way, students who are new to flutter development do not need to download the FLUTTER SDK manually, just execute any Flutterw command. For example,./ Flutterw –version can automatically trigger the download and installation of the corresponding Flutter SDK, implementing elegant automation management. This approach also provides the basis for the packaging platform to support the Flutter project
Vii. The hybrid development of Flutter component architecture
If Flutter is to be used to develop a module or function in our existing Native engineering, it must not change the structure of Native engineering or affect the existing development process. Then, how to carry out the mixed development? As for the four engineering models of Flutter, we can directly ignore the Flutter App, because this is a project to develop a new Flutter App. For the Flutter Module, To build a Flutter project, you must have a main.dart portal, which also happens to have a main portal in the Flutter Module
Therefore, component partitioning is carried out by using the Flutter Module as the aggregation entrance of all modules or functions implemented by Flutter, through which bidirectional association of the Flutter layer to the Native layer is carried out. Where is the Flutter development code written? Of course, we can write our Dart code directly into the Flutter Module. If we develop multiple modules and components later, we cannot write all our Dart code into the Flutter Module lib/. This is the simplest way to differentiate modules by creating subdirectories under the Lib/directory. However, there are some problems with this. All modules share a remote Git address. First, it is completely coupled in component development isolation, second, each module component does not have a separate version number or Tag, and the subsequent increase in module components brings more test regression costs
Componentized right way for a component with a separate remote Git address management, so that each component in the formal version has a version number and the Tag, and completely isolated on each component development, increasing follow-up component does not affect the other components, a component of new requirements and does not need to return to the other components, results in lower test cost
As mentioned above, the Flutter Plugin can have the corresponding Dart layer code and platform layer implementation. Therefore, it can be designed that a component corresponds to a Flutter Plugin, and a Flutter Plugin is a complete Flutter project with an independent Git address. Therefore, these components are in the business layer, which can be called business components. The communication and common services between these business components can be divided into another basic layer, which can be called basic components. All business components depend on the basic layer. The Flutter Module, as an aggregation layer, depends on all the Flutter components. The dependencies between these Flutter projects are managed through Pub dependencies
Therefore, the overall componentized architecture can be designed as follows:
Positioning of business components versus base components
For the basic components above, finer granularity can also be divided, but too much division is not recommended. For communication with Native platform layer, each business component corresponds to a Channel. Of course, finer granularity Channel can also be divided internally. This Channel is responsible for providing the Native layer services for the Flutter layer to consume. The Native layer should call the Api of the Flutter layer as little as possible, only when some value callbacks occur
Because the essence of Flutter is to run on both ends of development at once, if there are too many platform-dependent implementations, they will be violated and the UI will just write one. The implementation of the platform layer should also try to maintain one principle, namely:
Try to make the Native platform layer become the service layer, and make the Flutter layer become the consumer layer to invoke the services of the Native layer. That is, Dart calls the Native Api, so that the Flutter developers can use and develop the Flutter smoothly after the two developers write a consistent base of service interfaces
As for the design of the Dart Api layer of the public service component in the basic component, because the public service mainly calls the services of the Native layer, the public Dart Api is provided in Flutter as a bridge from Native to Flutter. There are many kinds of Native services. The design of the corresponding Api is that a DART file corresponds to a type of service. The entire public service component provides a unified exposed DART. The internal fine-grained DART implementation is imported through export. Dart is common_service. Dart:
library common_service; export 'network_plugin.dart'; export 'messager_plugin.dart'; .Copy the code
Upper-layer business component Api calls only need to import a DART, which is transparent to upper-layer business component developers without requiring them to know what apis are available:
import 'package:common_service/common_service.dart';
Copy the code
Viii. Engineering architecture of Flutter mixed development
We have set up the basic componentized architecture. The next step is how to make Flutter hybrid development complete engineering management. We all know that we cannot use the official local dependency mode directly, because it will directly affect the Native engineering, development process and packaging process. Therefore, we need to optimize the Flutter based on the official dependence, so we derive two ways to link Flutter to Native works:
- Local dependency (source dependency)
- Remote dependencies (product dependencies)
The existing environment of the packaging platform can only support the standard Gradle engineering structure for packaging. Besides, local dependence is disastrous for students who do not need to develop businesses related to Flutter. Therefore, remote dependence is introduced. Remote dependencies rely directly on the packaged Flutter products. Android relies on them via Gradle and IOS relies on them via Pod. This is transparent to other business development students, who do not need to care about Flutter or know that Flutter exists
The context in which these two dependency patterns are used is also different
Local dependencies are mainly used by students who need to develop a Flutter. Configure whether to enable the local Flutter Module dependency and the linked local Flutter Module address in the corresponding configuration file of the Native project. In this way, Native projects can automatically rely on local Flutter projects. The whole process is seamless. Local dependencies can also be made through source code. In IOS, local.xcconfig is created locally. The configuration properties of the two platforms are the same:
FLUTTER_MODULE_LINK_ENABLE=true
FLUTTER_MODULE_LOCAL_LINK=/Users/Sunzxyong/FlutterProject/flutter_module
Copy the code
Remote dependencies are to publish the components of the Flutter Module to remote sites and then rely on them in Native projects. This method is the default method of dependencies. This method is transparent to other developers and does not affect the development process and packaging platform
The two dependency modes mentioned above, how to carry out the engineering management and customization of these two dependency modes
1. BugFix and customize the Flutter SDK source code without invading it
The use of the Flutter SDK will inevitably encounter some problems or bugs with the Flutter SDK. However, these problems are usually caused by bugs in the linked scripts of the Flutter SDK on each platform layer. If we want to accommodate existing projects and extend the customization functions, we will directly modify the Flutter SDK source code. This will add to the cost of smooth updates to subsequent SDKS
Bugs or scripts that require customization are usually related to platform linking, excluding the need to modify the DART Api code, which can only be changed to the source code. However, the probability of bugs is relatively small, which is related to the SDK Api level. The following places are usually compatible or customized with high probability of problems:
- $FLUTTER_SDK/packages/flutter_tools/gradle/flutter.gradle
- $FLUTTER_SDK/bin/cache/artifacts/engine/android-arch/flutter.jar
- $FLUTTER_MODULE /. Android/build. Gradle,. Android/Settings. Gradle
- $FLUTTER_MODULE/.android/Flutter/build.gradle
- $FLUTTER_MODULE/.ios/Flutter/Generated.xcconfig
- $FLUTTER_MODULE/.ios/Flutter/podhelper.rb
- $FLUTTER_MODULE/.ios/Podfile
- $FLUTTER_SDK/packages/flutter_tools/bin/xcode_backend.sh
The problems and customization points we need to have with the Flutter SDK are as follows:
- AndroidThe Flutter engine in the Flutter SDK is not supported
armeabi
architecture- Android: in the Flutter SDK
flutter.gradle
Link scripts do not support nonapp
Name of the Application project- Android: in the Flutter SDK
flutter.gradle
Link script local dependencies existflutter_shared
Resource file does not copy Bug- Android: Agents are needed to solve the above problems
build.gradle
Build scripts, as well as inbuild.gradle
Customize our build artifact collection Task in the build script- IOS: automatically generated in the Flutter Module ios
podhelper.rb
Ruby scripts use Podpost_install
Methods lead to conflicts that Native engineering cannot be used or used, which indirectly intrudes into Native engineering and coupling and is too restrictive- IOS: automatically generated in the Flutter Module
Podfile
File, need to add our own privateSpecs
The warehouse is customized- IOS: solve
post_install
Problem after Flutter SDKxcode_backend.sh
Link script environment variable reading problem
In order to make the Flutter SDK non-invasive, we used proxy Bug fixes and customizations to solve these problems. The following are the implementation strategies for the two platforms
1. Android
For the Android platform, we can automate the armeabi support by scripting. The above mentioned version automation management of Flutterw is also included in the armeabi support script. The advantage of this is that you can remove the support and add the armeabi architecture engine automatically by calling./flutterw armeabi
The problem with the Flutter. Gradle link script in the Flutter SDK will not be changed directly in the source code. Instead, it will be copied as Flutter_proxy. gradle and fixed in the proxy script. The main fixes for flutter_shared support compatibility with app hardcoded names are as follows:
Task copySharedFlutterAssetsTask = project.tasks.create(name: "copySharedFlutterAssets${variant.name.capitalize()}", type: Copy) {
from(project.zipTree(chosenFlutterJar))
include 'assets/flutter_shared/*'
into "src/${variant.name}"
}
Copy the code
Let the copyFlutterAssetsTask task depend on it, and the app hardcoded name compatibility is easier, by configuring the Module name in the Native project local.properties, Add the code to read the property in flutter_proxy.gradle:
String appName = loadRootProjectProperty(project, "FLUTTER_APP_NAME", "app")
Task mergeAssets = project.tasks.findByPath(":${appName}:merge${variant.name.capitalize()}Assets")
Copy the code
Agent for the build. Gradle build scripts, we can through the execution of gradle build time, through the -c command Settings. Gradle agent, and acting out the build. Gradle and specify the build of the Module. The gradle script, as follows:
cd .android ./gradlew assembleDebug -c .. /script/proxy/settings.gradleCopy the code
Gradle: settings.gradle: settings.gradle: build.gradle
getRootProject().buildFileName = 'build_proxy.gradle'
project(":flutter").buildFileName = "build_proxy.gradle"
Copy the code
Where the agent’s script apply in Flutter/build.gradle is changed to the script agent in the fixed Flutter SDK:
apply from: "${project.projectDir.parentFile.parentFile.absolutePath}/script/proxy/flutter_proxy.gradle"
Copy the code
In this way, the Android project can be completely controlled by us during the construction period, including some custom features such as product collection plug-ins and product publishing to remote plug-ins
However, in this way, the proxy script needs to be manually specified when executing the build command, which will not be specified for Native automatic build. Therefore, based on this method, we will optimize it again. Because Flutter in the Module. The android and ios. Engineering is generated automatically by the Flutter SDK internal template, as long as the executive build | packages the get command is automatically generated, such as the first thought is to change the Flutter SDK internal engineering template, It is in the packages/ Flutter_tools /templates directory of the Flutter SDK, but this is against our non-invasive Flutter SDK, so this method cannot be selected
Recall that our Flutter SDK version consistency management is automated through the Flutterw script, which eventually links to the commands in the native Flutter SDK. Therefore, we can add a script to Flutterw that will be used to build the.Android and.ios projects Gradle and settings.gradle scripts are directly replaced with the content of our proxy scripts. This will not invade the Flutter SDK and is convenient for future maintenance. This function is no longer needed. The native build script was restored, and the Flutterw script was executed as follows:
function main() {
# ...
link_flutter "$@"
inject_proxy_build_script
# ...
}
Copy the code
Inject_proxy_build_script This Shell function will replace the corresponding script with our script. There is also a corresponding decision in this function, because Flutterw is mainly used to manage the consistency of the Flutter SDK version. This function only applies to the Flutter Module project. So this approach works perfectly both under locally dependent builds and through command line builds
2. IOS
For IOS, the main support is for Podfile and PodHelper. rb scripts. For Podfiles, we can simply script our own private Specs repository into the Podfile header:
Source 'https://. * * * / XXSpecs git' source 'https://github.com/CocoaPods/Specs.git' platform: ios, '8.0'...Copy the code
This work will also be compatible after flutterw execution, and can be directly commented if it is not needed later. This automatic injection script will only apply to the Flutter Module project
The podhelper.rb script is compatible with Native dependencies. The post_install function is implemented after pod install, which conflicts with Native dependencies. Therefore, post_install was commented out by default after the execution of the Flutterw script, but it certainly could not be commented out without reason. We need to understand the function of this paragraph, which is to set environment variables to prepare for the subsequent construction and execution of xcode_backend.sh script. How to comment out a different way to restore the setting of an environment variable will be discussed later. After the comment, the podhelper.rb script snippet reads:
# post_install do |installer|
# installer.pods_project.targets.each do |target|
# target.build_configurations.each do |config|
# config.build_settings['ENABLE_BITCODE'] = 'NO'
# xcconfig_path = config.base_configuration_reference.real_path
# File.open(xcconfig_path, 'a+') do |file|
# file.puts "#include \"#{File.realpath(File.join(framework_dir, 'Generated.xcconfig'))}\""
# end
# end
# end
# end
Copy the code
Finally, the above processing script execution flow was automatically supported in Flutterw:
function main() {
# ...
link_flutter "$@"
# ...
podfile_support
podhelper_support
collect_ios_product "$@"
}
Copy the code
Function internal judgment only applies to the Flutter Module project, after all, other Flutter Plugin projects do not require this treatment
2. Local dependency non-invasive process
We need to open or close the local Flutter Module link dependencies through a property configuration file. It is definitely not possible to open or close the local Flutter Module link dependencies through configuration development. Affect the development of other students without Flutter environment and affect packaging on the packaging platform. The main job of the agent layer is to determine whether there is a corresponding property configuration file locally and whether the property value meets the conditions of local dependency on the Flutter Module. If so, the local dependency on the Flutter Module will be implemented. If not, Return is returned, and no processing is done by default
Therefore, this agent method does not affect the original development process of Native project, and is transparent to other business developers and packaging platform
The implementation of the agent layer is different on Android and IOS platforms
1. Android
Android is automatically managed by a Gradle script that checks the properties of the local.properties configuration file in settings. Gradle and build. Gradle. Determines whether to enable local Flutter Module links
2. IOS
IOS is a bit more complicated, because there are two types of proxy scripts that involve the Ruby execution script proxy in the Podfile and the Shell script proxy in Build Phases: In Ruby and the Shell, the final execution of the proxy script still calls the propped script, just a layer of wrapping logic before calling. In IOS, there is no local configuration file, so we create a new IOS local configuration file named local.xcconfig. This configuration file is not managed with the version and will be gitignore dropped.
eval(File.read(File.join('./', 'FlutterSupport', 'podhelper_proxy.rb')), binding)
Copy the code
The Build Phases call:
chmod +x "${SRCROOT}/FlutterSupport/xcode_backend_proxy.sh"
"${SRCROOT}/FlutterSupport/xcode_backend_proxy.sh" flutterBuild
Copy the code
The post_install function in the podhelper.rb script is commented out and replaced by the xcode_backend.sh environment variable that is executed during the IOS build phase. The environment variables Generated in the Flutter Module are Generated. Xcconfig. If we copy the contents of this file into the corresponding xcConfig of the IOS project, For example, debug.xcconfig and release. Xcconfig are feasible, but will invade the Native project, resulting in many variables in the Native project, which is not elegant. What we need to do is to ensure non-invasion
Since we have used proxy script for proxy, we can obtain these environment variables completely. Through the features of Shell script, the sub-shell will inherit the environment variable value of export from the parent Shell, so add the following code in proxy Shell script:
function export_xcconfig() {
export ENABLE_BITCODE=NO
if [[ $# != 0 ]]; then
local g_xcconfig=$1/.ios/Flutter/Generated.xcconfig
if [[ -f "$g_xcconfig" ]]; then
# no piping.
while read -r line
do
if [[ ! "$line" =~ ^// ]]; then
export "$line"
fi
done < $g_xcconfig
fi
fi
}
Copy the code
Note that you cannot use pipes; pipes are in a different Shell process
3. Remote dependency product packaging process
The remote dependencies of Flutter, Android via Aar and IOS via.A and.framework static libraries, are easy to implement. The key is how to package the dependencies and upload them to remote, because the existing componentized packaging, Aar, App. Framework, Flutter_assets and other products are generated in the aggregation layer Flutter Module, as well as corresponding packaged products in business components and basic components. These packaging products will be packaged with different types of products for each platform, Android or AAR, while IOS is a static library. The following is the packaging process of Android and IOS respectively
1. Android
Android packaging is relatively simple. By executing the./ Gradlew assembleRelease in the. Android subproject of the Flutter Module, the aar products will be output in the build directory of the corresponding Android subproject of the Flutter Module. The key to obtaining the products of the dependencies of the Flutter Plugin is through the. Flutter -plugins file. These files are automatically generated in the packages Get and contain the libraries that the Flutter project relies on through the Pub. To get the artifacts of the corresponding dependent library
2. IOS
Packaging on IOS is a bit more complicated than on Android. We use.ios/Runner to package static libraries and other artifacts, so we also need to set up signatures by executing them directly in the Flutter Module. This command automatically executes pod install, so we don’t need to execute it alone. The product acquisition built on IOS is relatively cumbersome. Besides obtaining the related products of Flutter, we also need to obtain the static libraries and header files of all the components that Flutter depends on.
Flutter. Framework App. Framework FlutterPluginRegistrant Flutter_assets. A static library of all Plugin dependencies and header files
So, App. Framework is a compilation of the Dart in The Flutter engine. FlutterPluginRegistrant is the registry of all plug-in channels and is automatically generated. Flutter_assets contains fonts and other resources. A static library is the implementation of each component on the IOS platform layer
In addition to collecting *. Framework static libraries and Flutter_assets in the. IOS /Flutter directory, the rest is to collect. A static libraries and corresponding header files
build/ios/$variant-iphoneos
Copy the code
For the variant, the name of the build variant is also retrieved by parsing the.flutter-plugins file. The corresponding.a static library, but the header file is not in the.a static library. Therefore, the header files are obtained separately, because the resolution of the flutter-plugins obtained the KV key pair, and the corresponding V was the project address of the flutter plugin, so we obtained the header files from it
Finally, you need to get the static library of the FlutterPluginRegistrant registry and the header file
3. Product collection and delivery dependency
The products built by the Flutter Module aggregation layer are collected and aggregated into a separate product output directory, all automatically by scripting
On Android, Gradle plugin Hook assembleTask:
collectAarTask.dependsOn assembleTask
assembleTask.finalizedBy collectAarTask
Copy the code
The./gradlew assemble${variant} command is assembled automatically
On IOS, flutterw script was used to determine whether the build command was an IOS build command after the build, and then the products after the build were automatically collected:
function collect_ios_product() {
if [[ $# != 0 && $# > 2 ]]; then
if [[ "$1" = "build" && "$2" = "ios" ]]; then
# do collect...
fi
fi
}
Copy the code
The key script code corresponding to. A static library and header file collection is as follows:
while read -r line
do
if [[ ! "$line" =~ ^// && ! "$line" =~ ^# ]]; then
array=(${line//=/ })
local library=$product_dir/${array[0]}/lib${array[0]}.a
if [[ -f "$library" ]]; then
local plugin=$dest_dir/plugins/${array[0]}
rm -rf $plugin
mkdir -p $plugin
cp -f $library $plugin
local classes=${array[1]}ios/Classes
for header in `find "$classes" -name *.h`; do
cp -f $header $plugin
done
else
echo "The static library $library do not exist!"
fi
fi
done < $flutter_plugins
Copy the code
The directory structure of packaged products of Android and IOS is as follows:
We know that a single AAR file and podSpec declaration of static library artifacts will lose the transfer dependencies. Losing the transfer dependencies may result in tripartite libraries not used in our Native project, referenced in the Flutter project, and then Crash the App. To ensure the transfer of dependencies, Android will publish them to remote Maven. Finally, through remote dependencies, the above products are only local dependencies. IOS will parse all the PodSpec files in the Flutter plug-in and restore them to JSON format. Get the corresponding dependency library name and version number, and finally add the dependencies to the IOS remote artifact’s PodSpec profile
For remote dependencies on IOS, we know that it is possible to create a separate Git repository for remote dependencies on IOS Native by configuring podSpecs. However, large files, including Flutter. Prepare_command is a feature of PodSpec that will execute a script to pull the two products down when the pod library is installed. For now, you can upload them to Git first. This is more intuitive and controllable, easy to version management
4. Overall engineering process of Flutter mixed development
Nine, after the order
For the mixed development of existing projects using Flutter, there are still some drawbacks, such as performance and page stack management, but they have not been used yet. In addition, some basic libraries on Flutter are not mature at present. It is still not recommended to use Flutter for the development of important pages and pages with high dynamic intensity within the project. Instead, lightweight pages can be used. For the communication between Flutter and Native layer, Flutter should be used as the consumer layer to consume the services provided by the Native layer. The Native end should make as few changes as possible, preferably only one line of code. Replace page route hops with Flutter page routes to ensure zero coupling between Native and Flutter