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 |
|
注意啦,这里有个坑,我们上面写了
1 |
|
运行之后,AS会自动生成@string/app_name,内容是这样的
1 |
|
那么问题来了,如果你在app的strings.xml文件也定义了
1 |
|
编译的时候就会出现问题,因为有2个app_name,所以我们要把app的strings.xml去掉,编译就会正常了。
再注意啦,我们 只能在app的build.gradle文件配置各个版本的值 ,如
1 2 3 4 5 6 |
|
如果你在其他子模块配置的话,编译时出现乱七八糟的错误!!
如果子模块需要配置的值,可以在公共模块定义静态变量,在app模块取出配置值后,设置到公共模块定义的静态变量中,这样的话各个模块都可以取到!!
修改AndroidManifest.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
注意啦,icon属性的值是${app_icon},虽然为红色,但是不要紧,我们在build.gradle文件中加入了这么一段代码:
1 2 3 4 5 6 7 |
|
这里的作用就是即使项目中报错也不会停止打包 。
打包并签名APK
我们打包时,就会出现2个app:
我们选择2个app,AS就会帮我们打包2个不同的APK了,就这么简单!!
填坑
今天按照以上步骤在开发项目尝试了一下,虽然可以打包不同版本的apk,但是不能同时安装到同一台设备,提示:xx有相同组件之类的。然后查看两个apk的包名是否一样,结果两个apk的包名是不同的。就这个问题查了一整天,果然皇天不负有心人,被我找到了,原因是我在AndroidManifest文件定义了一个provider:
记住,provider标签的authorities属性的值一定要是唯一的,如果两个app的authorities属性值一样,就会提示安装失败,一定要记住!!
查看包名的方法:
因为我们此时的apk不能安装的手机上,所以只有apk这个包,这时,我们使用aapt命令(aapt是sdk自带的一个工具,在sdk\builds-tools\目录下):
1 |
|
由于已经集成在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)包:
再新建以下包和文件:
资源文件同:
调试时点击 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无影响
defaultConfig的相关设置属性可直接在ProjectStructure中查看并设置
buildConfigField是用于解决Beta版本服务和Release版本服务地址不同
或者一些Log打印需求控制的.
形式:buildConfigField "boolean", "LOG_DEBUG", "true"
三个字段分别表示为: 自定义字段类型 自定义字段名 自定义字段值
将buildConfigField写入到Flavors中在打包编译时就会单独的编入进去(PS.请注意单双引号的使用,对于链接需要额外的单引号buildConfigField的放置位置很灵活,如果Flavors的buildConfigField是一样的话,直接放入到debug{}和release{}中即可.)
eg:查看时请注意路径,另外如果创建了多个Flavors时同步后可能只出现了第一个Flavors的文件夹.手动针对其他Flavors编译即可.
设置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;
最后子stackoverflow上得到了解决
http://stackoverflow.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.****.net/u010818425/article/details/52335844
https://blog.****.net/reancool/article/details/50673762
https://blog.****.net/xx326664162/article/details/49247815