In the previous article we discussed how the Flutter App can be integrated into the iOS App. In this article we will discuss how the Flutter App can be integrated into the Android App.
Gradle
Let’s take a quick look at the code structure of the Android project:
As an automated build tool for the Android project, let’s take a look at what Gradle has done to build the Flutter APP.
settings.gradle
Settings. gradle is used to configure all dependent modules in Android Project.
settings.gradle
// 1 include ':app' // 2 def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() assert localPropertiesFile.exists() localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath ! = null, "flutter.sdk not set in local.properties" // 3 apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"Copy the code
- Let’s start with the App Module
- readlocal.propertiesIn this file
flutter.sdk
The value of the property is assigned toflutterSdkPath
This variable
- In addition to the Android SDK path, local. Properties also defines Flutter related values. For example, the Flutter SDK path, Flutter build mode, Flutter version number, etc.
local.properties
sdk.dir=/Users/*/Library/Android/sdk flutter.sdk=/Users/*/Documents/flutter flutter.buildMode=debug Flutter. VersionName = 1.0.0 flutter. VersionCode = 1Copy the code
- The introduction of
"$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
The script in the
Let’s look at the app_plugin_loader.gradle script:
app_plugin_loader.gradle
Import groovy. Json. JsonSlurper def flutterProjectRoot = rootProject. ProjectDir. ParentFile find configuration file / / 1 def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins-dependencies') if (! Def object = new JsonSlurper().parseText(pluginsfile.text) assert object instanceof Map assert object.plugins instanceof Map assert object.plugins.android instanceof List object.plugins.android.each { androidPlugin -> assert androidPlugin.name instanceof String assert androidPlugin.path instanceof String def pluginDirectory = new File(androidPlugin.path, 'android') assert pluginDirectory.exists() include ":${androidPlugin.name}" project(":${androidPlugin.name}").projectDir = pluginDirectory }Copy the code
- readAndroidFile in the same directory
.flutter-plugins-dependencies
file - Read the Android array under the plugins field in that file and configure dependencies on each element of the array.
Hint: Familiar? That’s right, the iOS Pod script reads the iOS field values in the plugins field of this file.
{ "plugins":{ "android":[ ... { "name":"sqflite", "Path" : "/ Users / * / Documents/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.2+3/", "dependencies" : []}...]. , "ios":[...] ,}}Copy the code
To summarize, settings.gradle produces something like this:
include ':app' include ':fijkplayer' project(":fijkplayer").projectDir = '/ Users / * / Documents/flutter/.pub-cache/hosted/pub.dartlang.org/fijkplayer-0.8.7/android' include ': shared_preferences' project(":shared_preferences").projectDir = '/ Users / * / Documents/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.5.12+4/android' include ': sqflite' Project (" : sqflite "). ProjectDir = '/ Users / * / Documents/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.2+3/android' include ':url_launcher' project(":url_launcher").projectDir = '/ Users / * / Documents/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-5.7.10/android'Copy the code
Summary: Settings. gradle completes the configuration of all dependent modules.
Project / build.gradle
Let’s look at some of the Settings in Project/build.gradle:
// 1. rootProject.buildDir = '.. /build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } // 2. subprojects { project.evaluationDependsOn(':app') }Copy the code
- Set the output path of project and sub-project in the build folder of the same level as android.
Check it out by going inside:
As you may have noticed, iOS builds are also in this directory.
- through
evaluationDependsOn
Defines all the othersmouduleThe configuration depends onappthismoudule. Everything elsemouduleConfiguration has to waitappthismouduleAfter the configuration is complete, configure the.
Summary: Project/build.gradle configures the build output path of each moudule and dependencies between moudule.
app / build.gradle
Let’s take a look at app/build.gradle:
// 1. def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) {flutterVersionName = '1.0'} // 2. "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" // 3. flutter { source '.. /.. '}Copy the code
Code explanation:
- The first section reads the version number and version name from the local.properties file and sets it to the Android App version number and version name.
- According to the fromlocal.propertiesRead from the file
$flutterRoot
Path to the import$flutterRoot/packages/flutter_tools/gradle/flutter.gradle
Script run; - toflutterThe extended
source
Property is configured to. /..
.
flutter.gradle
The purpose of flutter. Gradle is to perform some flutter related tasks in the build process of the Android host App.
Flutter. Gradle has two important classes: FlutterPlugin and FlutterTask.
FlutterPlugin
FlutterPlugin implements the Plugin interface as a Gradle Plugin, so its entry method is the apply() method:
FlutterPlugin/apply
void apply(Project project) { ... // 1. project.extensions.create("flutter", FlutterExtension) // 2 project.afterEvaluate this.&addFlutterTasks // 3 if (shouldSplitPerAbi()) { project.android { splits { abi { enable true reset() universalApk false } } } } getTargetPlatforms().each { targetArch -> String abiValue = PLATFORM_ARCH_MAP[targetArch] project.android { if (shouldSplitPerAbi()) { splits { abi { include abiValue } } } } } // 4 String flutterRootPath = resolveProperty("flutter.sdk", System.env.FLUTTER_ROOT) if (flutterRootPath == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.") } flutterRoot = project.file(flutterRootPath) if (! flutterRoot.isDirectory()) { throw new GradleException("flutter.sdk must point to the Flutter SDK directory") } engineVersion = useLocalEngine() ? "+" // Match any version since there's only one. : "1.0.0 -" + Paths. The get (flutterRoot absolutePath, "bin", "internal", "engine.version").toFile().text.trim() String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter" flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile(); // 5 project.android.buildTypes { profile { initWith debug if (it.hasProperty("matchingFallbacks")) { matchingFallbacks = ["debug", "release"] } } } // 6 if (shouldShrinkResources(project)) { String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", "gradle", "flutter_proguard_rules.pro") project.android.buildTypes { release { shrinkResources isBuiltAsApp(project) proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro" } } } // 7 if (useLocalEngine()) { String engineOutPath = project.property('local-engine-out') File engineOut = project.file(engineOutPath) if (! engineOut.isDirectory()) { throw new GradleException('local-engine-out must point to a local engine build') } localEngine = engineOut.name localEngineSrcPath = engineOut.parentFile.parent } // 8 project.android.buildTypes.each this.&addFlutterDependencies project.android.buildTypes.whenObjectAdded this.&addFlutterDependencies }Copy the code
Code explanation:
- To create aFlutterExtensionExtension, this extension has two properties,
source
–Flutter APPThe path of the project,target
– Flutter APPExecution entry, do not set the defaultlib/main.dart; - app moduleThe rest of theTaskExecute after completion
addFlutterTasks
Methods; - Decide whether to enable ABI subcontracting;
- Get some system environment variables;
- flutterRootPath — /Users/*/Documents/flutter
- flutterRoot — /Users/*/Documents/flutter
- EngineVersion c956a31c0a3d350827aee6c56bb63337c5b4e6e 1.0.0-2
- flutterExecutable — flutter(mac), flutter.bat(windows)
- By default, there are two debug and Release modes. In this case, a profile build mode was added based on the Debug mode, so now there are three debug, Release and profile build modes.
- Enable shrinkResources.
- Whether to set up a local Maven repository;
- Add to each build patternFlutterRely on
addFlutterDependencies
The call.
Let’s look at the implementation in addFlutterDependencies:
FlutterPlugin/addFlutterDependencies
void addFlutterDependencies(buildType) { ... // 1. String hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ? : DEFAULT_MAVEN_HOST String repository = useLocalEngine() ? project.property('local-engine-repo') : "$hostedRepository/download.flutter.io" project.rootProject.allprojects { repositories { maven { url repository } } } // 2 addApiDependencies(project, buildType.name, "io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion") print("io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion\n"); // 3 List<String> platforms = getTargetPlatforms().collect() if (flutterBuildMode == "debug" && ! useLocalEngine()) { platforms.add("android-x86") platforms.add("android-x64") } platforms.each { platform -> String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_") addApiDependencies(project, buildType.name, "io.flutter:${arch}_$flutterBuildMode:$engineVersion") print("io.flutter:${arch}_$flutterBuildMode:$engineVersion\n"); }}Copy the code
Code explanation:
- Setup maven url address of the warehouse, the default is storage.googleapis.com/download.fl… If the network speed is not ideal, you can also configure the FLUTTER_STORAGE_BASE_URL environment variable to point to the domestic mirror address **storage.flutter-io.cn/download.fl…
- Add embedded dependencies
io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion
, this depends on andBuild a modelandFlutter Engine versionHave a relationship. Example –IO. Flutter: flutter_embedding_debug: c956a31c0a3d350827aee6c56bb63337c5b4e6e 1.0.0-2
- addlibflutter.soDependence, this dependence and thetaarchitectureandFlutter Engine versionHave a relationship. Example –
IO. Flutter: armeabi_v7a_debug: c956a31c0a3d350827aee6c56bb63337c5b4e6e 1.0.0-2
Tip:
- Flutter_embedding endows Flutter with the ability to embed native.
- Libflutter. So is the Flutter Engine;
- The last thing these two dependencies call is
project.dependencies.add(configuration, dependency, config)
This method, so it’s givenprojectPlus dependence. becausesqfliteothermoduleBoth dependencies are required.
Now that the FlutterPlugin/ Apply process is analyzed, let’s analyze the addFlutterTasks method left over from Step 2.
FlutterPlugin/addFlutterTasks
This method takes a lot of code, so let’s summarize it.
private void addFlutterTasks(Project project) { // 1 String target = project.flutter.target if (target == null) { target } // 2 def addFlutterDeps = {variant -> 2.1 FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) { ... } 2.2 Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) { ... 2.3 addApiDependencies} (project, the variant. The name, Project. files {packFlutterAppAotTask}) 2.4 Task copyFlutterAssetsTask = project.tasks. Create (name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy, ) { ... } } // 3 project.android.applicationVariants.all { variant -> ... } // 4 configurePlugins() }Copy the code
- Get various parameters from gradle.properties. The example given above – if there is no target configuration, default to lib/main.dart.
- Define a function addFlutterDeps.
- Create the corresponding FlutterTask based on the build pattern and the parameters obtained from gradle.properties in the first step. The function of FlutterTask is to compile the code of the Flutter APP.
- PackFlutterAppAotTask package FlutterTask compilation results into libs.jar files.
- Add libs.jar file dependencies to project.
- CopyFlutterAssetsTask is the asset associated with a Flutter App to copy. Because Flutter may be compiled as a plug-in or subproject, the compilation result is packaged as an AAR if the plug-in is compiled, and the compilation result is packaged as an APK when the subproject is compiled, there is a difference between the two cases.
- Add Flutter dependencies to all applicationVariants or libraryVariants, execute the addFlutterDeps function and copy the APK to the target path.
- This method adds a Plugin dependency to the project. Dependencies are handled differently depending on how they are compiled.
FlutterTask
FlutterTask’s build() calls its parent’s buildBundle() method:
void buildBundle() { if (! sourceDir.isDirectory()) { throw new GradleException("Invalid Flutter source directory: ${sourceDir}") } intermediateDir.mkdirs() // Compute the rule name for flutter assemble. To speed up builds that contain // multiple ABIs, the target name is used to communicate which ones are required // rather than the TargetPlatform. This allows multiple builds to share the same // cache. String[] ruleNames; if (buildMode == "debug") { if (fastStart) { ruleNames = ["faststart_android_application"] } else { ruleNames = ["debug_android_application"] } } else { ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" } } project.exec { logging.captureStandardError LogLevel.ERROR executable flutterExecutable.absolutePath workingDir sourceDir if (localEngine ! = null) { args "--local-engine", localEngine args "--local-engine-src-path", localEngineSrcPath } if (verbose) { args "--verbose" } else { args "--quiet" } args "assemble" args "--depfile", "${intermediateDir}/flutter_build.d" args "--output", "${intermediateDir}" if (performanceMeasurementFile ! = null) { args "--performance-measurement-file=${performanceMeasurementFile}" } if (! fastStart || buildMode ! = "debug") { args "-dTargetFile=${targetPath}" } else { args "-dTargetFile=${Paths.get(flutterRoot.absolutePath, "examples", "splash", "lib", "main.dart")}" } args "-dTargetPlatform=android" args "-dBuildMode=${buildMode}" if (trackWidgetCreation ! = null) { args "-dTrackWidgetCreation=${trackWidgetCreation}" } if (splitDebugInfo ! = null) { args "-dSplitDebugInfo=${splitDebugInfo}" } if (treeShakeIcons == true) { args "-dTreeShakeIcons=true" } if (dartObfuscation == true) { args "-dDartObfuscation=true" } if (dartDefines ! = null) { args "--DartDefines=${dartDefines}" } if (bundleSkSLPath ! = null) { args "-iBundleSkSLPath=${bundleSkSLPath}" } if (codeSizeDirectory ! = null) { args "-dCodeSizeDirectory=${codeSizeDirectory}" } if (extraGenSnapshotOptions ! = null) { args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}" } if (extraFrontEndOptions ! = null) { args "--ExtraFrontEndOptions=${extraFrontEndOptions}" } args ruleNames } }Copy the code
This method actually executes a FLUTTER build method with some parameters.
APK
Finally, let’s look at the structure of APK.
- inlibThe file contains support for 4 architectures by default, includingFlutter Engine—
libflutter.so
.Flutter App code—libapp.so
.The SO file that the Flutter plugin depends on—libijkffmpeg.so,libijkplayer.so,libijksdl.so
;
You may encounter couldn’t find “libflutter. So “because there is no libflutter or libapp.so in x86, this is a known problem with flutter.
- Flutter AppThe resource files in the
assets/flutter_assets
In the.
AndroidManifest.xml
Let’s take a look at what’s configured in androidmanifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.jj_movie"> // 1. <application android:name="io.flutter.app.FlutterApplication" android:label="jj_movie" android:icon="@mipmap/ic_launcher"> <activity . > // 2 <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> // 3 <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background" /> </activity> // 4 <meta-data android:name="flutterEmbedding" android:value="2" /> </application> </manifest>Copy the code
- Project Application such as IO. Flutter. App. FlutterApplication;
- By default, a white theme is defined to be visible to the user before the Flutter APP is loaded and displayed, and to be the background of the Window after the Flutter APP is loaded and displayed.
- You can modify the startup diagram;
- useFlutter Android Embedding V2Version.
FlutterActivity
.FlutterActivity
.FlutterActivity
andFlutterActivity
And so on are inV2Introduced in the version.
FlutterApplication
The code in FlutterApplication is very simple. It mainly executes flutterinjector.instance ().flutterloader ().startInitialization(this).
public class FlutterApplication extends Application { @Override @CallSuper public void onCreate() { super.onCreate(); FlutterInjector.instance().flutterLoader().startInitialization(this); }}Copy the code
The main function of FlutterLoader is to load FLutter Engine and FLutter APP resources, etc. Let’s look at the main code in the startInitialization method of FlutterLoader:
public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
// 1.
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("startInitialization must be called on the main thread");
}
// 2.
VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE))
.init();
// 3.
Callable<InitResult> initTask =
new Callable<InitResult>() {
@Override
public InitResult call() {
...
}
};
}
Copy the code
- Ensure that this method is executed on the main thread;
- VsyncWaiter is initialized. Its main function is to register with the Android system and wait for the VSync signal.
VsyncWaiter is the repeater of Flutter rendering. When a signal is received, the Flutter app will be notified to initiate a rendering call, and then perform some layout and paint columns, which will be submitted to the GPU thread for rendering.
- Start an asynchronous thread and load some asset resources.
Summary: FlutterLoader performs startInitialization to prepare the Flutter app for loading and rendering.
FlutterActivity
Let’s look at the important code for FlutterActivity:
public class FlutterActivity extends Activity { // 1 protected FlutterActivityAndFragmentDelegate delegate; // 2 private LifecycleRegistry lifecycle; public FlutterActivity() { lifecycle = new LifecycleRegistry(this); } @ Override protected void onCreate (@ Nullable Bundle savedInstanceState) {/ / 3.1 switchLaunchThemeForNormalTheme (); super.onCreate(savedInstanceState); / / 3.2 delegate = new FlutterActivityAndFragmentDelegate (this); delegate.onAttach(this); delegate.onActivityCreated(savedInstanceState); / / 3.3 configureWindowForTransparency (); / / 3.4 the setContentView (createFlutterView ()); / / 3.5 configureStatusBarForFullscreenFlutterExperience (); } private View createFlutterView() { return delegate.onCreateView(null , null, null); }}Copy the code
- FlutterActivityAndFragmentDelegateattribute
delegate
In charge, so to speakFLutter appRelated to most functional objects; - LifecycleRegistry is a LifeCycle that handles the FlutterActivity LifeCycle, initialized in the constructor;
- in
onCreate
The main tasks of the method are:- First switch to the theme of the startup image to display the startup image
- Initialize theFlutterActivityAndFragmentDelegateobject
delegate
- Then make the window’s background transparent
- FlutterActivitytheViewAdd the
delegate
Object to createFlutterViewAs aFLutter appThe rendering ofView.FlutterViewisSurfaceViewThe subclass. - Android 5.0 above set to immersive status bar
So far, everything is ready, just wait FlutterActivityAndFragmentDelegate will load content came in.
FlutterActivityAndFragmentDelegate
- The constructor passes FlutterActivity as Host, mainly to get the context.
FlutterActivityAndFragmentDelegate(@NonNull Host host) {
this.host = host;
}
Copy the code
onAttach
It’s basically initializedFlutter EngineAnd registeredFlutter plug-in;
void onAttach(@NonNull Context context) {
ensureAlive();
if (flutterEngine == null) {
setupFlutterEngine();
}
host.configureFlutterEngine(flutterEngine);
}
Copy the code
// FlutterActivity
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
}
Copy the code
Logic and iOS similar GeneratedPluginRegistrant this registered plug-in.
- Load the contents of the Flutter App
Call when FlutterActivity execution onStart FlutterActivityAndFragmentDelegate onStart method, and then from the lib/main dart entry documents began to perform.
void onStart() { doInitialFlutterViewRun(); } private void doInitialFlutterViewRun() { if (flutterEngine.getDartExecutor().isExecutingDart()) { return; } if (host.getInitialRoute() ! = null) { flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute()); } String appBundlePathOverride = host.getAppBundlePath(); if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) { appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath(); } DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint( appBundlePathOverride, host.getDartEntrypointFunctionName()); flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); }Copy the code