Android打包

打包的事,之前很少做,也了解的比较少,今天好好看了看,总结一下,以后要是用到了,或许可以少走些弯路。

多渠道打包

友盟多渠道打包

友盟在 Github 也有相对应的文档,主要是利用 Android Gradle 中的 ProductFlavor 功能添加的多个渠道。

首先在 AndroidManifest.xml 中添加

1
<meta-data android:value="UMENG_CHANNEL_VALUE" android:name="UMENG_CHANNEL"/>

接着在 app 的 build.gradle 中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
productFlavors {
anzhi {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "anzhi"]
}
baidu {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
}
c360 {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "c360"]
}
yingyongbao {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "yingyongbao"]
}
huawei {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "huawei"]
}
xiaomi {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
}
}

上面的配置看起来有点蠢,可以抽取出来,用一个方法来实现:

1
2
3
4
5
6
7
8
9
10
11
productFlavors {
anzhi {}
baidu {}
c360 {}
yingyongbao {}
huawei {}
xiaomi {}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}

另外,签名的配置肯定不能少,也是在 app 的 build.gradle 文件中:

1
2
3
4
5
6
7
8
signingConfigs {
release {
storeFile file("demo.jks")
storePassword "123123"
keyAlias "123123"
keyPassword "123123"
}
}

jks 和 keystore 俩文件,前者是 Android Studio 生成的,后者是 Eclipse 生成的,使用的时候两者都是可以的。

在 buildTypes 中加入对生成的 apk 的命名方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
File outputDirectory = new File(outputFile.parent);
def fileName
if (variant.buildType.name == "release") {
// fileName = "app_v${defaultConfig.versionName}_${packageTime()}_${variant.productFlavors[0].name}.apk"
fileName = "caixiangji_${variant.productFlavors[0].name}.apk"
} else {
// fileName = "app_v${defaultConfig.versionName}_${packageTime()}_beta.apk"
fileName = "caixiangji_${variant.productFlavors[0].name}_beta.apk"
}
output.outputFile = new File(outputDirectory, fileName)
}
}
}

然后在 AS 自带的 Terminal 下输入:

1
gradlew assembleRelease

不过有可能出现一些问题,我在 mac 下就遇到过,例如:

1
-bash :gradlew command not found

那么有可能是权限问题,执行

1
sudo chmod +x gradlew

还有,在 mac 下执行当前目录下的命令要在前面加上 “./“。

当然也有可能是你没有配置好环境变量,按步骤设置一遍就好了。

如果一切顺利,那么差不多等上 10 分钟可能就打包好了。其实除了命令行,也可以直接使用图形化窗口操作,双击运行即可。

屏幕快照 2017-08-09 下午4.00.08

美团的多渠道打包

Walle : Android Signature V2 Scheme 签名下的新一代渠道包打包神器

打包速度相对前者来说,真的是特特别快。

集成步骤 Github 上的文档写的也挺详细的了。

配置build.gradle

在位于项目的根目录 build.gradle 文件中添加Walle Gradle插件的依赖, 如下:

1
2
3
4
5
buildscript {
dependencies {
classpath 'com.meituan.android.walle:plugin:1.1.5'
}
}

并在当前App的 build.gradle 文件中apply这个插件,并添加上用于读取渠道号的AAR

1
2
3
4
5
apply plugin: 'walle'

dependencies {
compile 'com.meituan.android.walle:library:1.1.5'
}
配置插件
1
2
3
4
5
6
7
8
walle {
// 指定渠道包的输出路径
apkOutputFolder = new File("${project.buildDir}/outputs/channels");
// 定制渠道包的APK的文件名称
apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';
// 渠道配置文件
channelFile = new File("${project.getProjectDir()}/channel")
}

这里是将渠道的配置文件单独抽取出来了,所以需要在 app 文件夹下新建一个文件 channel ( Text 类型),在里面配置渠道的信息,例如:

1638147-1e316c70eb9a0af6

最后可以打包了,所有渠道的:

1
./gradlew clean assembleReleaseChannels

或者是单独的渠道 (华为):

1
./gradlew clean assembleReleaseChannels -PchannelList=huawei

也可以直接用界面操作。

3 分钟不到, 10 多个不同渠道的包就打完了。

输出的位置是在 outputs/channels ,也可以自己配置。

多版本,多环境

多渠道使用 Walle 的情况下,我们可以基于 buildTypes 和 productFlavors 使用多版本和多环境的打包。

多版本

基于buildTypes

  1. debug:调试版本,无混淆
  2. release:发布版本,有混淆、压缩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.test
}

debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

signingConfig signingConfigs.test
}
}

多环境

基于productFlavors

  1. develop:开发环境,开发和自测时使用
  2. check:测试环境,克隆一份生产环境的配置,在这里测试通过后,再发布到生产环境。
  3. product:生产环境,正式提供服务的。
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
26
27
28
29
productFlavors {
//开发环境
develop {
buildConfigField "int", "ENV_TYPE", "1"
applicationId 'com.zhanglf.servertest.develop'
manifestPlaceholders = [
app_name: "开-Demo",
app_icon: "@mipmap/dev_launcher"
]
}
//测试环境
check {
buildConfigField "int", "ENV_TYPE", "2"
applicationId 'com.zhanglf.servertest.check'
manifestPlaceholders = [
app_name: "测-Demo",
app_icon: "@mipmap/check_launcher"
]
}
//生产环境
product {
buildConfigField "int", "ENV_TYPE", "3"
applicationId 'com.zhanglf.servertest.product'
manifestPlaceholders = [
app_name: "Demo",
app_icon: "@mipmap/pro_launcher"
]
}
}

在 mainfest 中使用占位符:

1
2
android:icon="${app_icon}"
android:label="${app_name}"

不同渠道 资源替换问题

一种比较想到的方式是就是通过代码的方式获取渠道信息,再通过该信息,分配不同的资源。但是该方式的缺点是,不同的资源在打包的时候都被放进了 apk 中。

1
String channel = WalleChannelReader.getChannel(this.getApplicationContext());

另一种方式就是根据不同的渠道在打包的时候就将对应的资源分配好了,上面的多环境中的 icon 和 app_name 占位符的方式就是如此。

具体实现:

在项目工程的 src 文件夹下创建对应渠道的文件夹,下面存放资源文件,如下:

20151227235630312

这样通过不同渠道打包的时候,资源文件会自动覆盖 main 中的资源,而且 main 中的或者是别的渠道中的文件都不会再该渠道的包中出现,从而更加节省空间。