多渠道打包——同一台设备上同时安装你的正式与测试环境APP

我们的APP请求的服务器经常会区分正式环境和测试环境,正常情况下一台设备上不能安装重复applicationid的app,这样如果我们测试正式环境和测试环境的时候回经常要在正式版和测试版中反复卸载安装,这样很麻烦,不方便测试,我们会想要是能在同一台设备上都安装上测试和正式的app,那么就能比较愉快地在正式环境和测试环境中切换了。

多渠道打包

既然一台设备上不能安装重复applicationid的app,那么我们就只能正式环境下对应一个applicationid,测试环境下对应一个测试版的applicaitionid。这个applicationid指的就是app module下的gradle文件中的applicaitonid。
那么需要每次打包的时候我们都去手动修改这个applicationid吗?答案是不用的,gradle可以配置productFlavors即所谓的多渠道打包来帮我们完成这件事。废话不多说接下来说具体的步骤。

1.首先贴一下我的配置完整的gradle文件

apply plugin: 'com.android.application'
apply from: 'tinker-support.gradle'//tinker没使用tinker的热更新不需要配置
apply plugin: 'bugly'
bugly {
    appId = "4892***ed"  //buglyid 如果你的工程没引进bugly sdk不需要配置
    appKey = "da76***********e46046a189"
    appVersion = "${rootProject.ext.versionName}"
}
android {
    signingConfigs {
        config {
            keyAlias KEY_ALIAS
            keyPassword KEY_PASSWORD
            storeFile file(STORE_FILE)
            storePassword STORE_PASSWORD

            v1SigningEnabled true
            v2SigningEnabled false
        }
    }

    compileSdkVersion 27
    buildToolsVersion '27.0.3'
    defaultConfig {
        applicationId "com.tindliang.www.demo"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.appTargetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    
        multiDexEnabled true
        signingConfig signingConfigs.config

     
        flavorDimensions "test"//1.配置dimension 
    }
//2配置buildTypes,这里的release的debuggable 一定要设为false
    buildTypes {
        release {
            minifyEnabled true//开启混淆 你若不需要混淆可设为false 跟多渠道打包没什么关系
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.config
            debuggable false
        }
    }
  //3.配置多渠道,这里配置2个渠道:dev测试环境,prod正式环境
    productFlavors {
        dev {
            applicationId "com.tindliang.www.demotest"//配置测试环境的applicationid 要和正式的不一样
            resValue "string", "app_name", "测试版"//设置测试版的appname,然后记得将strings文件中的app_name删掉
            buildConfigField "boolean", "SERVER_DEBUG", 'true'//给BuildConfig类增加一个boolean类型的字段,用来标记服务器地址是否是测试环境的
            dimension "test"//配置第一步中的flavorDimensions 配置的“test”,这里我们没有额外的需求不多做配置          
        }
        prod {
            applicationId "com.tindliang.www.demo"
            resValue "string", "app_name", "正式版"
            buildConfigField "boolean", "SERVER_DEBUG", 'false'
            dimension "test"
        }
    }

    android.applicationVariants.all { variant ->
//4.配置正式和测试环境下不同的buglyid和key 如果你没有引用bugly sdk 可不做此配置
        variant.outputs.all {
            def buglyAppId = null
            def buglyAppKey = null
            if (variant.flavorName == "dev") {//测试环境
                buglyAppId = 'f02d2adb52'
                buglyAppKey = '5e4d4ca8-8343-4909-ae2c-4be960c50a1c'
            } else if (variant.flavorName == "prod") {//正式环境
                buglyAppId = '4892fba0ed'
                buglyAppKey = 'da76b97c-f5d3-4a0d-9321-77e46046a189'
            }
            if(buglyAppId != null) {
                variant.ext.buglyAppId = buglyAppId
            }
            if (buglyAppKey != null) {
                variant.ext.buglyAppKey = buglyAppKey
            }
      //5.配置打包好的apk文件名称,如果你没有需要可不做此配置
            outputFileName = "${rootProject.getName()}_${buildType.name}_${variant.flavorName}_v${rootProject.ext.versionName}.apk"
        }
    }
}


dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "com.android.support:appcompat-v7:${V7_APPCOMPAT_VERSION}"
    implementation "com.android.support:recyclerview-v7:${RECYCLER_VIEW_SUPPORT_VERSION}"
    implementation "com.android.support:design:${DESIGIN_SUPPORT_VERSION}"
    implementation "com.android.support.constraint:constraint-layout:${CONSTRAIT_SUPPORT_VERSION}"
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

步骤说明

1.首先在app module 下的gradle文件中配置了 flavorDimensions "test"(gradle3.0以后要求),这里我们对此没有额外的需求,可以不多做配置,有兴趣的同学可打开这篇文章自行了解。

2.配置buildTypes,buildTypes默认就已经有 release和debug 两种,如果你不开启混淆也就是不配置minifyEnabled 为true的话,那么你甚至可以直接跳过这步,什么都不配,默认就好。但是如果配置了,记得不要将debuggable 设为true,其默认值也是false(如果设为true,那么你的正式上线的app也就能被adb调试,这是应该不被允许的,所以一些安全扫描漏洞也会扫描此项并建议你要设为false)

3.重点关注productFlavors ,这里我们配置了2个渠道,注释其实已经说明得很清楚了,其中的
buildConfigField "boolean", "SERVER_DEBUG", 'true',当你重新构建工程(Build->RebuildProject)的时候,会自动生成一个类
com.tindliang.www.demo.BuildConfig(你的applicationid.BuidlConfig),自动生成的代码如下

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");//对应buildTypes的debuggable 
  public static final String APPLICATION_ID = "com.tindliang.www.demo";
  public static final String BUILD_TYPE = "debug";//对应我们buildTypes的名称
  public static final String FLAVOR = "prod";//对应我们productFlavors 中的配置的渠道名称
  public static final int VERSION_CODE = 4;
  public static final String VERSION_NAME = "1.0.1";
  // Fields from product flavor: prod
  public static final boolean SERVER_DEBUG = false;//对应我们productFlavors 中的配置的“SERVER_DEBUG”
}

最后一个字段SERVER_DEBUG 即为你在多渠道中声明的字段。
注意当你的清单文件中有引用到applicationid的地方,比如

      <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.tindliang.www.demo.fileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>

需要将写死的applicationid即com.tindliang.www.demo替换为${applicationId},修改后的应为如下这种写法

    <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.fileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>

剩下的4和5的步骤如注释说的,没有需求的可以不做此配置

当你在gradle文件中做好上述配置后,重新构建工程。

RebuildProject.png

构建完成会生成步骤3说的BuildConfig和
variants.png

dev前缀就是我们gradle多渠道中配置的测试环境渠道。
prod前缀就是我们gradle多渠道中配置的测试环境渠道。
Debug后缀就是我们gradle中配置的debug buildType。
Release后缀就是我们gradle中配置的release buildType。

我们看studio右上角的gradle task也会多出来2个task:assembleDev和assembleProd

gradle task.png

这些东西我们稍后再做说明,现在我们只差一步就可以实现开头要的效果。

配置服务器地址

声明一个类来保存服务器地址的静态变量

public class Constants{
 /**
     * 控制此版本是否为平时测试版本,{@link BaseConstants#SERVER_DEBUG}为false的时候,DEBUG应该设置为false
     * 即上线的正式版 DEBUG应该设置为false
     */
    public static boolean DEBUG = BuildConfig.DEBUG;
    /**
     * 控制服务器URL是否为正式地址 false为正式地址,true为测试地址,打包的时候需要切换
     */
    public static boolean SERVER_DEBUG = BuildConfig.SERVER_DEBUG;

