前段时间升级 qq sdk 实现第三方登录功能. 在用户手机未安装 QQ 时, 会引导用户下载安装. Google Play 商店以它的规则为由下架了我们的 App. 这个问题很多开发者都遇到过, 毕竟我们都得听 Google 的, 规则制定者不是开玩笑的. 经过沟通, 坚决不让重新上架, 提议换个包名拷贝代码, 重新上传一个新的应用. 我们当时按照这个简单的方法上架了紧急修复的 App, 但是考虑到后续功能迭代以及两个项目的维护成本, 需要寻求一个新的方式来完成这些操作.
背景
公司有个老版本的读书软件一直在线上, 但是很久没维护了. 这次事故正好可以把老的软件作为壳子, 替换成目前维护的新的 App. 这样可以避免用户流失 (Google Play 渠道用户量本身就不是很大).
修改应用 ID
每个 Android 应用均有一个唯一的应用 ID, 像 Java 软件包名称一样, 如 com.example.myapp. 此 ID 可以在设备上和 Google Play 商店中对您的应用进行唯一标识. 如果您想要上传新版本的应用, 应用 ID(以及使用它签署的证书) 必须与原始 APK 相同 - 如果您更改应用 ID,Google Play 商店会将 APK 视为完全不同的应用. 所以您发布应用后, 绝不应更改应用 ID.
基于这个原则, 我们实现的方式很简单, 配置编译变体, 指定特定的 applicationId
应用是基于 Walle https://github.com/Meituan-Dianping/walle 进行多渠道打包的, 那么我们需要配置两个 productFlavors , 其中一个是对 Google Play 商店的定制版, 另一个是使用 [Wally] 进行多渠道打包的基础 apk. 当然如果你没有做多渠道打包, 那么也至少需要配置两个, 毕竟你需要一个 未变体的 Apk 供其他渠道使用.
- flavorDimensions "default" // 当只有一个时, 不需要在 Flavors 中重复指定
- productFlavors {
- google { // Google Play 商店的定制版
- applicationId "com.yun.reader"
- }
- yun { // 基础版本 apk
- applicationId "com.yun.news"
- }
- }
好了, 现在我们可以编译两个不同的 apk 了.
指定不同的签名
老版本的 Apk 签名和新开发 Apk 并不是用的同一个签名文件, 所以, 在 productFlavors 中需要指定签名文件.
首先我们需要定义签名文件:
- Android {
- signingConfigs {
- yun {
- if (System.getenv("YUN_NEWS_KEYSTORE_FILE") != null) {
- storeFile file(System.getenv("YUN_NEWS_KEYSTORE_FILE"))
- storePassword System.getenv("YUN_NEWS_KEYSTORE_PASSWORD")
- keyAlias System.getenv("YUN_NEWS_KEY_ALIAS")
- keyPassword System.getenv("YUN_NEWS_KEY_PASSWORD")
- }
- }
- google {
- if (System.getenv("YUN_READER_KEYSTORE_FILE") != null) {
- storeFile file(System.getenv("YUN_READER_KEYSTORE_FILE"))
- storePassword System.getenv("YUN_READER_KEYSTORE_PASSWORD")
- keyAlias System.getenv("YUN_READER_KEY_ALIAS")
- keyPassword System.getenv("YUN_READER_KEY_PASSWORD")
- }
- }
- }
- }
看上面的代码你会发现, 我们的路径都是放在远程编译服务器的环境变量中的, 你也可以存放在本地的 local.properties 中, 具体的操作可以自行 Google.
两个签名定义完成, 使用起来很简单:
- flavorDimensions "default" // 当只有一个时, 不需要在 Flavors 中重复指定
- productFlavors {
- google { // Google Play 商店的定制版
- applicationId "com.yun.reader"
- signingConfig signingConfigs.google
- }
- yun { // 基础版本 apk
- applicationId "com.yun.news"
- signingConfig signingConfigs.yun
- }
- }
这里有几点需要注意
signingConfigs 声明需要放在 productFlavors 之前, 否则找不到对应的自定义签名
productFlavors 中已指定签名, 需要把
buildTypes -> release
下面指定的签名删除, 否则在编译 Release 版本时会覆盖掉 productFlavors 中指定的签名.
微信支付相关报错
我们替换掉 applicationId 之后, 其实 Context.getPackageName() 方法返回的值也跟着变化了, 那么, 微信支付根据这个包名去查找相关的配置肯定是找不到的, 所以, 我们需要将 WXEntryActivity 和 WXPayEntryActivity 拷贝一份到 com.yun.reader.wxapi 包名下面. 记得在 manifests 中用绝对路径声明这两个 Activity.
有的同学就算这样配置之后, 微信支付仍然会报错, 因为我们改了 applicationId 导致以前默认是 news 的相关配置对 reader 是不适用的. 所以我们需要为以前的代码背锅, 将所有写死的地方改为配置型.
例如: 在微信支付时, 需要传递 app_code 字段, 让服务器在生成订单时, 知晓当前的应用是哪一个, 方便后续的校对. 如果服务器直接写死了是 news, 那么在后续匹配中, news 申请的 支付码和 packageName 不能匹配, 那么支付失败是必然的.
类似的问题还有 cookie , 与 web 端交互的时候, 对方会根据 cookie 来判别应用, 所以 Web 端也需要做兼容. RN 端类似.
参考:
来源: https://juejin.im/post/5c6cce54e51d453669066f9f