Google Play 下架 App 之后的替身制作

4,759 阅读4分钟

前段时间升级 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 进行多渠道打包的,那么我们需要配置两个 productFlavors ,其中一个是对 Google Play 商店的定制版,另一个是使用 Wally 进行多渠道打包的基础apk。当然如果你没有做多渠道打包,那么也至少需要配置两个,毕竟你需要一个 未变体的 Apk 供其他渠道使用。

flavorDimensions "default" // 当只有一个时,不需要在 Flavors 中重复指定
productFlavors {
    google { // Google Play 商店的定制版
        applicationId "com.yun.reader"
    }

    yun { // 基础版本 apk
        applicationId "com.yun.news"
    }
}

好了,sync之后,现在我们就可以在 Gradle->:app->Tasks->build 中编译两个不同的 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
    }
}

这里有几点需要注意

  1. signingConfigs 声明需要放在 productFlavors 之前,否则找不到对应的自定义签名
  2. productFlavors 中已指定签名,需要把 buildTypes -> release 下面指定的签名删除,否则在编译 Release 版本时会覆盖掉 productFlavors 中指定的签名。

微信支付相关报错

我们替换掉 applicationId 之后,其实 Context.getPackageName() 方法返回的值也跟着变化了,那么,微信支付根据这个包名去查找相关的配置肯定是找不到的,所以,我们需要将 WXEntryActivityWXPayEntryActivity 拷贝一份到 com.yun.reader.wxapi 包名下面。记得在 manifests 中用绝对路径声明这两个 Activity。

有的同学就算这样配置之后,微信支付仍然会报错,因为我们改了 applicationId 导致以前默认是 news 的相关配置对 reader 是不适用的。所以我们需要为以前的代码背锅,将所有写死的地方改为配置型。

例如: 在微信支付时,需要传递 app_code 字段,让服务器在生成订单时,知晓当前的应用是哪一个,方便后续的校对。如果服务器直接写死了是 news, 那么在后续匹配中,news 申请的 支付码和 packageName 不能匹配,那么支付失败是必然的。

类似的问题还有 cookie , 与Web端交互的时候,对方会根据cookie来判别应用,所以Web端也需要做兼容。RN 端类似。

AndroidManifest 中根据 productFlavors 指定特定变量

例如:QQ 登录,每个 App 有不同的 scheme,本文中用 applicationId 来伪装成两个不同的 App, scheme 需要配置不同的 tencent_id。这里可以利用manifestPlaceholders来实现这个需求。

首先需要在 gradle 中声明:

defaultConfig {
    manifestPlaceholders = [tencent_id: "1000000000", package_name: "com.yun.news"]
}

如果 manifestPlaceholders 包含多个值,需要用 , 隔开。

productFlavors中配置很简单

productFlavors {
    google {
        applicationId "com.yun.reader"
        signingConfig signingConfigs.google
        manifestPlaceholders.tencent_id = "1000000000"
        manifestPlaceholders.package_name = "com.yun.reader"
    }

    yun {
        applicationId "com.yun.news"
        signingConfig signingConfigs.yun
        manifestPlaceholders.tencent_id = "1000000001"
        manifestPlaceholders.package_name = "com.yun.news"
    }
}

manifest 中使用方式如下:

<activity
    android:name="com.tencent.tauth.AuthActivity"
    ...>
    <intent-filter>
        ...
        <data android:scheme="tencent${tencent_id}" />
    </intent-filter>
</activity>

这样我们就可以根据不同的 productFlavors 来配置特性字段了。 类似的有:MIPush(如果写死包名,手机不能同时安装两个变体),

参考:developer.android.com/studio/buil…