浅析Android打包
1501210946 刘彬彬
使用Android Studio的"build"选项下面的"Generate Signed APK..."选项,我们可以很轻松地打包出一个安装文件。但在现在的商业应用中,企业不会只打包出一个APK包,然后提供给大家下载。企业同时还需要要其他不同的渠道上发布APP,国内的主流渠道就有豌豆荚、应用宝、小米应用商店等等数十个渠道,通常每个渠道的APK包都会有一点差异,那么我们会每打包一个渠道的APK,就去改信息,然后执行一遍"Generate Signed APK..."吗?
当然不会。使用gradle打包apk已经成为当前主流趋势。配置好Gradle之后,只需敲几行命令,就可以安心地去喝一杯咖啡,等待程序把所有渠道的APK打包出来。
什么是Gradle
Gradle是一种依赖管理工具,基于Groovy语言,面向Java应用为主,它抛弃了基于XML的各种繁琐配置,取而代之的是一种基于Groovy的内部领域特定(DSL)语言。
Gradle基本概念
Fragments是一个项目,和Gradle相关的几个文件一般有如下几个:
1.Fragments/app/build.gradle
这个文件是app文件夹下这个Module的gradle配置文件,也可以算是整个项目最主要的gradle配置文件,我们来看下这个文件的内容:
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.example.bus.fragments"
minSdkVersion 15
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.1.1'
}
- 文件开头apply plugin是最新gradle版本的写法,以前的写法是apply plugin: ‘android’
- compileSdkVersion是安卓所用编译器的版本
- buildToolsVersion是Gradle工具的版本,第一次使用时会自动下载
- applicationId代表应用的包名,minSdkVersion代表最小支持的API,targetSdkVersion代表目标API,versionCode和versionName是自己定义的应用版本
- buildTypes代表生成APK时的类型,默认只有release版本
- minifyEnabled代表混淆处理
- proguardFiles这部分有两段,前一部分代表系统默认的android程序的混淆文件,后一部分是我们项目里的自定义的混淆文件
2.Fragments/build.gradle
这个文件是整个项目的gradle基础配置文件,我们来看看这里面的内容:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
内容主要包含了两个方面:一方面是声明仓库的源,这里可以看到是指明的jcenter();另一方面是声明了android gradle plugin的版本。
3.Fragments/settings.gradle
这个文件是全局的项目配置文件,里面主要声明一些需要加入gradle的module,我们来看看该文件的内容:
include ':app'
app是项目的module,如果还有其他module,也要加上去。
如何使用Gradle
1.使用命令行终端,cd到要操作的项目根路径下。推荐使用Android Studio里面集成的Terminal,默认路径就是项目根路径。
2.执行 gradlew -v 来查看下项目所用的Gradle版本 如果你是第一次执行会去下载Gradle。执行成功后会看到如下信息:
------------------------------------------------------------
Gradle 2.2.1
------------------------------------------------------------
Build time: 2014-11-24 09:45:35 UTC
Build number: none
Revision: 6fcb59c06f43a4e6b1bcb401f7686a8601a1fb4a
Groovy: 2.3.6
Ant: Apache Ant(TM) version 1.9.3 compiled on December 23 2013
JVM: 1.8.0_45 (Oracle Corporation 25.45-b02)
OS: Windows 7 6.1 x86
3.接着执行 gradlew clean。执行这个命令会去下载Gradle的一些依赖,下载成功并编译通过时会看到如下信息:
:app:clean
BUILD SUCCESSFUL
4.最后执行 gradlew build。这个命令会直接编译并生成相应的apk文件,如果看到如下字样就代表build成功了:
BUILD SUCCESSFUL
Total time: 31.456 secs
Gradle常用命令
- gradlew -v 版本号
- gradlew clean 清除Fragements/app目录下的build文件夹
- gradlew build 检查依赖并编译打包
- gradlew assembleDebug 编译并打Debug包
- gradlew assembleRelease 编译并打Release的包
- gradlew installRelease Release模式打包并安装
- gradlew uninstallRelease 卸载Release模式包
替换AndroidManifest中的占位符
把其中的${app_label}替换为字符串
AndroidManifest.xml中:
<meta-data android:name="UMENG_APPKEY" android:value="${app_label}" />
build.gradle中:
android{
defaultConfig{
manifestPlaceholders = [app_label:"xixihaha"]
}
}
上面是替换默认配置中的占位符,如果只需替换debug版本,则:
android{
buildTypes {
debug {
manifestPlaceholders = [app_label:"xixihaha"]
}
release {
}
}
}
独立配置签名信息
对于签名相关的信息,直接写在gradle当然不好,特别是一些开源项目,可以添加到gradle.properties:
RELEASE_KEY_PASSWORD=xxxx
RELEASE_KEY_ALIAS=xxx
RELEASE_STORE_PASSWORD=xxx
RELEASE_STORE_FILE=../.keystore/xxx.jks
然后在build.gradle中引用即可:
android {
signingConfigs {
release {
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
}
}
}
多渠道打包
多渠道打包的关键之处在于,定义不同的product flavor, 并把AndroiManifest中的channel渠道编号替换为对应的flavor标识:
android {
productFlavors {
baidu {}
alibaba {}
tencent {}
xiaomi {}
jingdong {}
renren {}
sina {}
netease{}
youku {}
facepp {}
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [channel_value: name]
}
}
上面定义了baidu等这些渠道,用一个循环把channel_value的值设为渠道的名字。
自定义Build Type
现在有一种需求,增加一种build type,介于debug和release之间,就是和release版本一样,但是要保留debug状态,我们称为preview版本吧。
android {
buildTypes {
debug {}
preview {}
release {}
}
}
使用buildtype还有好处就是,如果想生成所有渠道的release版本,使用assembleRelease命令即可,debug版本和preview版本同理。
Build Type中的定制参数
android {
buildTypes {
debug {}
preview {}
release {
manifestPlaceholders = [app_label:"Dabao_release"]
minifyEnabled true
shrinkResources true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
zipAlignEnabled false
}
}
}
//manifestPlaceholders 替换占位符
//minifyEnabled 混淆处理
//shrinkResources 去除无用资源
//signingConfig 签名信息
//proguardFiles 混淆配置
//zipAlignEnabled 压缩
基本的混淆方法
混淆能让反编译的代码可读性变的很差,而且还能显著的减少APK包的大小。相信很多朋友对混淆都觉得麻烦,甚至说,非常乱。因为添加混淆规则需要查询官方说明文档,甚至有的官方文档还没说明。当你引用了太多库后,添加混淆规则将使一场噩梦。 这里介绍一个技巧,不用查官方文档,不用逐个库考虑添加规则。 首先,除了默认的混淆配置(android-sdk/tools/proguard/proguard-android.txt), 自己的代码肯定是要自己配置的:
## 位于module下的proguard-rules.pro
#####################################
######### 主程序不能混淆的代码 #########
#####################################
-dontwarn xxx.model.**
-keep class xxx.model.** { *; }
## 等等,自己的代码自己清楚
#####################################
########### 不优化泛型和反射 ##########
#####################################
-keepattributes Signature
接下来是麻烦的第三方库,一般来说,如果是极光推的话,它的包名是cn.jpush, 添加如下代码即可:
-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
其他的第三库也是如此,一个一个添加,太累!其实可以用第三方反编译工具(比如jadx:https://github.com/skylot/jadx ),打开apk后,一眼就能看到引用的所有第三方库的包名,把所有不想混淆或者不确定能不能混淆的,直接都添加又有何不可:
#####################################
######### 第三方库或者jar包 ###########
#####################################
-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
-dontwarn com.squareup.**
-keep class com.squareup.** { *; }
-dontwarn com.octo.**
-keep class com.octo.** { *; }
-dontwarn de.**
-keep class de.** { *; }
-dontwarn javax.**
-keep class javax.** { *; }
-dontwarn org.**
-keep class org.** { *; }
-dontwarn u.aly.**
-keep class u.aly.** { *; }
-dontwarn uk.**
-keep class uk.** { *; }
-dontwarn com.baidu.**
-keep class com.baidu.** { *; }
-dontwarn com.facebook.**
-keep class com.facebook.** { *; }
-dontwarn com.google.**
-keep class com.google.** { *; }
## ... ...
自定义导出APK的名称
在导出的文件足够多的时候,使用默认的命名不够清晰直观。这个时候,我们就需要自定义APK的名称,并且把不同版本的APK放到不同子文件夹下。
android {
applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFile = new File(
output.outputFile.parent + "/${variant.buildType.name}",
"${variant.buildType.name}-${variant.versionName}-${variant.productFlavors[0].name}.apk".toLowerCase())
}
}
}
用以上代码就可以生成形如preview-v1.0-baidu.apk的文件了。
一些额外信息
如果要把编译的时间、编译的机器添加到APK中,而又不方便直接在代码里面实现,那要怎么办呢?这时候也可以使用Gradle:
android {
defaultConfig {
resValue "string", "build_time", buildTime()
resValue "string", "build_host", hostName()
}
}
def buildTime() {
return new Date().format("yyyy-MM-dd HH:mm:ss")
}
def hostName() {
return System.getProperty("user.name") + "@" + InetAddress.localHost.hostName
}
以上代码动态地添加了build_time、build_host两个字符串资源,在其他地方可以像引用字符串一样使用:
getString(R.string.build_time)
getString(R.string.build_host)
DEMO
最后做了一个可以用Gradle批量打包APK的demo,整个项目一起打包了。导入项目后,如果Android Studio是默认安装路径应该就不用配置什么了,就可以用上述命令进行尝试。生成的APK文件位于${project}\Dabao\app\build\outputs\apk目录下(${project}是项目所处路径)。