随着google如火如荼的推出android studio IDE和gradle buld工具,很多公司要对build系统做从ant到gradle的升级,不幸的是我被分配负责该任务。
发现一个问题,gralde编译后的apk比ant要大大约1M左右,对于老大之前严格要求控制apk在10M左右的需求,只能说造化弄人。压缩包打开发现gradle build之后的apk resources.arsc没有压缩,导致apk增大很多。
于是乎,设置resource shrink 为true,结果如下:
图一:ant打包之后的详单:
图二:gradle shrinkResources false的详单
图三:gradle shrinkResources true的详单
结果发现:ant打包后的resources.arsc,从1.1M压缩到267K,还是比较狠的。
至于是否shrinkResources,没有什么效果
aapt l -v Client-release-unsigned-ant.apk ,查看apk明细:
发现ant包和gradle包的差异:
Length Method Size Ratio Offset Date Time CRC-32 Name
1158688 Deflate 273481 76% 9835381 11-17-15 15:18 04aecb3e resources.arsc -------- <span style="color:#ff0000;">ant build apk</span>
1158508 Stored 1158508 0% 2899860 11-17-15 15:21 9222ccf9 resources.arsc -------- <span style="color:#ff0000;">gradle build apk</span>
ant build resources.arsc是deflate method,而gradle build 的是stored method,而且size相差很大。
于是乎,寻找gradle设置deflate模式的配置开关,未果,似乎是硬编码方式,不支持dsl配置,只能从最后的apk包下手,对resources做二次压缩。下面代码:
apply plugin: 'android'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile project(':MyProj')
}
android {
compileSdkVersion 19
buildToolsVersion '21.1.2'
lintOptions {
abortOnError false
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
// Move the tests to tests/java, tests/res, etc...
instrumentTest.setRoot('tests')
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
defaultConfig {
multiDexEnabled true
}
dexOptions {
javaMaxHeapSize "2g"
jumboMode true
}
buildTypes {
debug {
minifyEnabled false
proguardFile('proguard.cfg')
}
release {
minifyEnabled true
proguardFile('proguard.cfg')
}
}
packagingOptions {
exclude('META-INF/DEPENDENCIES')
exclude('META-INF/NOTICE')
exclude('META-INF/LICENSE')
}
lintOptions {
// Or, if you prefer, you can continue to check for errors in release builds,
checkReleaseBuilds false
// but continue the build even when errors are found:
abortOnError false
}
}
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
// 打包过程中很多手工zip过程:
// 1,为了压缩resources.arsc文件而对标准产出包重新压缩
// 2,以及各子apk的纯手打apk包
// 但对于音频等文件,压缩会导致资源加载报异常
// 重新打包方法,使用STORED过滤掉不应该压缩的文件们
// 后缀名列表来自于android源码
def repackApk(originApk, targetApk){
def noCompressExt = [".jpg", ".jpeg", ".png", ".gif",
".wav", ".mp2", ".mp3", ".ogg", ".aac",
".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
".amr", ".awb", ".wma", ".wmv"]
ZipFile zipFile = new ZipFile(originApk)
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(targetApk)))
zipFile.entries().each{ entryIn ->
if(entryIn.directory){
println "${entryIn.name} is a directory"
}
else{
def entryOut = new ZipEntry(entryIn.name)
def dotPos = entryIn.name.lastIndexOf('.')
def ext = (dotPos >= 0) ? entryIn.name.substring(dotPos) : ""
def isRes = entryIn.name.startsWith('res/')
if(isRes && ext in noCompressExt){
entryOut.method = ZipEntry.STORED
entryOut.size = entryIn.size
entryOut.compressedSize = entryIn.size
entryOut.crc = entryIn.crc
}
else{
entryOut.method = ZipEntry.DEFLATED
}
zos.putNextEntry(entryOut)
zos << zipFile.getInputStream(entryIn)
zos.closeEntry()
}
}
zos.finish()
zos.close()
zipFile.close()
}
task repack(){
doLast{
print "root dir is : $rootDir"
println "release打包之后,重新压缩一遍,以压缩resources.arsc"
def oldApkFile = file("$rootDir/projectPath/build/outputs/apk/Client-release-unsigned.apk")
assert oldApkFile != null : "没有找到release包!"
def newApkFile = new File("$rootDir/projectPath/build/outputs/apk/Client-release-unsigned-repack.apk")
print oldApkFile.absolutePath
print newApkFile.absolutePath
//重新打包
repackApk(oldApkFile.absolutePath, newApkFile.absolutePath)
assert newApkFile.exists() : "没有找到重新压缩的release包!"
}
}
其实也可以通过java或者python程序终结对apk进行二次处理达到效果,但是不如gradle处理的自然。
done, perfect !