关于 Portal 和 Bundle 工程

组件化框架是指 mPaaS 基于 OSGi(Open Service Gateway Initiative,开放服务网关倡议)技术把一个 App 划分成业务独立的一个或多个 Bundle 工程以及一个 Portal 工程的框架。mPaaS 会对每个 Bundle 工程的生命周期和依赖加以管理,使用 Portal 工程把所有的 Bundle 工程包合并成一个可运行的 .apk 包。

mPaaS 框架适合团队协同开发 App,并且该框架包含组件初始化、埋点等功能,方便您轻松接入 mPaaS 组件。

Bundle 工程

传统的原生工程由一个主模块或是一个主 module 和若干个子 module 组成,而一个 mPaaS Bundle 工程一般由一个名为 app 的主 module 和若干个子 module 组成。

例如,在支付宝中,一个 Bundle 一般由一个名为 app 的主 module 和以下三个子 module 组成:

  • api:纯代码接口,interface 的定义。

  • biz:interface 的实现。

  • ui:activity,自定义 view 等。

说明

至少有一个名为 api 的子 module。如果没有子 module, 就打不出 Bundle 的接口包,并且该 Bundle 不能被其他 Bundle 依赖。

通过阅读本文,您将从以下方面了解 Bundle 工程:

Bundle 与传统工程区别

Bundle 本质上也是一个原生工程,只是在 工程主 Module子 Modulebuild.gradle 中多了 mPaaS 的 Apply 插件,具体差别体现在以下方面:

  • 工程根目录 build.gradle

  • 主 module 的 build.gradle

  • 子 module 的 build.gradle

工程根目录 build.gradle

在工程根目录的 build.gradle 中,增加了对 mPaaS 插件的依赖:

说明

因功能迭代,插件版本可能会不断增加。

classpath 'com.alipay.android:android-gradle-plugin:3.0.0.9.13'
image.png

主 module 的 build.gradle

在主 module 的 build.gradle 中,增加了 mPaaS Bundle Apply 插件 的声明,表示该工程为 Bundle 工程,Bundle 配置如下:

apply plugin: 'com.alipay.bundle'

主 module 的 build.gradle 中还增加了以下配置:image

其中:

  • version:该 Bundle 的 version。

  • group:该 Bundle 的 groupid。

  • exportPackages: 描述当前 Bundle 工程所有的类在哪些包名下面,包名可以取合集。非静态链接的 Bundle 必须填写 exportPackages,否则会出现类加载不到的问题。例如,如果所有的代码在 com.alipay.democom.alipay.bundle 下,那么在 exportPackages 中就可以写 com.alipay,也可以写 com.alipay.democom.alipay.bundle。包名不宜过长或过短。

  • initLevel:框架启动时加载该 Bundle 的时机。时机范围在 0-100,数字越小表示越早加载。其中 11110000 为使用时加载,即懒加载。

  • packageId:描述当前 Bundle 的资源的 ID,供 aapt 打包时需要。由于是多 Bundle 架构,每个 Bundle 的 packageId 必须唯一,不可与其它 Bundle 的 packageId 重复。目前 mPaaS 已经使用的 packageId 如下:

Bundle

packageId

com.alipay.android.phone.thirdparty:androidsupportrecyclerview-build

28

com.alipay.android.phone.mobilesdk:framework-build

30

com.alipay.android.phone.rome:pushservice-build

35

com.alipay.android.phone.sync:syncservice-build

38

com.alipay.android.phone.wallet:nebulabiz-build

41

com.alipay.android.phone.mobilecommon:share-build

42

com.alipay.android.phone.wallet:nebulacore-build

66

com.alipay.android.mpaas:scan-build

72

com.alipay.android.phone.wallet:nebula-build

76

com.alipay.android.phone.securitycommon:aliupgrade-build

77

dependencies 中会添加对 mPaaS 的如下依赖:

dependencies {
    compile project(":api")
    apt 'com.alipay.android.tools:androidannotations:2.7.1@jar'
    //mPaaS dependencies
    provided 'com.alipay.android.phone.thirdparty:fastjson-api:1.1.73@jar'
    provided 'com.alipay.android.phone.thirdparty:androidsupport-api:13.23@jar'
}

子 module 的 build.gradle

在子 module 的 build.gradle 中,增加了 mPaaS Apply 插件 的声明,表示该工程为 Bundle 的子 module 工程,最终会打出该 Bundle 的接口包。

apply plugin: 'com.alipay.library'

dependencies 中会添加对 mPaaS 的如下依赖:

dependencies {
    apt 'com.alipay.android.tools:androidannotations:2.7.1@jar'
    //mPaaS dependencies
    provided "com.alipay.android.phone.thirdparty:utdid-api:1.0.3@jar"
    provided "com.alipay.android.phone.mobilesdk:framework-api:2.1.1@jar"
}

