Android Studio 一个工程打包多个不同包

AS主要是利用gradle来实现这个需求的,具体做法如下:

修改app的build.gradle文件

假设我们同一套代码编译2个app:app1和app2

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

android {

 ...

 productFlavors {

  // app1

  app1 {

   // 设置applicationId(这里很重要,两个相同applicationId的apk不同同时安装在同一台Android手机中)

   applicationId "com.johan.demo"

   // 自动生成@string/app_name为demo

   resValue "string","app_name","demo"

   // 定义app_icon字段,在AndroidManifest.xml文件中用到

   manifestPlaceholders = [app_icon : "@mipmap/ic_launcher"]

  }

  // app2

  app2 {

   // 解释同app1

   applicationId "com.johan.demo1"

   resValue "string","app_name","demo1"

   manifestPlaceholders = [app_icon : "@mipmap/ic_launcher1"]

  }

 }

 lintOptions {

  checkReleaseBuilds false

  abortOnError false

 }

}

注意啦,这里有个坑,我们上面写了

?

1

resValue "string","app_name","demo"

运行之后,AS会自动生成@string/app_name,内容是这样的

?

1

<item type="string" name="app_name">demo</item>

那么问题来了,如果你在app的strings.xml文件也定义了

?

1

<string name="app_name">demo</string>

编译的时候就会出现问题,因为有2个app_name,所以我们要把app的strings.xml去掉,编译就会正常了。

再注意啦,我们 只能在app的build.gradle文件配置各个版本的值 ,如

?

1

2

3

4

5

6

app1 {

 buildConfigField "int", "TYPE", "1"

}

app2 {

 buildConfigField "int", "TYPE", "2"

}

如果你在其他子模块配置的话,编译时出现乱七八糟的错误!!

如果子模块需要配置的值,可以在公共模块定义静态变量,在app模块取出配置值后,设置到公共模块定义的静态变量中,这样的话各个模块都可以取到!!

修改AndroidManifest.xml文件

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

 package="com.johan.demo">

 <application

  android:allowBackup="true"

  // 注意啦,这里改为${app_icon}

  android:icon="${app_icon}"

  android:label="@string/app_name"

  android:supportsRtl="true"

  android:theme="@style/AppTheme">

  <activity android:name=".ui.MainActivity">

   <intent-filter>

    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />

   </intent-filter>

  </activity>

 </application>

</manifest>

注意啦,icon属性的值是${app_icon},虽然为红色,但是不要紧,我们在build.gradle文件中加入了这么一段代码:

?

1

2

3

4

5

6

7

android {

 ...

 lintOptions {

  checkReleaseBuilds false

  abortOnError false

 }

}

这里的作用就是即使项目中报错也不会停止打包 。

打包并签名APK

我们打包时,就会出现2个app:

Android Studio 一个工程打包多个不同包

我们选择2个app,AS就会帮我们打包2个不同的APK了,就这么简单!!

填坑

今天按照以上步骤在开发项目尝试了一下,虽然可以打包不同版本的apk,但是不能同时安装到同一台设备,提示:xx有相同组件之类的。然后查看两个apk的包名是否一样,结果两个apk的包名是不同的。就这个问题查了一整天,果然皇天不负有心人,被我找到了,原因是我在AndroidManifest文件定义了一个provider:

Android Studio 一个工程打包多个不同包

记住,provider标签的authorities属性的值一定要是唯一的,如果两个app的authorities属性值一样,就会提示安装失败,一定要记住!!

查看包名的方法:

因为我们此时的apk不能安装的手机上,所以只有apk这个包,这时,我们使用aapt命令(aapt是sdk自带的一个工具,在sdk\builds-tools\目录下):

?

1

aapt dump badging D:\apk\xxx.apk | findstr package

 

由于已经集成在gradle中,所以能直接在gradle中使用,在app的build.gradle中创建productFlavors结构

