firebase
This article is part of the series GloballyDynamic: Dynamic delivery everywhere. For a brief introduction on GloballyDynamic, head over to the main article or the website.
本文是“ 全球动态:无处不在的动态交付 ”系列的一部分。 有关GloballyDynamic的简要介绍,请转到主要文章或网站 。
Firebase App Distribution (formerly Fabric Beta) is a distribution platform that allows for quick and flexible beta testing, it is very useful for getting builds out to trusted testers early in order to receive feedback and collect statistics.
Firebase App Distribution (以前称为Fabric Beta)是一个分发平台,可以进行快速灵活的Beta测试,对于尽早将其扩展到受信任的测试人员以接收反馈和收集统计信息非常有用。
The platform does not (yet at least) support app bundles, this is a bit unfortunate if you otherwise publish your app in the form of an app bundle on Google Play Store, since the internally tested version on Firebase App Distribution will have slightly different behaviour compared to the one published on Play Store. For example, the flow for downloading and installing dynamic feature modules will not be testable on Firebase App Distribution.
该平台不(至少)不支持应用程序捆绑包,如果您以其他方式在Google Play商店中以应用程序捆绑包的形式发布应用程序,这有点遗憾,因为Firebase App Distribution上经过内部测试的版本的行为会略有不同与Play商店上发布的内容相比。 例如,下载和安装动态功能模块的流程将无法在Firebase App Distribution上进行测试。
While dynamic feature module flows can be tested very nicely through Internal App Sharing, it requires testers to have Google Play installed on their devices — although this condition holds true most of the time, it is becoming more common for devices to not have it, e.g. newer Huawei devices as well as Amazon Fire devices come without Play Store installed.
虽然通过内部应用共享可以很好地测试动态功能模块流,但它要求测试人员在其设备上安装Google Play-尽管这种情况在大多数情况下都是正确的,但设备不具备此功能正变得越来越普遍,例如较新的华为设备以及Amazon Fire设备均未安装Play商店。
Dynamic delivery for platforms like Firebase App Distribution can be enabled through a process very similar to the outlined in the previous article about a development setup. If you haven’t done so already, I suggest you give it a glance before proceeding with this article.
可以通过与上一篇有关开发设置的文章非常相似的过程来启用Firebase App Distribution等平台的动态交付。 如果您还没有这样做,建议您在继续本文之前先浏览一下。
The flow between all components involved in making this happen is illustrated in the image below:
下图说明了实现这一目标的所有组件之间的流程:

