文章目录
- 摘要
- 创建module
- 依赖
- Application
- Dynamic Feature Module
- App主模块
- 本地构建调试
- 拆分注意事项
- 问题
- 结束语
摘要
Android App Bundle 是一种发布格式,其中包含您应用的所有经过编译的代码和资源,它会将 APK 生成及签名交由 Google Play 来完成。
Google Play会根据每种设备配置使用您的App Bundle生成并提供经过优化的APK,这样只会下载特定设备所需的代码和资源来运行您的应用。这意味着您无需再构建、签署和管理多个APK以优化对不同设备的支持,同时用户也可以获得更小且更优化的下载文件包。
对于大多数应用项目而言,构建App Bundle以支持提供经过优化的APK并不费力。举个例子,如果您已经按照既定惯例组织管理应用的代码和资源,只需使用Android Studio或命令行来构建已签名的Android App Bundle,并将其上传到Google Play。这样一来,您就能够自动享受到经过优化的APK所带来的种种好处了。
当您使用App Bundle格式发布应用时,您还可以选择使用Play Feature Delivery。它允许您向应用项目中添加功能模块,这些模块包含仅在满足特定条件时提供给应用的功能和资源,或者在运行时通过Play核心库下载供后续使用。
通过使用App Bundle来发布应用,游戏开发者可以利用Play Asset Delivery来分发大量的游戏资产。Play Asset Delivery是Google Play为此而提供的解决方案,它为开发者提供了灵活的分发方式和卓越的性能表现。
创建module
依赖
主模块App需要进行以下依赖:
请改写下面的这段话: ```java implementation 'com.google.android.play:core-ktx:1.8.1' implementation 'com.google.android.play:core:1.10.0' ```Application
主模块App的Application需要继承
改写为: ```java // 添加以下依赖库 implementation 'com.google.android.play:core-ktx:1.8.1' implementation 'com.google.android.play:core:1.10.0' // 在主模块App的Application中继承SplitCompatApplication类 public class MyApplication extends SplitCompatApplication { // 其他代码... } ``` 希望对你有所帮助!SplitCompatApplication
```kotlin ``` ```java class App : SplitCompatApplication() { } ```plugins { id 'com.android.dynamic-feature' ... } dependencies { // code here }属性 说明 <manifest ...
这是您的典型<manifest>
块。xmlns:dist="http://schemas.android.com/apk/distribution"
指定一个新的dist:
XML 命名空间,如下所述。split="split_name"
当 Android Studio 构建 app bundle 时,会包含该属性。因此,您不应自行添加或修改此属性。
定义模块的名称,当应用使用 Google Play 核心库发出按需模块请求时会指定该名称。
Gradle 如何确定该属性的值:
默认情况下,当您使用 Android Studio 创建功能模块时,IDE 会使用您指定的模块名称,在 Gradle 设置文件中将该模块标识为 Gradle 子项目。
当您构建 app bundle 时,Gradle 会使用子项目路径的最后一个元素将此清单属性注入模块的清单。例如,如果您在MyAppProject/features/
目录中创建了一个新功能模块,并指定了“dynamic_feature1”作为其模块名称,IDE 会在settings.gradle
文件中添加':features:dynamic_feature1'
作为子项目。构建 app bundle 时,Gradle 会将<manifest split="dynamic_feature1">
注入模块的清单。android:isFeatureSplit="true | false">
当 Android Studio 构建 app bundle 时,会包含该属性。因此,您不应手动添加或修改此属性。
指定此模块为功能模块。基本模块和配置 APK 中的清单要么省略此属性,要么将其设置为false
。<dist:module
这一新的 XML 元素定义了一些属性,这些属性可确定如何打包模块并作为 APK 分发。dist:instant="true | false"
指定是否应通过 Google Play 免安装体验为模块启用免安装体验。
如果应用包含一个或多个启用免安装体验的功能模块,您也必须为基本模块启用免安装体验。如果您使用的是 Android Studio 3.5 或更高版本,当您创建支持免安装体验的功能模块时,IDE 会为您完成此操作。
在设置<dist:on-demand/>
时,不能将此 XML 元素设置为true
。不过,您仍可使用 Play Core 库请求以免安装体验的形式按需下载支持免安装体验的功能模块。当用户下载并安装您的应用时,设备会默认下载并安装应用的支持免安装体验的功能模块以及基本 APK。dist:
为模块指定一个面向用户的名称。例如,当设备请求确认下载时,便可能会显示该名称。
您需要将此名称的字符串资源包含在基本模块的module_root/src/source_set/res/values/strings.xml
文件中。<dist:fusing dist:include="true | false" /> </dist:module>
指定是否在面向搭载 Android 4.4(API 级别 20)及更低版本的设备的 multi-APK 中包含此模块。
此外,当您使用bundletool
从 app bundle 生成 APK 时,只有将此属性设置为true
的功能模块才会包含在通用 APK 中。通用 APK 是一个单体式 APK,其中包含了应用所支持的所有设备配置的代码和资源。<dist:delivery>
封装自定义模块分发的选项,如下所示。请注意,每个功能模块必须只配置这些自定义分发选项的一种类型。<dist:install-time>
指定模块应在安装时可用。对于未指定自定义分发选项的其他类型的功能模块,这是默认行为。
如需详细了解安装时下载,请参阅配置安装时分发。
此节点还可以指定条件,用于限定要下载模块的设备所需满足的某些要求,例如设备功能,用户所在国家/地区或最低 API 级别。如需了解详情,请参阅配置按条件分发。<dist:removable dist:value="true | false" />
当未设置或设置为false
时,bundletool 会在根据 bundle 生成拆分 APK 时将安装时模块整合到基本模块中。 由于整合会使拆分 APK 的数量减少,因此此设置可以提升应用的性能。
当removable
设置为true
时:安装时模块将不会整合到基本模块中。如果您想要在将来卸载这些模块,请将其设置为true
。 不过,配置过多可移除的模块可能会导致应用的安装时间增加。
默认为false
。只有当您想要针对某个功能模块停用融合功能时,才需要在清单中设置此值。
注意:只有在使用 Android Gradle 插件 4.2 或从命令行使用 bundletool v1.0 时,才能使用此功能。</dist:install-time>
<dist:on-demand/>
指定应以按需下载的形式分发模块。也就是说,模块在安装时不会下载,但应用可以稍后请求下载。
如需详细了解按需下载,请参阅配置按需分发。</dist:delivery>
<application [android:hasCode](https://developer.android.google.cn/guide/topics/manifest/application-element#code)="true | false"> ... </application>
如果功能模块没有生成 DEX 文件(也就是说,它不包含之后编译成 DEX 文件格式的代码),您必须执行以下操作(否则,您可能会遇到运行时错误):
1. 在功能模块的清单中将android:hasCode
设置为"false"
。
2. 将以下内容添加到基本模块的清单中:<application<br> android:hasCode="true"<br> tools:replace="android:hasCode"><br> ...<br></application>
App主模块
- 检查需要调用的模块是否安装
- 如果没有安装则下载,这个过程是googlePlayCore库处理的
- 如果已安装通过全类名进行调用访问
class MainActivity : AppCompatActivity() { val muduleName = "login_feature" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val manager = SplitInstallManagerFactory.create(this) //安装模块监听 manager.registerListener { val status=it.status() Log.d("TAG", it.toString()) } loginModule.setOnClickListener { //模块已安装 if (manager.installedModules.contains(muduleName)) { val intent = Intent() val componentName = ComponentName(packageName, "cn.xxstudy.login_feature.LoginActivity") intent.component = componentName startActivity(intent) } else { //安装模块: GlobalScope.launch { try { manager.requestInstall(listOf(muduleName)) } catch (e: Exception) { MainScope().launch { Toast.makeText(this@MainActivity, "该模块还未安装成功", Toast.LENGTH_SHORT).show() } } } } } } override fun attachBaseContext(newBase: Context?) { super.attachBaseContext(newBase) //必须添加 SplitCompat.install(this) }}
manager.registerListener
安装监听status说明:
SplitInstallSessionStatus.CANCELED -> 模块下载已被取消SplitInstallSessionStatus.CANCELING -> 正在取消下载SplitInstallSessionStatus.DOWNLOADING -> Installing( state.bytesDownloaded.toDouble() / state.totalBytesToDownload)下载进度SplitInstallSessionStatus.DOWNLOADED -> 下载完成但未安装SplitInstallSessionStatus.FAILED ->下载或安装失败SplitInstallSessionStatus.INSTALLED -> 安装完成SplitInstallSessionStatus.INSTALLING -> 安装中SplitInstallSessionStatus.PENDING -> 将要进行下载SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> 需要用户确认,大于10MSplitInstallSessionStatus.UNKNOWN -> 未知错误
本地构建调试
- 调试
接下来运行后login_fuature模块就不会被安装了,当然也是无法使用该模块的 - 生成.aab
- Build→Build Bundle(s)/ APK→Build Bundle(s)
- Build→Generate Sigend Bundle Apk →Android App Bundle
- 通过BundleTool生成.apks
java -jar F:\Temp\bundletool.jar build-apks --local-testing --bundle=app/build/outputs/bundle/debug/app-debug.aab --output=app.apks
- 安装
java -jar F:\Temp\bundletool.jar install-apks --apks=app.apks
编译过程:
E:\Android\Simple\Temp\PlayCoreDemo>java -jar F:\Temp\bundletool.jar build-apks --local-testing --bundle=app/build/outputs/bundle/debug/app-debug.aab --output=app.apksINFO: The APKs will be signed with the debug keystore found at 'C:\Users\DELL\.android\debug.keystore'.E:\Android\Simple\Temp\PlayCoreDemo>java -jar F:\Temp\bundletool.jar install-apks --apks=app.apksThe APKs have been extracted in the directory: C:\Users\DELL\AppData\Local\Temp\1369014306226025145The APKs have been extracted in the directory: C:\Users\DELL\AppData\Local\Temp\1369014306226025145ADB << rm -rf '/sdcard/Android/data/cn.xxstudy.demo/files/local_testing'ADB >> OKADB << mkdir -p '/sdcard/Android/data/cn.xxstudy.demo/files/local_testing' && rmdir '/sdcard/Android/data/cn.xxstudy.demo/files/local_testing' && mkdir -p '/sdcard/Android/data/cn.xxstudy.demo/files/local_testing'ADB >> OKPushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-xxhdpi.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-master.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ca.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-da.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-fa.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ja.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ka.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-pa.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ta.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-nb.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-be.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-de.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ne.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-te.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-af.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-bg.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-th.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-fi.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-hi.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-si.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-vi.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-kk.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-mk.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-sk.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-uk.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-el.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-gl.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ml.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-nl.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-pl.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-sl.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-tl.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-am.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-km.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-bn.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-in.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-kn.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-mn.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ko.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-lo.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ro.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-sq.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ar.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-fr.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-hr.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-mr.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-or.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-sr.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-tr.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ur.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-as.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-bs.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-cs.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-es.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-is.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ms.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-et.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-it.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-lt.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-pt.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-eu.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-gu.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-hu.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ru.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-zu.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-lv.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-sv.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-iw.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-sw.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-hy.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-ky.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-my.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-az.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-uz.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-en.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/base-zh.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/login_feature-xxhdpi.apk"Pushed "/sdcard/Android/data/cn.xxstudy.demo/files/local_testing/login_feature-master.apk"
这个时候就将login_feature模块导入到设备中去了,执行manager.requestInstall(listOf(muduleName))
后就会进行安装了(一般是通过googlePlay下载,我们这里是使用BundleTool本地测试)
拆分注意事项
-
当
dynamic-feature-module
引用base module
资源时,不能直接使用R.drawable
,而是需要使用[base module package name].R.drawable
的方式。 -
请勿以数字开头命名动态功能模块(dynamic-feature-module)项目。
-
需要注释掉
build.gradle
中的splite {abi{}}
,否则会出现java.lo.lOException: Cannot find PROCESSED_ RES output for Main{type=MAIN, fullName=flavor1Debug, filters=I, versionCode=-1, versionName=null}
异常。 -
base module
无法访问dynamic-feature-module
中的id
。在dynamic-feature-module
的R.java文件中,资源索引id 的值为 0x7e ,而在 base module 的 R.java i d c ode c ode>
还木有评论哦,快来抢沙发吧~