应用场景
之前一直没有做 Android APK 发包管理,所以这次重构把这打包这部分考虑进去,之后可能会发布到一些应用市场。
要实现的功能
混淆代码
实现签名
过滤无用资源
生成 release 版本 APK
自定义 APK 名称
生成多渠道包
带着这几个问题,一步一步在 Simplenews 项目中实现
混淆代码
说到混淆代码,还真看过一些上线的 app 没有做混淆,这种可以用反编译工具给反编译出来,安全性太差,虽说这个小项目没有商业价值可言,但是还是要掌握app的安全性的,了解如何混淆代码,很简单看实例 app proguard-rules.pro
本项目中一些不能混淆的类或者方法:
-keep class com.library.event.AppExitEvent
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe ;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
-keep class com.kong.app.news.beans.** { *; }
-keep class com.actionbarsherlock.** { *; }
-keep class com.google.gson.** {*;}
-dontobfuscate
-dontwarn okio.**
-dontwarn okhttp3.**
-dontwarn rx.**
-dontnote sun.misc.Unsafe
-dontnote android.net.http.*
-dontnote org.apache.commons.codec.**
-dontnote org.apache.http.**
-dontnote com.android.org.conscrypt.**
-dontnote org.apache.harmony.xnet.provider.jsse.**
-dontnote com.android.internal.**
-dontwarn android.support.v4.**
-dontwarn com.dsi.ant.**
-dontwarn com.samsung.**
-dontwarn com.vividsolutions.jts.awt.**
-dontwarn android.support.**
-dontwarn com.squareup.okhttp.**
-dontnote android.net.http.*
-dontnote org.apache.http.**
-dontwarn javax.annotation.**
-dontwarn java.lang.invoke.**
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
主要是集中在一些注解,第三方库以及 Android 源码部分类和方法。
例如:
-keep class com.kong.app.news.beans.** { *; }
实现签名
给 App 签名,就相当于给 APP 上了一个牌照,保证了 APP 在线上市场的唯一性,这样才能安装到用户手机上话不多说,看操作
选择 Build -> Generate Signed APK
Next
Create New
生成签名文件还是挺简单的,不过真正有上线项目的时候,签名文件要注意保密,同时也不要上传到 github 上。
过滤无用资源
release {
minifyEnabled true //是否启动混淆
shrinkResources true //是否移除无用资源文件,shrinkResources依赖于minifyEnabled
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
通过过滤能够减少apk包大小,增加安装率
生成 release 版本包
我们发到线上的 apk 都是 release 版本,是经过混淆,签名,打包的 apk,是可以在各大市场进行下载安装合法 app。
根据时间更改打包 APK 名称
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
def fileName = "SimpleNews_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
最终生成的:SimpleNews_v1.0_2017-06-12_product1.apk
生成多渠道包
顺带着提提多渠道打包,我这个项目目前没涉及到多渠道打包,但真正项目中有很多这样的需求,比如,产品要统计每一个市场的安装下载和用户使用情况,针对不同的市场就要有不同版本的apk,也很好配置:
1、在AndroidManifest.xml里配置PlaceHolder
2、在 build.gradle 设置productFlavors
productFlavors {
product1 {
manifestPlaceholders.put("PRODUCT_CHANNEL_VALUE", 'product1')
}
product2 {
manifestPlaceholders.put("PRODUCT_CHANNEL_VALUE", 'product2')
}
}
批量处理
release {
//多渠道
productFlavors.all { flavor ->
manifestPlaceholders.put("UMENG_CHANNEL_VALUE", name)
}
}
我这里有两个例子,product1 product2 两个渠道,同步代码,输入打包命令打包即可,就会在build目录下看到生成的 apk
这里正好使用上一条更改 apk 名称功能,根据配置生成不同渠道对应的名称。
完整的 Gradle 文件
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion //编译的 SDK API 版本
buildToolsVersion rootProject.ext.android.buildToolsVersion //构建工具版本
defaultConfig {
applicationId rootProject.ext.android.applicationId//配置包名的
minSdkVersion rootProject.ext.android.minSdkVersion //支持最小的 SDK 版本
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode//版本号
versionName rootProject.ext.android.versionName//版本名称
// manifestPlaceholders = [PRODUCT_CHANNEL_VALUE: "github"]// 默认
}
signingConfigs {
releaseConfig {
keyAlias rootProject.ext.releaseConfig.keyAlias
keyPassword rootProject.ext.releaseConfig.keyPassword
storeFile file(rootProject.ext.releaseConfig.storeFile)
storePassword rootProject.ext.releaseConfig.storePassword
}
}
buildTypes {
debug {
applicationIdSuffix ".debug"
minifyEnabled false
zipAlignEnabled false
shrinkResources false
}
release {
zipAlignEnabled true
minifyEnabled true //是否启动混淆
shrinkResources true //是否移除无用资源文件,shrinkResources依赖于minifyEnabled
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.releaseConfig
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
def fileName = "SimpleNews_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
//多渠道
productFlavors.all { flavor ->
manifestPlaceholders.put("UMENG_CHANNEL_VALUE", name)
}
}
}
productFlavors {
product1 {
manifestPlaceholders.put("PRODUCT_CHANNEL_VALUE", 'product1')
}
product2 {
manifestPlaceholders.put("PRODUCT_CHANNEL_VALUE", 'product2')
}
}
}
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
dependencies {
//将libs文件夹中所有的jar文件视为依赖包。
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
//依赖远程仓库
compile rootProject.ext.dependencies.design
compile rootProject.ext.dependencies.cardview
compile rootProject.ext.dependencies.circleimageview
compile rootProject.ext.dependencies.sufficientlysecure
compile project(':lib.utils')
compile project(':lib.style')
}
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
完整 App build.gradle
总结
了解了以上知识,已经能满足简单的打包功能了,小项目中可能也就够了,先掌握最基本的内容,之后在再这之上做扩展,后续可能还需要在优化一些内容,比如 使用微信的混淆工具来大幅度压缩apk大小。
SimpleNews 项目的重构之旅其他文章
SimpleNews 项目的重构之旅(1) -项目架构定位 & Gradle 全局配置
SimpleNews 项目的重构之旅(2) - 整理项目 .gitignore 文件
SimpleNews 项目的重构之旅(3) -EventBus 接入
SimpleNews 项目的重构之旅(4) -Gradle for Android 基础知识汇总
SimpleNews 项目的重构之旅(5) - Android Gradle 打包&混淆应用
SimpleNews 项目的重构之旅(6) - 命名规范 & Android Toolbar
SimpleNews 项目的重构之旅(7) - 改头换面&深度清理