android{
    #......
    productFlavors{
        productA{
            #这里定义产品A的特性
        }

        productB{
            #这里定义产品B的特性
        }

        #更多产品 ...
    }

进行编译,在Build Variants版块里面我们能看到生成出来的不同Build Variant 
 
这里切换不同的product能够切换到不同的产品上面去。

那么怎么样才能在不同产品上体现出差异化呢?? 
肯定是在对应的product括号下添加对应的属性,我们看下写法..
productA{ 
buildConfigField ‘String’, ‘HOST’, ‘”http://192.168.0.208:7001/“’ 
}

productB{ 
buildConfigField ‘String’, ‘HOST’, ‘”http://192.168.0.30:7001/“’ 
}

首先我们添加一个名为HOST的字符串对象,实现编译, 然后我们在app的build/generated/buildConfig/productA(由于当前选的是productA项目)/包名/下有个BuildConfig.java的文件,打开看下 

首先我们可以看到这个文件是自动生成的且无法修改的,可以看到文件里面有DEBUG,AOOLICATION_ID这些参数,这些都是默认有的,我们看重点 
 
这不就是我们在productA里面声明的HOST参数吗,系统帮我们声明了一个名为HOST的String对象,那么在Activity里面怎么使用它呢,很简单,只要BuildConfig.HOST就可以拿到HOST对象了。

由于我们现在是在productA项目下,我们在Build Variants里面将现场切换成ProductB试一下。然后我们在刚才Activity声明的HOST变量 点进去看一下,发现跳到了ProductB的BuildConfig.java文件来了 

很显然,HOST的对象已经变了,变成了productB的对象值。

此外,除了String对象,我们还可以定义其他不同的对象,比如boolean,Class对象等等,

 buildConfigField 'boolean', 'displayFamilyName', 'true'  //列表Item前面是显示姓还是名
  buildConfigField 'Class', 'scheduleAddr', 'cn.com.minstone.httplogic.schedule.addr.PASys.class'

都是能够生成出来的。

替换manifest字段 
在mainfest里按照一定格式自定义个可变的字段,在gradle里根据不同版本赋予不同的值. 
注意:自定义mainfest字段的格式为:
${ YOURNAME };例如${APP_NAME}
具体写法是

productA{ 
   manifestPlaceholders = ["APP_KEY": "123456789",]
}

在manifest文件里面可以这么引用

<meta-data
   android:name = "APP_KEY"
   android:value = "${APP_KEY}"
>
</meta-data>

定义res对象 
我们还可以定义资源对象,具体用法
productA{
  resValue "string", "appName", '"柳州数字质监移动办公"'
}

然后在strings.xml文件里面使用

<resources>
    <string name="app_name">@string/appName</string>`
</resource>

此外,我们还可以定义像versionCode,versionName这样的字段,这里我就不一一类出来了。

假如所有现场都有一个统一的变量,这时候我总不能每个product里面都申明一模一样的变量吧??放心,这个问题productFlavors也帮我们考虑了,我们可以定义一个叫跟productX同级的defaultConfig的标签,标签里面可以放所有现场共用的属性和变量,具体操作是

android{
    defaultConfig{
        //存放公共变量
        buildConfigField 'String', 'COMMON', '"admin"'
    }
    productA{
      ....
    }
    productB{
      ...
    }
}

这样的话在productA和productB项目里都可以用到COMMON变量,假如productA的COMMON变量跟其他现场不一样怎么办?可以在productA标签下重写该变量进行差异化区分。

 productA{
 buildConfigField 'String', 'COMMON', '"aaaa"'
 }

通过productFlavors,我们可以很方便地对每个现场的变量进行控制,只需要鼠标点击只能切换到对应的项目,并且变量的值也能够对应切换,这样我们就能够去掉恶心的if else语句,可维护性更高。

最后,我说下我根据自己项目特点使用productFlavors的一些小技巧。
首先,随着项目的增大,你会发现每个标签下的变量越来越多,这样的话会导致app的build.gradle文件的行数会越来越多,影响可阅读性。所以,我将不同现场的标签抽取出来,分成个个文件,然后在app的build.gradle引用这些文件 

这样的话显得更加的直观 结构更加清晰,并且减少了build.gradle的代码量

2.假如我一个现场的主页面要导航页的5个功能,另外一个现场只要其中的4个功能,导致功能性的差异,这时候使用productFlavors怎么做? 
这里我提供一种解决思路,也是使用String变量来存,但是存放的值是Json字符串

            buildConfigField 'String', 'workBenchMenu', '"[{\\"title\\": \\"所有公文\\",\\"icon\\": \\"workbench_review\\"},{\\"title\\": \\"消息\\",\\"icon\\": \\"workben_message\\"},{\\"title\\": \\"任务\\",\\"icon\\": \\"workben_task\\" },{\\"title\\": \\"联系人\\",\\"icon\\": \\"workben_contact\\"},{\\"title\\": \\"单位动态\\",\\"icon\\": \\"workbench_dynamic\\"},{\\"title\\": \\"行程\\",\\"icon\\": \\"workben_schedule\\"},{\\"title\\": \\"公文分类\\",\\"icon\\": \\"workbench_documentrank\\"}]"'
然后Activity里面通过解析该json去生成布局,实现功能性差异。 
 

1.创建productFlavors

在APP的gradle中添加:

android {
    ...
    //创建productFlavors
    productFlavors {
        ceshi{//配置测试版包名和应用名
            applicationId "ceshi.yb.com.wanandroid"
            manifestPlaceholders = [APP_NAME: "@string/app_name_ceshi"]
        }
        shengchan{//配置生产版包名和应用名
            applicationId "shengchan.yb.com.wanandroid"
            manifestPlaceholders = [APP_NAME: "@string/app_name_shengchan"]
        }
    }
}

此时应该报以下错误:
Error:All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com/r/tools/flavorDimensions-missing-error-message.html
在defaultConfig 中加上:

defaultConfig {
        ...
        flavorDimensions "versionCode"
    }
再重新编译下就OK了.

2.创建统一文件夹

切换到Project模式的目录:

在src目录下新建ceshi(/shengchan)包:

再新建以下包和文件:Android Studio 一个工程打包多个不同包

资源文件同:Android Studio 一个工程打包多个不同包

调试时点击 Build Variant 选择自己需要的版本即可正常引用

3.配置不同的应用名或其他属性

首先strings.xml中添加:

    <string name="app_name_ceshi">测试版WanAndroid</string>
    <string name="app_name_shengchan">生产版WanAndroid</string>
然后manifest中改下lable:

<application
        android:name=".base.BaseApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="${APP_NAME}"//引用gradle中定义的变量
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ui.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ui.ImgDetailActivity"></activity>
    </application>
 

 通过配置Flavors和自定义buildConfigField进行多个服务器地址打包

    productFlavors{ } 是配置多渠道打包的.
       1).productFlavors{ } 与 buildTypes{ }里面的配置是多对多的关系。

            2).productFlavors{ } 其实是defaultConfig{ }的子集
        defaultConfig {}中的属性在productFlavors{}中都可以单独设置,若重复会以productFlavors{}中的属性为最终属性.

        a.可以对单独的productFlavors{ } 设置applicationId进而达到多个相同应用安装到同一台设备(现applicationId为唯一标识,区别于packageName)
        b.对单独的productFlavors{ }进行一些单独的配置或者操作

其中需要关注的是三部分: 
defaultConfig {},buildTypes {},productFlavors {} 
这三部分均可以对applicationId,图标及appname进行设置,但是优先级不同。优先级如下:

buildTypes >  productFlavors > defaultConfig  
设置AndroidManifest.xml中的属性可以通过manifestPlaceholders 进行设置,不同场景下的常量可以通过buildConfigField进行设置,资源的引用通过resValue 进行设置

      eg:单独对company的flavors进行了applicationId、minSdk、和JUnit测试限制操作,这样打出的包只会对company的有影响,其他的flavors无影响

Android Studio 一个工程打包多个不同包  

        defaultConfig的相关设置属性可直接在ProjectStructure中查看并设置
 buildConfigField是用于解决Beta版本服务和Release版本服务地址不同
或者一些Log打印需求控制的.
    形式:buildConfigField "boolean", "LOG_DEBUG", "true"
    三个字段分别表示为: 自定义字段类型  自定义字段名  自定义字段值
    将buildConfigField写入到Flavors中在打包编译时就会单独的编入进去(PS.请注意单双引号的使用,对于链接需要额外的单引号buildConfigField的放置位置很灵活,如果Flavors的buildConfigField是一样的话,直接放入到debug{}和release{}中即可.)
 eg:查看时请注意路径,另外如果创建了多个Flavors时同步后可能只出现了第一个Flavors的文件夹.手动针对其他Flavors编译即可.

Android Studio 一个工程打包多个不同包

设置buildConfigField的IS_PUBLIC无用 
一个app,两个module,一个是application module,另外一个是library module; 
在library中设置buildConfigField,如下

release {
     minifyEnabled false
     proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
     buildConfigField("boolean", "IS_PUBLIC", "true")
}

debug{
     buildConfigField("boolean", "IS_PUBLIC", "false")
}
用来控制http及debug在测试环境,和正式环境的不同; 
然而,无论如何都只能得到IS_PUBLIC为false;

最后子*上得到了解决 
http://*.com/questions/20176284/buildconfig-debug-always-false-when-building-library-projects-with-gradle

解决方法如下
With Android Studio 1.1 and having also the gradle version at 1.1 it is possible:

Library

android {
    publishNonDefault true
}
App

dependencies {
    releaseCompile project(path: ':library', configuration: 'release')
    debugCompile project(path: ':library', configuration: 'debug')
}

 

致谢:

https://www.jianshu.com/p/533240d222d3

https://blog.csdn.net/u010818425/article/details/52335844

https://blog.csdn.net/reancool/article/details/50673762

https://blog.csdn.net/xx326664162/article/details/49247815

 https://blog.csdn.net/u010818425/article/details/52319382

https://developer.android.com/studio/publish/app-signing