Bundle 属性

本框架的 Bundle 属性设计思路参考 OSGi 的 Bundle,但比 OSGi 的 Bundle 更简洁和轻巧。

以下表格列出 Bundle 属性及其解释:

属性

解释

Bundle-Name

Bundle Name,来自于由 build.gradle 文件中的 groupsettings.gradle 中定义的 name

Bundle-Version

Bundle Version,来自于 build.gradle 文件中的 version

Init-Level

Bundle 的加载时机,来自于 build.gradle 文件中定义的 properties:init.level

Package-Id

Bundle 资源的 packageid,来自于 build.gradle 文件中定义的 properties。

Contains-Dex

是否包含 dex,编译插件自动判断。

Contains-Res

是否包含资源,编译插件自动判断。

Native-Library

包含的 so 文件有哪些,编译插件自动判断。

Component-Name

来自于 AndroidManifest.xml 文件中定义的 ActivityServiceBroadcastReceiverContentProvider

exportPackages

该 Bundle 的所有的类所在的包名,参考主 module 的 build.gradle

Bundle 接口包

一个 Bundle 有可能包含多个 子 Module,如 biz, api, ui。在编译打包 Bundle 的时候,每个子 module 都会分别生成一个格式为 .jar 接口包,其中 api 接口包可以被其他 Bundle 使用。

在打包的同时,也会生成一个 Bundle 工程包,这个工程包包含所有的子 module,工程包可以被 Portal 工程使用,工程包在 Portal 中编译,最后生成 .apk 包。

  • 由 Bundle 的 子 module 打出来的接口包,只对外提供定义的 java/kotlin 接口类,不包含其他如 res 下的资源,且这些接口包仅限于来自名称为 api 的 module。

  • 各 Bundle 工程直接通过 Bundle 的接口包互相依赖,需要在 bundle 的 build.gradle 中的 dependency 配置依赖 api 接口。例如,Bundle A 依赖 Bundle B 中的 bapi 子 module,那么在 Bundle A 相应的子 module 的 build.gradle 中的 dependency 配置对 bapi 的依赖。

    provided "com.alipay.android.phone:bundleB:1.0.1:bapi@jar"
  • 依赖中涉及的 groupId:artifactid:version:classifier 分别对应 Bundle 中声明的 group,name,version,子 module 的名字。

  • Bundle 的 name 默认为主 module 的文件夹名,可以在 settings.gradle 中修改,如下代码所示,其中 app 为主 module 的工程名:

    include ':api', ':xxxx-build'
    project(':xxxx-build').projectDir = new File('app')

Bundle 工程包

  • 由整个 Bundle 工程打出来的 .jar 包其实是一个 .apk 格式的文件,但是也以 .jar 结尾,如 framework-build.jar

  • 如果要在 Portal 中依赖 Bundle,则在 Portal 主 module 的 build.gradle 中的 dependency 中声明依赖 Bundle 包,如下所示:

    dependencies {
        bundle "com.alipay.android.phone.mobilesdk:framework-build:version@jar"
        manifest "com.alipay.android.phone.mobilesdk:framework-build:version:AndroidManifest@xml"
    }
  • 对 Bundle 打包分两种:debug 包和 release 包,在 Portal 依赖 Bundle 的 debug 包时,需要在 debug 包中额外加上 :raw

    • 当 Portal 依赖 Bundle 的 debug 包时,bundle "com.alipay.android.phone.mobilesdk:framework-build:version:raw@jar"

    • 当 Portal 依赖 Bundle 的 release 包时,bundle "com.alipay.android.phone.mobilesdk:framework-build:version@jar"

说明
  • 打 Portal 包时,需要确定以下内容:

    • 哪些 Bundle 是要打在 app 的主 dex 中。静态链接,有 ContentProvider 的 Bundle 必须放在静态链接中。

    • 哪些动态加载。如果 App 不大,建议都在主 dex 中。

  • 如果想把 Bundle 的代码打进主 dex 中,则需要在 Portal 的 slinks 文件中配置当前 Bundle。配置内容为:groupId-artifactId。如果以 -build 结尾,则去掉 -build。例如, groupId 为 com.mpaas.group,artifactId 为 testBundle-build,则需要在 slinks 文件中添加一行:com.mpaas.group-testBundle

  • 静态链接:把 Bundle 的代码打进 apkclasses.dex 或者 classes1.dexclasses2.dex 等中,以便工程启动时就可以加载 Bundle 中的类。

Portal 工程

Portal 工程把所有的 Bundle 工程包合并成一个可运行的 .apk 包。

Portal 与传统工程区别

Portal 和传统开发中的工程的区别体现在 build.gradle

  • 工程根目录 build.gradle

  • 主 module 目录 build.gradle

工程根目录 build.gradle

如下图所示,classpath 多了一个 com.alipay.android:android-gradle-plugin:2.1.3.2.7 插件 :