The two main differences here in comparison to the development setup described in the previous article are the following:
与上一篇文章中描述的开发设置相比,这里的两个主要区别如下:
The GloballyDynamic Server obviously has to be reachable from apps that are installed via Firebase App Distribution, hence we have to host it in a dedicated location which allows for that, i.e. not on the local development machine.
显然必须通过Firebase App Distribution安装的应用程序可以访问GloballyDynamic Server ,因此我们必须将其托管在允许该位置的专用位置,即不在本地开发计算机上。
An APK that has been stripped of on-demand dynamic features, but not stripped of install-time dynamic features, has to be produced and uploaded to Firebase App Distribution.
必须制作已删除按需动态功能但未删除安装时动态功能的APK,并将其上传到Firebase App Distribution。
The remainder of this article will focus on how to get these pieces of the puzzle in place.
本文的其余部分将重点介绍如何解决这些难题。
1)运行GloballyDynamic服务器 (1) Run a GloballyDynamic Server)
To be able to store bundles and send split APKs (dynamic feature APKs etc) to clients, we need to launch a GloballyDynamic Server in a location that is reachable from our app.
为了能够存储分发包并将拆分的APK(动态功能APK等)发送给客户端,我们需要在可以从我们的应用访问的位置启动GloballyDynamic Server。
The server is designed to be lightweight and environment agnostic, it can be embedded into a Java application, or run standalone on any system where Java is available. Below follows a few examples on how it can be launched.
该服务器设计为轻巧且与环境无关,可以嵌入Java应用程序中,也可以在任何有Java的系统上独立运行。 以下是一些有关如何启动它的示例。
运行独立服务器 (Running a standalone server)
To start a server, you can simply run the following shell commands on your favourite cloud provider or a dedicated server machine on your network:
要启动服务器,只需在您喜欢的云提供商或网络上的专用服务器计算机上运行以下shell命令:
# Download the latest server jar
curl -L --output globallydynamic-server.jar https://github.com/jeppeman/GloballyDynamic/releases/download/server-1.0.0/globallydynamic-server-1.0.0-standalone.jar# Run the server
java -jar globallydynamic-server.jar \
--port 8080 \
--username johndoe \
--password my-secret-password \
--storage-backend local \
--local-storage-path $(pwd)
Note that in the above example, uploaded bundles will be stored on the machine on which the server is running, you can configure the server to store bundles on Google Cloud Storage or Amazon s3 as well; refer to the documentation for a complete list of configurable options.
请注意,在上面的示例中,上传的捆绑包将存储在运行服务器的计算机上,您可以配置服务器以将捆绑包存储在Google Cloud Storage或Amazon s3上。 请参阅文档以获取可配置选项的完整列表。
将服务器嵌入到应用程序中 (Embedding the server into an application)
If you’d rather embed the server into an existing Java application (such as a Spring Boot app), it is available on maven central with the following coordinates:
如果您希望将服务器嵌入到现有的Java应用程序(例如Spring Boot应用程序)中,则可以在具有以下坐标的maven Central中使用它:
com.jeppeman.globallydynamic.server:server:1.0.0
You then configure and start the server through the following API:
然后,您可以通过以下API配置和启动服务器:
val configuration = GloballyDynamicServer.Configuration.builder()
.setPort(8080)
.setUsername("johndoe")
.setPassword("password")
.setStorageBackend(StorageBackend.LOCAL_DEFAULT)
.build()val server = GloballyDynamicServer(configuration)server.start()
在Google Compute Engine上运行服务器 (Running the server on Google Compute Engine)
As a way of getting up and running quickly with a server that is immediately publicly reachable, I’ll also provide an example on how to launch a server on Google Compute Engine — If you choose to run the server elsewhere than Google Compute Engine you can skip to step 2.
作为一种使用可立即公开访问的服务器快速启动和运行的方法,我还将提供一个示例,说明如何在Google Compute Engine上启动服务器- 如果您选择在Google Compute Engine之外 的其他位置运行该服务器,则可以跳至步骤2 。
Note: As with any cloud provider, running a server on Compute Engine incurs costs. However, new customers get $300 worth of free credit, which is plenty for evaluation purposes. You can read more on GCP free tier here.
注意:与任何云提供商一样,在Compute Engine上运行服务器会产生成本。 但是,新客户可以获得价值$ 300的免费信用,对于评估目的而言,这是足够的。 您可以在此处阅读有关GCP免费套餐的更多信息 。
If you don’t have it installed already, start by installing and configuring Google Cloud SDK and creating a project. Once that is done, run the following shell command:
如果尚未安装,请先安装和配置Google Cloud SDK并创建一个项目 。 完成后,运行以下shell命令:
curl https://globallydynamic.io/scripts/compute_engine_launch.sh | /usr/bin/env bash
It will download and run a script that:
它将下载并运行以下脚本:
A) Creates a bucket in Google Cloud Storage in which to store uploaded bundles. B) Creates a VM instance on Compute Engine and starts a GloballyDynamic server on it, the server has the same credentials configured as the above examples (johndoe/my-secret-password).
A)在Google云端存储中创建一个存储桶,用于存储上传的捆绑包。 B)在Compute Engine上创建VM实例,并在其上启动GloballyDynamic服务器,该服务器具有与上述示例相同的凭据(johndoe / my-secret-password)。
If you’re curious about the exact steps of the downloaded script you can find the source here.
如果您对下载脚本的确切步骤感到好奇,可以在这里找到源代码。
On the last line of output from the script you should see a printout of what the address to the started server is, looking something like this:
在脚本输出的最后一行,您应该看到已启动服务器的地址的打印输出,如下所示:
Done! Address to server: http://<ip-to-server>:8080
This means that the server has started successfully and is publicly accessible. We can now use this address to configure our firebase builds to use dynamic delivery, like so:
这意味着服务器已成功启动并且可以公开访问。 现在,我们可以使用此地址来配置Firebase构建以使用动态交付,如下所示:
android {
flavorDimensions 'platform' productFlavors {
firebase {
dimension 'platform'
}
} globallyDynamicServers {
serverUrl 'http://<ip-to-server>:8080'
username 'johndoe'
password 'my-secret-password'
applyToBuildVariants 'firebaseRelease'
}
}dependencies {
firebaseImplementation 'com.jeppeman.globallydynamic.android:selfhosted:1.0.0'
}
2)生成适合动态交付的APK,并将其上传到Firebase App Distribution (2) Produce an APK suitable for dynamic delivery and upload it to Firebase App Distribution)
Since we can not upload app bundles to Firebase App Distribution, we have to upload an APK, in particular an APK that does not contain on-demand dynamic feature modules, but does contain install-time dynamic feature modules.
由于我们无法将应用程序捆绑包上传到Firebase App Distribution,因此我们必须上传一个APK,尤其是不包含按需动态功能模块但包含安装时动态功能模块的APK。
建立APK (Building the APK)
We can not use ./gradlew assemble
for this end since it will leave out all dynamic feature modules, on-demand and install-time alike.
./gradlew assemble
我们不能使用./gradlew assemble
,因为它将遗漏所有动态功能模块,按需安装时均如此。
Instead we have to build a universal APK; meaning an APK that has been produced from an app bundle that contains all code and resources, including that from dynamic feature modules.
相反,我们必须构建一个通用的APK ; 表示从包含所有代码和资源(包括动态功能模块的代码和资源)的应用包中生成的APK。
A problem with universal APKs is that they also includes on-demand dynamic features by default. Luckily, there is a straightforward way to exclude them — in the AndroidManifest.xml
of your on-demand module, disable fusing, this will cause it to be excluded from the universal APK, as illustrated below:
通用APK的问题在于,默认情况下它们还包含按需动态功能。 幸运的是,有一种直接的方法可以将它们排除在外—在按需模块的AndroidManifest.xml
中,禁用fusing ,这将导致它从通用APK中排除,如下所示:
<manifest xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.myapplication.dynamicfeature">
<dist:module
dist:instant="false"
dist:title="@string/my_feature">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<!-- Fusing disabled -->
<dist:fusing dist:include="false" />
</dist:module>
</manifest>
So how do we actually build a universal APK? The Android Gradle Plugin does not currently expose a task which can, so normally you have to build a bundle with AGP (./gradlew bundle
), and then use bundletool from the command line to extract a universal APK from it.
那么,我们如何真正构建通用APK? Android Gradle插件当前不公开可以执行的任务,因此通常您必须使用AGP构建一个包( ./gradlew bundle
),然后使用命令行中的bundletool从中提取通用APK。
However, the GloballyDynamic Gradle plugin exposes a task that combines these steps, so you can simply run:
但是, GloballyDynamic Gradle插件公开了结合了这些步骤的任务,因此您可以简单地运行:
./gradlew buildUniversalApkForFirebaseRelease
The path to the universal APK produced by this task will be ${buildDir}/outputs/universal_apk/firebaseRelease/universal.apk
.
此任务生成的通用APK的路径为${buildDir}/outputs/universal_apk/firebaseRelease/universal.apk
。
上载APK (Uploading the APK)
In order to upload the universal APK, we need to instruct the Gradle plugin of Firebase App Distribution to select it, this is done as follows:
为了上传通用APK,我们需要指示Firebase App Distribution的Gradle插件选择它,方法如下:
apply plugin: 'com.google.firebase.appdistribution'android {
flavorDimensions 'platform' productFlavors {
firebase {
dimension 'platform'
firebaseAppDistribution {
apkPath "${buildDir}/outputs/universal_apk/firebaseRelease/universal.apk"
}
}
}
}
With this configuration in place, all that’s left to do is to run the upload task exposed by Firebase, like so:
有了此配置后,剩下要做的就是运行Firebase公开的上传任务,如下所示:
./gradlew appDistributionUploadFirebaseRelease
That’s it. After that you can download the APK from Firebase App Tester app and start installing split APKs from our GloballyDynamic Server through GlobalSplitInstallManager#startInstall(GlobalSplitInstallRequest)
.
而已。 之后,您可以从Firebase App Tester应用程序下载APK,然后开始通过GlobalSplitInstallManager#startInstall(GlobalSplitInstallRequest)
从我们的GloballyDynamic服务器安装拆分的APK。
The gist below puts all of the above configuration together in a more complete manner:
以下要点将上述所有配置以更完整的方式组合在一起:
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath "com.google.firebase:firebase-appdistribution-gradle:2.0.1"
classpath 'com.jeppeman.globallydynamic.gradle:plugin:1.0.0'
}
}
apply plugin: 'com.android.application'
apply plugin: 'com.google.firebase.appdistribution'
apply plugin: 'com.jeppeman.globallydynamic'
android {
flavorDimensions 'platform'
productFlavors {
firebase {
dimension 'platform'
firebaseAppDistribution {
apkPath "${buildDir}/outputs/universal_apk/firebaseRelease/universal.apk"
}
}
}
globallyDynamicServers {
firebase {
serverUrl 'http://<ip-to-server>:8080'
username 'johndoe'
password 'my-secret-password'
applyToBuildVariants 'firebaseRelease'
}
}
dynamicFeatures = [':my_dynamic_feature']
}
dependencies {
firebaseImplementation 'com.jeppeman.globallydynamic.android:selfhosted:1.0.0'
}
For a live example, you can have a look at my example project which is published on Firebase App Distribution with dynamic delivery.
作为一个实时示例,您可以看一下我的示例项目 ,该项目以动态交付的形式发布在Firebase App Distribution上。
The method described in this article, apart from the uploading, can be used for enabling dynamic delivery with any other platform that does not support it natively, such as Amazon App Store or Samsung Galaxy Store, you can read more on this on the website.
除了上载之外,本文描述的方法还可以用于与其他本机不支持的平台(例如Amazon App Store或Samsung Galaxy Store)实现动态交付,您可以在网站上阅读更多内容。
In the third and final part of the series we’ll go through how to configure a project for handling multiple dynamic delivery platforms, e.g. Google Play Store, Huawei App Gallery and Firebase App Distribution.
在本系列的第三个也是最后一部分 ,我们将介绍如何配置一个项目来处理多个动态交付平台,例如Google Play商店,华为App Gallery和Firebase App Distribution。
有用的链接 (Useful links)
firebase