用Android Studio和Gradle构建项目实现极少配置打包APK文件

最近因为公司与太保合作的一个项目,本身使用的是一个已有项目的源码直接修改进行开发的,当时那个已有项目是用Eclipse构建的,后来在集成太保推送SDK的时候,对方要求在不同的环境使用不同的ApplicationId,也就相当于Eclipse里的packageName,但是在Gradle构建的Android项目中,packageName与ApplicationId是两个不同的概念。为了达到上面的要求,首先我将Eclipse构建的项目迁移到Android Studio中,这个工作量对我来说也算挺大的了,因为整个项目有多个依赖类库,造成了改造的时候依赖的各种混乱。种种恶心的问题不一而足,就不在这里细说了。下面单单说一下如何达成在不同的环境使用不同的ApplicationId进行打包,并达到成功获取推送信息的目的。

首先要将Project Structure中创建3个Signing配置,如下图所示:

其中debug可以不用配置,但为了方便我还是配了一下,另外两个Sit和Release两个我配的都是使用生产的签名文件,为了让用户测试方便。
之后在Flavors标签中添加两个配置,如图所示:

其中图片中显示的Unrecognized value,这个等一下再解释,添加好之后什么都不用修改进入下一个标签页Build Types,同样的Buid Type我们也添加两个新的配置,把每一个配置修改一下,debug中的配置如图:

Debuggable是指打包的应用是否支持Debug,
Jni Debuggable是指打包的应用是否支持Native方法Debug,我这里都是false,因为我只是集成了一些so文件,并不需要调试Native的方法
Signing Config就用到了我们刚刚创建的签名配置,下拉菜单中也是可以看到的,这里debug我就使用的deubg的配置,sit使用sit的签名配置,release使用release的签名配置。
接下来就是重点了:那好笔和纸,做好笔记哟。

打开Project根目录下的build.gradle文件,最上面添加一条apply from: "config.gradle",表示可以接受在config.gradle文件中的配置的参数数据。接着在Project根目录下添加config.gradle文件,这个就是我们需要使用的配置信息所在的文件了。下面贴出来我的Project中的config.gradle文件的源码,以免会有遗漏的东西导致出问题:

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
apply from: "config.gradle"

