前面一篇文章我们探讨了一下Flutter App如何被整合到iOS App中的,本文我们接着来讨论下Flutter App如何被整合到Android App中的。
Gradle
我们简单看一下Android项目的代码结构:
作为Android项目的自动化构建工具,我们先来看看Gradle在Flutter APP的构建过程中大概做了哪些工作。
settings.gradle
在settings.gradle中主要是用来配置Android Project中所有需要依赖的module,即进行工程树的配置。
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"
- 首先引入app module
- 读取local.properties这个文件中的
flutter.sdk
属性的值,赋值给flutterSdkPath
这个变量
- local.properties中除了Android SDK路径,还定义了Flutter相关的一些值。如Flutter SDK路径,Flutter构建模式,Flutter版本编号等。
local.properties
sdk.dir=/Users/*/Library/Android/sdk
flutter.sdk=/Users/*/Documents/flutter
flutter.buildMode=debug
flutter.versionName=1.0.0
flutter.versionCode=1
- 引入
"$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
中的脚本
我们接下来来看看app_plugin_loader.gradle脚本的代码:
app_plugin_loader.gradle
import groovy.json.JsonSlurper
def flutterProjectRoot = rootProject.projectDir.parentFile
// 1 找到配置文件
def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins-dependencies')
if (!pluginsFile.exists()) {
return
}
// 2 读取配置文件
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
}
- 读取Android文件同级目录下的
.flutter-plugins-dependencies
文件 - 读取该文件下的plugins字段下的android数组,对数组的每个元素配置依赖。
提示: 是不是很熟悉? 没错iOS的Pod脚本读的就是这个文件plugins字段下的的ios字段的值。
{
"plugins":{
"android":[
...
{
"name":"sqflite",
"path":"/Users/*/Documents/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.2+3/",
"dependencies":[
]
}
...
],
"ios":[...],
}
}
总结一下,最后settings.gradle大致生成的内容如下所示:
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'
总结:settings.gradle中完成了所有依赖的module的配置。
Project / build.gradle
我们来看看Project / build.gradle中的一些设置:
// 1.
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
// 2.
subprojects {
project.evaluationDependsOn(':app')
}
- 设置project和子project的输出路径,路径为和android同级的build文件夹下。
进去瞅瞅就可以验证:
你估计发现了,iOS的编译结果也是放在这个目录里面的。
- 通过
evaluationDependsOn
定义了所有其他的moudule的配置都依赖于app这个moudule。即其他的所有moudule配置必须得等app这个moudule的配置完成后再进行配置。
总结:Project / build.gradle中配置了各个moudule的编译输出路径和moudule间的依赖关系。
app / build.gradle
接下来我们来看下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.
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
// 3.
flutter {
source '../..'
}
代码解释:
- 第一大段的作用是从local.properties文件中读取版本编号和版本名称,设置为Android App的版本编号和版本名称;
- 根据从local.properties文件中读取到的
$flutterRoot
路径导入$flutterRoot/packages/flutter_tools/gradle/flutter.gradle
的脚本运行; - 给flutter扩展的
source
属性配置为.. / ..
。
flutter.gradle
flutter.gradle
的目的是在Android 宿主App的编译构建流程中执行一些Flutter相关的任务。
flutter.gradle中有两个重要的类,一个是FlutterPlugin,一个是FlutterTask。
FlutterPlugin
FlutterPlugin作为Gradle Plugin实现了Plugin接口,所以它的入口方法是apply()
方法:
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.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
}
代码解释:
- 创建一个FlutterExtension扩展,这个扩展有两个属性,
source
-Flutter APP工程的路径,target
- Flutter APP的执行入口,不设置就默认lib/main.dart; - app module的其他Task完成后执行
addFlutterTasks
方法; - 决定是否开启abi分包;
- 获取一些系统环境变量;
- flutterRootPath --- /Users/*/Documents/flutter
- flutterRoot --- /Users/*/Documents/flutter
- engineVersion --- 1.0.0-2c956a31c0a3d350827aee6c56bb63337c5b4e6e
- flutterExecutable --- flutter(mac), flutter.bat(windows)
- 默认有debug和release两个模式,这里参照debug模式又添加了一个profile构建模式,所以现在变成了debug,release和profile三个构建模式;
- 是否启动shrinkResources资源缩减;
- 是否设置本地maven仓库;
- 给每个构建模式添加Flutter依赖
addFlutterDependencies
的调用。
接下来我们看看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 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");
}
}
代码解释:
- 设置maven仓库的url地址,默认是https://storage.googleapis.com/download.flutter.io, 如果网速不太理想也可以配置FLUTTER_STORAGE_BASE_URL环境变量,让其指向国内的镜像地址https://storage.flutter-io.cn/download.flutter.io;
- 添加嵌入式的依赖
io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion
, 这个依赖与构建模式和Flutter Engine版本有关系。例子-io.flutter:flutter_embedding_debug:1.0.0-2c956a31c0a3d350827aee6c56bb63337c5b4e6e
- 添加libflutter.so依赖,这个依赖和架构和Flutter Engine版本有关系。例子-
io.flutter:armeabi_v7a_debug:1.0.0-2c956a31c0a3d350827aee6c56bb63337c5b4e6e
提示:
- flutter_embedding的作用是赋予Flutter嵌入native的能力;
- libflutter.so就是Flutter Engine;
- 这两个依赖最后调用的是
project.dependencies.add(configuration, dependency, config)
这个方法,所以是给project加的依赖。因为sqflite等其他的module都需要这两个依赖。
至此FlutterPlugin/apply的流程已经分析完了,接下来我们就来分析第2步遗留的addFlutterTasks
方法。
FlutterPlugin/addFlutterTasks
这个方法的代码量比较大,我们来概括总结一下。
private void addFlutterTasks(Project project) {
// 1
String target = project.flutter.target
if (target == null) {
target = 'lib/main.dart'
}
// 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, variant.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()
}
- 从gradle.properties获取各种参数,上面给出的一个例子---如果没有target配置,就默认设置为lib/main.dart。
- 定义一个addFlutterDeps的函数.
- 根据构建模式和第一步从gradle.properties获取各种参数一起创建对应的FlutterTask。FlutterTask的功能则是编译Flutter APP的代码。
- packFlutterAppAotTask这个task是将FlutterTask的编译结果打包成libs.jar文件。
- 给project加上libs.jar的文件依赖。
- copyFlutterAssetsTask是进行Flutter App相关的asset进行拷贝。由于Flutter可能作为插件编译或子项目编译,如果是插件编译产物编译结果打包为AAR,子项目编译则子项目编译时,编译结果被打包成APK,所以两种情况下有区别处理。
- 为所有applicationVariants或libraryVariants添加Flutter依赖,执行addFlutterDeps函数然后把APK拷贝到目标路径。
- 这个方法是给project添加Plugin的依赖.编译方式不同依赖的处理方式也不一样。
FlutterTask
FlutterTask的build()
调用的是父类的buildBundle()
方法:
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
}
}
这个方法其实是执行带一些参数的flutter build方法。
APK
最后,我们来看看APK的结构。
- 在lib文件中默认包含了4种架构的支持,里面包括了Flutter Engine---
libflutter.so
,Flutter App代码---libapp.so
,Flutter插件依赖的so文件---libijkffmpeg.so,libijkplayer.so,libijksdl.so
;
你可能会遇到
couldn't find "libflutter.so"
,因为x86里面没有libflutter.so和libapp.so
,这是flutter的一个已知的问题。
- Flutter App中的资源文件都打包到了
assets/flutter_assets
中。
AndroidManifest.xml
我们来看看AndroidManifest.xml
里面都配置了些什么东东:
// 1.
// 2
// 3
// 4
- 项目的Application的类为io.flutter.app.FlutterApplication;
- 默认定义了一个白色的主题,在Flutter APP加载显示前用户可见,在Flutter APP加载显示后作为Window的背景;
- 可以修改启动图;
- 使用Flutter Android Embedding V2 版本。
FlutterActivity
,FlutterActivity
,FlutterActivity
和FlutterActivity
等类都是在V2版本中引入的。
FlutterApplication
FlutterApplication中的代码很简单,就主要执行了FlutterInjector.instance().flutterLoader().startInitialization(this)
这行代码。
public class FlutterApplication extends Application {
@Override
@CallSuper
public void onCreate() {
super.onCreate();
FlutterInjector.instance().flutterLoader().startInitialization(this);
}
}
FlutterLoader主要的作用就是加载FLutter Engine 和加载Flutter APP的资源等。我们看看FlutterLoader的startInitialization
方法中的主要代码:
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 initTask =
new Callable() {
@Override
public InitResult call() {
...
}
};
}
- 确保本方法是在主线程执行;
- VsyncWaiter进行初始化,它的主要作用是在Android系统注册并等待VSync信号。
VsyncWaiter是Flutter渲染的中继者,当收到信号后,会通知Flutter app发起渲染调用,然后执行一些列的layout和paint,最后提交给GPU线程合成上屏。
- 开启一个异步线程,加载一些asset资源。
总结:FlutterLoader执行startInitialization
是为Flutter app的 加载和渲染做好准备工作。
FlutterActivity
我们来看看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
setContentView(createFlutterView());
// 3.5
configureStatusBarForFullscreenFlutterExperience();
}
private View createFlutterView() {
return delegate.onCreateView(null , null, null);
}
}
- FlutterActivityAndFragmentDelegate属性
delegate
可以说是负责处理FLutter app相关的绝大部分功能的对象; - LifecycleRegistry是一个LifeCycle,处理FlutterActivity生命周期的事情,在构造函数中初始化;
- 在
onCreate
方法中主要的工作是:- 先切换到启动图的主题显示启动图片
- 初始化FlutterActivityAndFragmentDelegate对象
delegate
- 然后将window的背景设置透明
- FlutterActivity的View添加由
delegate
对象创建的一个FlutterView,作为FLutter app的渲染View。FlutterView是SurfaceView的子类。 - Android 5.0以上设置为沉浸式状态栏
到此为止,一切都已经准备就绪了,就等FlutterActivityAndFragmentDelegate将内容加载进来了。
FlutterActivityAndFragmentDelegate
- 构造函数传入FlutterActivity作为Host, 主要就是为了获取context。
FlutterActivityAndFragmentDelegate(@NonNull Host host) {
this.host = host;
}
-
onAttach
主要是初始化了Flutter Engine和注册了Flutter插件;
void onAttach(@NonNull Context context) {
ensureAlive();
if (flutterEngine == null) {
setupFlutterEngine();
}
host.configureFlutterEngine(flutterEngine);
}
// FlutterActivity
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
}
GeneratedPluginRegistrant这个注册插件的逻辑和iOS的类似。
- 加载Flutter App的内容
当FlutterActivity执行onStart
时会调用FlutterActivityAndFragmentDelegate的onStart
方法,然后就从lib/main.dart入口文件开始执行了。
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);
}