    /*----------------debug--------------------------------*/
    public static final String DEBUG_API_SERVER_URL = "https:你的测试服务器URL"


    /*-----------------release-----------------------------*/
    public static final String RELEASE_API_SERVER_URL = "https:你的正式服务器URL"

    //最终引用的数据请求接口的地址
    public static final String API_SERVER_URL = SERVER_DEBUG ? DEBUG_API_SERVER_URL : RELEASE_API_SERVER_URL;
}

我们这里DEBUG用来标记是否是测试设备具体用途比如在你的app启动页上判断是否显示一些信息,比如DEBUG为true证明是测试设备那么我们可以显示出当前app的versionName,versionCode之类的信息
SERVER_DEBUG 当然就是来判断引用正式还是测试的服务器地址了。

好了到目前为止准备工作都已经做完了,接下来说我们该怎么打包和直接run 不同环境的app module

直接运行不同环境的app

我们配置好环境后,比如我们要直接运行测试环境的,那么你选中前面截图中的variants的devDebug


测试环境.png

有人可能会问和选devRelease有什么区别,区别就在于选devRelease后,BuildConfig中的DEBUG字段会变成false,那么根据你的业务需求看你需要是这个字段是true还是false来选择是要Debug还是Release,dev和prod同理。

选中后工程会自动重新构建,等工程build完毕后,运行项目。再选prodDebug,再运行项目,就会发现你的设备上安装上了正式和测试环境两种个app,他们的区别只是服务器引用地址不一样而已。

放一张我项目的截图

app_launcher.png

打包不同环境的apk

前面说的gradle task多出来2个task: assembleDevassembleProd加上本来就有的assembleReleaseassembleDebug,现在打包出来的组合为[debug,release][dev,prod]的2x2的排列组合。

apk.png

我们需要选择哪一个task打包呢?

如果你选assembleProd那么gradle会打出prod渠道下(即正式服务器环境下)的2个apk,分别为releaseProddebugProd两个apk。
如果你选assembleRelease那么gradle会打包出release BuildType的2个apk,分别为releaseDevreleaseProd两个apk。

所以自己内部测试的时候选assembleDebug这样打包出来即为开发设备的2个测试与正式服务器环境的2个apk,这样即和前面说的直接运行不同环境的app中的2个apk一样。
打要正式要发布的apk的时候选assembleProd这样生成可调试的和不可调试的正式服务器环境的2个apk,不可调试的apk(releaseProd)拿去上线发布,可调试的debugProd用来内部测试。

那么你可能要问,你不需要每次都生成2个apk,比如你只要打上线要发布的一个releaseProd就好,我只知道可以选这种task,有同学知道其他方法的可以评论中告诉我下,谢谢!

installl.png

可以看到这里的task即为我们前面说的[debug,release][dev,prod]的2x2的排列组合的单个task。

总结

其实就是修改不同渠道下的applicationid加根据不同task来配置不同服务器环境变量来实现的。
ok,感谢阅读,希望对你有帮助,有什么不对的地方也希望可以在评论中指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,225评论 25 707
  • 最近在项目中遇到需要实现 Apk 多渠道、定制化打包, Google 、百度查找了一些资料,成功实现了上述功能,在...
    看一季残花落幕阅读 2,498评论 1 8
  • 关于作者: 李涛,腾讯Android工程师,14年加入腾讯SNG增值产品部,期间主要负责手Q动漫、企鹅电竞等项目的...
    稻草人_3e17阅读 3,622评论 0 10
  • 一大早接到楚楚的电话,她声嘶力竭地说到:英国要求脱欧了,更有甚者一名女内阁议员因此被暗杀,她很担心欧洲的局势。接着...
    马小轩阅读 359评论 19 5
  • 长了二十几年,没有什么具体的三观,就是该做不该做,在这种该做不该做的标准中,我以不给别人添麻烦为基准,在我看来,这...
    笑不二阅读 164评论 0 0