buildscript {
repositories {
jcenter()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

接下来配置项目主要Module中的build.config文件,

上图中可以很清楚的了解到这3项内容就是我们在Project Structure中的signing标签页所创建的配置。所以在这里不再细说了。
然后看buildTypes,这里面有三个配置也是我们当时创建好的。但是为了多渠道打包方便,这里需要做一些改造。

  1. buildConfigField "boolean", "LOG_DEBUG", "true"这句话是在BuildConfig文件中添加一个布尔值参数,名称为“LOG_DEBUG”,值为“true”,BuildConfig是在每一次编译项目的时候都会自动生成的一个java类,通过这里的配置,我们可以向这个java类中增加我们需要的变量。下面两条类似,主要是为了规定当前配置下,所使用的SERVERURL的地址。这样配置完之后,Sync一下Gradle脚本,我们就可以在项目中直接引用这个变量了。例如:

    在改造过程中,为了不影响项目业务逻辑中所写的代码,所以只是替换了Constants这个类中的配置信息。这样我只需要一套配置信息就可以了,在不同的打包模式下,会自动修改替换BuildConfig中的对应变量的值。从而达到零修改多渠道打包的目的。
    2.

    而在上一张图中可以看到有这样一个值rootProject.ext.android.devServerUrl,这个其实就是我们引用的项目根目录下的config.gradle文件中配置的变量值。config.gradle文件中是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ext {
android = [compileSdkVersion: 22,
buildToolsVersion: "25.0.2",
sitApplicationId : "com.cpic.sit",
proApplicationId : "com.cpic.pro",
minSdkVersion : 16,
targetSdkVersion : 22,
versionCode: 1,
sitVersionName: "1.0.8",
proVersionName: "1.0.1",
devServerUrl: "\"https://dev.cpic.com.cn/dev/\"",
sitServerUrl: "\"https://sit.cpic.com.cn/sit/\"",
proServerUrl: "\"https://pro.cpic.com.cn/pro/\"",
devAPIUrl: "\"https://dev.cpic.com.cn/dev-http/\"",
sitAPIUrl: "\"https://sit.cpic.com.cn/sit-http/\"",
proAPIUrl: "\"https://pro.cpic.com.cn/pro-http/\""]
}

为了方便打包,我将本来是写在项目的Constants类中的url地址配在了config.gradle文件里。名称也根据环境的不同做了区分,分别加上了dev/sit/pro的前缀。我们在打包配置文件中引用的时候直接通过rootProject.ext.android.devServerUrl就可以取到这里的值了。signingConfig signingConfigs.debug这句就是使用Signing中的debug配置,所以在打debug包的时候就使用这个语句配置,打sit包使用signingConfig signingConfigs.sit进行配置,打生产包的时候signingConfig signingConfigs.release进行配置,很好理解。
上图中还有一个manifestPlaceholders的参数,这个参数后面的中括号中带了一堆的值,这个参数的作用是替换在AndroidManifest文件中所指定的参数的值,举个例子:JPUSH_CHANNEL_VALUE: "pro"就表示用“pro”替换AndroidManifest中以“JPUSH_CHANNEL_VALUE”为参数的值。而在清单文件中,我们有一项极光推送的配置是这样写的:

1
2
3
<meta-data
android:name="JPUSH_CHANNEL"
android:value="${JPUSH_CHANNEL_VALUE}"/>

所以,你可以知道,我们就是要将android:value=的值替换成”pro”。同样的我们又增加了一个THE_APPLICATION_ID: rootProject.ext.android.sitApplicationId这样的参数,这个参数引用了config.gradle配置文件中的sitApplicationId。这里替换的就比较多了,在清单文件中一共有6处需要做这样的替换,

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<permission
android:name="${THE_APPLICATION_ID}.permission.JPUSH_MESSAGE"
android:protectionLevel="signature"/>

<uses-permission android:name="${THE_APPLICATION_ID}.permission.JPUSH_MESSAGE"/>

<!-- 极光推送 -->
<activity
android:name="cn.jpush.android.ui.PushActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:theme="@android:style/Theme.NoTitleBar"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="cn.jpush.android.ui.PushActivity"/>

<category android:name="android.intent.category.DEFAULT"/>
<category android:name="${THE_APPLICATION_ID}"/>
</intent-filter>
</activity>
<!-- Required SDK 核心功能 since 1.8.0 -->
<service
android:name="cn.jpush.android.service.DaemonService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="cn.jpush.android.intent.DaemonService"/>

<category android:name="${THE_APPLICATION_ID}"/>
</intent-filter>
</service>

<!-- Required SDK核心功能 -->
<receiver
android:name="cn.jpush.android.service.PushReceiver"
android:enabled="true"
android:exported="false">
<intent-filter android:priority="1000">
<action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY"/>
<!-- Required 显示通知栏 -->
<category android:name="${THE_APPLICATION_ID}"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT"/>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
<!-- Optional -->
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>

<data android:scheme="package"/>
</intent-filter>
</receiver>

<!-- Required SDK核心功能 -->
<receiver android:name="cn.jpush.android.service.AlarmReceiver"/>

<!-- User defined. 用户自定义的广播接收器 -->
<receiver
android:name=".push.MyReceiver"
android:enabled="true">
<intent-filter>
<action android:name="cn.jpush.android.intent.REGISTRATION"/>
<!-- Required 用户注册SDK的intent -->
<action android:name="cn.jpush.android.intent.MESSAGE_RECEIVED"/>
<!-- Required 用户接收SDK消息的intent -->
<action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED"/>
<!-- Required 用户接收SDK通知栏信息的intent -->
<action android:name="cn.jpush.android.intent.NOTIFICATION_OPENED"/>
<!-- Required 用户打开自定义通知栏的intent -->
<action android:name="cn.jpush.android.intent.ACTION_RICHPUSH_CALLBACK"/>
<!-- Optional 用户接受Rich Push Javascript 回调函数的intent -->
<action android:name="cn.jpush.android.intent.CONNECTION"/>
<!-- -->
<action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY"/>
<!-- 接收网络变化 连接/断开 since 1.6.3 -->
<category android:name="${THE_APPLICATION_ID}"/>
</intent-filter>
</receiver>

对应的极光推送所需要的APP_KEY的值,我们也可以这样替换,以及百度推送的MyPushKey和env的值也是这样的

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<!-- Required . Enable it you can get statistics data with channel -->
<meta-data
android:name="JPUSH_CHANNEL"
android:value="${JPUSH_CHANNEL_VALUE}"/>
<meta-data
android:name="JPUSH_APPKEY"
android:value="${JPUSH_APPKEY_VALUE}"/>
<!-- </>值来自开发者平台取得的AppKey -->


<!-- 百度推送 -->
<!-- push应用定义消息receiver声明 -->
<receiver android:name=".push.MyPushMessageReceiver">
<intent-filter>

<!-- 接收push消息 -->
<action android:name="com.baidu.android.pushservice.action.MESSAGE"/>
<!-- 接收bind,unbind,fetch,delete等反馈消息 -->
<action android:name="com.baidu.android.pushservice.action.RECEIVE"/>
<action android:name="com.baidu.android.pushservice.action.notification.CLICK"/>
</intent-filter>
</receiver>
<receiver
android:name="com.baidu.android.pushservice.PushServiceReceiver"
android:process=":bdservice_v1">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
<action android:name="com.baidu.android.pushservice.action.notification.SHOW"/>
<action android:name="com.baidu.android.pushservice.action.media.CLICK"/>
<!-- 以下四项为可选的action声明,可大大提高service存活率和消息到达速度 -->
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<action android:name="android.intent.action.USER_PRESENT"/>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>
</receiver>
<!-- Push服务接收客户端发送的各种请求 -->
<receiver
android:name="com.baidu.android.pushservice.RegistrationReceiver"
android:process=":bdservice_v1">
<intent-filter>
<action android:name="com.baidu.android.pushservice.action.METHOD"/>
<action android:name="com.baidu.android.pushservice.action.BIND_SYNC"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>

<data android:scheme="package"/>
</intent-filter>
</receiver>

<service
android:name="com.baidu.android.pushservice.PushService"
android:exported="true"
android:process=":bdservice_v1">
<intent-filter>
<action android:name="com.baidu.android.pushservice.action.PUSH_SERVICE"/>
</intent-filter>
</service>
<!-- 4.4版本新增的CommandService声明,提升小米和魅族手机上的实际推送到达率 -->
<service
android:name="com.baidu.android.pushservice.CommandService"
android:exported="true"/>
<!-- 在百度开发者中心查询应用的API Key -->
<meta-data
android:name="api_key"
android:value="DzifCKe1j4NYwD0X3LvBrIpF"/>
<!-- appCode -->
<meta-data
android:name="MyPushKey"
android:value="${BAIDU_APPCODE_VALUE}"/>
<meta-data
android:name="env"
android:value="${ENV_VALUE}"/>
<!-- push结束 -->
<service
android:name="com.example.mysdk.service.ReceiptService"
android:enabled="true"
android:exported="false"/>
<service
android:name=".service.CpicPushService"
android:enabled="true"
android:exported="true">
</service>
<receiver android:name=".common.UpdateReceiver"/>

这样才能在打包的时候,节省我们大量的重复劳动

  1. 之后就是打包渠道的配置了,打包渠道就是在ProjectStructure中Flavors标签页按照正常来讲,会有各大应用市场的渠道要配置,比如360,应用宝,谷歌play,豆瓣等等。然而我们现在只讨论区分各个环境所应该使用的配置,因此我这里的配置是这样写的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
productFlavors {
sitConfig {
applicationId rootProject.ext.android.sitApplicationId
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.maxSdkVersion
versionName rootProject.ext.android.sitVersionName
versionCode rootProject.ext.android.versionCode
// dex突破65535的限制
multiDexEnabled true
}
proConfig {
applicationId rootProject.ext.android.proApplicationId
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.maxSdkVersion
versionName rootProject.ext.android.proVersionName
versionCode rootProject.ext.android.versionCode
// dex突破65535的限制
multiDexEnabled true
}
}

因为我们全部引用的是config.gradle文件中的配置信息,所以导致我们的Android Studio无法识别到这些参数的值,因此才会有上面的Unrecognized value的情况出现。

  1. 最后就是打包的APK文件名的生成规则,这里我就用的很简单的规则去生成了,你们也可以研究一下更加复杂的生成规则,具体的内容我也不细讲了,直接贴出来源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//修改生成的apk名字
applicationVariants.all{ variant->
variant.outputs.each { output->
def oldFile = output.outputFile
def newName = '';
if(variant.buildType.name.equals('release')){
// println(variant.productFlavors[0].name)
def releaseApkName = 'pro-' + rootProject.ext.android.proVersionName + '-' + variant.buildType.name +'.apk'
output.outputFile = new File(oldFile.parent, releaseApkName)
}
if(variant.buildType.name.equals('sit')){
newName = 'sit-' + rootProject.ext.android.sitVersionName + '-' + variant.buildType.name +'.apk'
output.outputFile = new File(oldFile.parent, newName)
}
}
}

注:以上Module配置文件的代码全部都要写在android {里面才不会报错。

下面就可以检验我们配置的成果了:

选择需要的打包方式

选择需要打包的渠道

这样打包一次只会打包单一渠道的。如果想要一次性打包多渠道,可以参考网上的教程折腾一下。
以上内容均为本博主个人劳动创作,转载请注明出处,谢谢!