说明

因功能迭代,插件版本可能会不断增加。

image.png

该插件中包含 Portal 插件,Portal 插件可以在打包过程中把各 Bundle 合并。

  • 合并 Bundle 的 .jar

  • 合并 Bundle 的 AndroidManifest

主 module 目录 build.gradle

多了 mPaaS Apply Portal 插件 的声明,表示该工程为 Portal 工程。Portal 配置如下:

apply plugin: 'com.alipay.portal'

同时,在 dependencies 中添加相应的对 Bundle 的依赖。dependencies中的语句是 bundle 和 manifest 的声明,用来表示 Portal 依赖了哪些 Bundle 或 manifest:

image
重要
  • 通常 Portal 下面不写代码。

  • 以下几种在 Bundle 工程中使用的资源(style/drawable/string等),必须放在 Portal 工程中,否则会导致编译/运行时找不到资源:

    • AndroidManifest.xml 中使用的资源。

    • 传递给 NotificaionManager 使用的资源。

    • 通过 getResources().getIdentifier() 方法使用的资源。

    • 引用的第三方 AAR 包中如有以上三种情况,也需要解压 AAR,将对应的资源复制一份放到 Portal 工程中。

工程依赖

一个 基于 mPaaS 框架 的 App 包括 一个或多个 Bundle一个 Portal。一个 App 有且只能有一个 Portal 工程,但可以有多个 Bundle 工程。

通过 mPaaS 插件,Portal 工程把所有的 Bundle 工程包合并成一个可运行的 .apk 包。合并后,该插件把 Bundle 工程部署至指定的仓库地址。该仓库地址在 Bundle 的主 module 中的 build.gradle 中定义,如下代码所示:

uploadArchives {
    repositories {
        mavenLocal()
    }
}

该仓库地址是上传至本地的 ~/.m2 仓库地址。您也可以添加自定义的仓库地址,如下所示:

mavenDeployer {
    mavenLocal()
    repository(url: "${repository_url}") {
        authentication(userName: 'userName', password: 'userName_pwd')
    }
    snapshotRepository(url: "${repository_url}") {
        authentication(userName: 'userName', password: 'userName_pwd')
    }
}

上传之后,Bundle 以 groupid:artifactid:version:classifier@type 的形式存在指定的仓库中。因此,只要在 Portal 最外层主 module 的 build.gradle 中声明 dependency 就可指定各 Bundle 依赖,如下代码所示:

dependencies {
    bundle 'com.alipay.android.phone.mobilesdk:quinox-build:2.2.1.161221190158:nolog@jar'
    manifest 'com.alipay.android.phone.mobilesdk:quinox-build:2.2.1.161221190158:AndroidManifest@xml'
}

此外,Bundle 工程之间的相互依赖也需要在 Bundle 的最外层的 build.gradle 中声明仓库依赖地址。

重要

以下配置中的 usernamepassword 不是控制台的登录用户名和密码。您必须 提交工单 获取这两个值。其中:

  • mavenLocal() 描述依赖的本地仓库地址。

  • maven{} 声明依赖的远程仓库地址。

allprojects {
    repositories {
        mavenLocal()
        mavenCentral()
        maven {
            credentials {
                username "{username}"
                password "{password}"
            }
            url "http://mvn.cloud.alipay.com/nexus/content/repositories/releases/"
        }
    }
}

Bundle 编译打包结果

使用 mPaaS 插件编译打包后,一个 Bundle 会生成一个工程包(是一个 .jar 包)。更多信息,请参考 Bundle 工程包Bundle 接口包

工程包会以 groupid:artifactid:version:classifier@type 的形式发布到指定仓库中。发布仓库地址在 Bundle 主 module 中的 build.gradle 中定义,示例如下:

uploadArchives {
    repositories {
        mavenLocal()
    }
}

上述配置指定发布仓库为本地 Maven 仓库(mavenLocal)。如需修改本地 Maven 仓库地址(默认 ~/.m2)或增加发布仓库,请参见 配置发布仓库

添加 Bundle 依赖

您可以在 Portal 中添加 Bundle 依赖,也可以在 Bundle 中添加其他 Bundle 依赖。只需:

  1. 在 Portal 或 Bundle 最外层的 build.gradle 中声明依赖仓库地址。依赖仓库应和上文 Bundle 发布仓库相对应。依赖仓库的配置方法,请参见 配置依赖仓库

  2. 在 Portal 或 Bundle 主 module 的 build.gradle 中声明 dependencies 依赖。如添加 Bundle(quinox)依赖的示例如下:

dependencies {
    bundle 'com.alipay.android.phone.mobilesdk:quinox-build:2.2.1.161221190158:nolog@jar'
    manifest 'com.alipay.android.phone.mobilesdk:quinox-build:2.2.1.161221190158:AndroidManifest@xml'
}

相关链接