Apk瘦身压缩体验
作者:互联网
文章目录
资源统一
尽量一个项目使用同一套资源,对于绝大对数APP来说,只需要取一套设计图就足够了。鉴于现在分辨率的趋势,建议取720p的资源,放到xhdpi目录。
相对于多套资源,只使用720P的一套资源,在视觉上差别不大,很多大公司的产品也是如此,但却能显著的减少资源占用大小,顺便也能减轻设计师的出图工作量了。不是xhdpi的目录都删除,而是强调保留一套设计资源就够了。
资源图片引入前先进行压缩,不使用原图(不要直接使用UI切的原图)
使用jpg格式图片
如果对于非透明的大图,jpg将会比png的大小有显著的优势,虽然不是绝对的,但是通常会减小到一半都不止。在启动页,活动页等之类的大图展示区采用jpg将是非常明智的选择。
使用webp格式图片
- WebP是一种支持有损压缩和无损压缩的图片文件格式,根据Google的测试,无损压缩后的WebP比PNG文件少了26%的体积,有损压缩后的WebP图片相比于等效质量指标的JPEG图片减少了25%~34%的体积。
- webp支持透明度,压缩比比jpg更高但显示效果却不输于jpg,官方评测quality参数等于75均衡最佳。
相对于jpg、png,webp作为一种新的图片格式,限于android的支持情况暂时还没用在手机端广泛应用起来。从Android 4.0+开始原生支持,但是不支持包含透明度,直到Android 4.2.1+才支持显示含透明度的webp,使用的时候要特别注意。 - 根据Google的测试,目前WebP与JPG相比较,编码速度慢10倍,解码速度慢1.5倍,问题在于1.5倍的解码速度是否会影响用户体验。同时由于减少了文件体积,缩短了加载的时间,页面的渲染速度加快了。同时,随着图片数量的增多,WebP页面加载的速度相对JPG页面增快了。所以,使用WebP基本没有技术阻碍。
使用shape背景和selector着色方案
对于一些简单的图形或者背景图片,通过自定义绘制图形方式来代替引入图片本身。
对于一些外形相同仅有颜色不同的图片,可以使用selector文件来实现。
在线化素材库
如果有很多或者一组图形需要引入,比如说表情包,可以考虑使用在线引入的方式,省去本地的空间。但同时会增加代码复杂度和APP流量消耗。
lint检查
代码扫描工具,可帮助您发现并更正代码结构质量的问题,例如,如果 XML 资源文件包含未使用的命名空间,这样不仅占用空间,而且还会引起不必要的处理。
官网地址
终端执行命令
gradlew lint
或者您可以通过依次选择 Analyze > Inspect Code,手动运行配置的 lint 及其他 IDE 检查。检查结果将显示在 Inspection Results 窗口中,如果有未使用的资源可以删除。
删除不必要的so库
基本上armable的so也是兼容armable-v7的,armable-v7a的库会对图形渲染方面有很大的改进,如果没有这方面的要求,可以精简。有极少数设备会Crash,测试通过再使用。x86包下的so在x86型号的手机是需要的,如果产品没用这方面的要求也可以精简。实际工作的配置是可以选择只保留armable、armable-x86下的so文件。
去除无用语言资源
只保留了中文和英文的语言资源,针对一些支持多语言的SDK。
android {
defaultConfig {
resConfigs "zh-rCN", "en-rUS"
}
}
开启混淆
android {
defaultConfig {
minifyEnabled true
}
}
开启shrinkResources去除无用资源
会把未使用到的资源文件在打包的时候移除内容,但源代码不会变,但此方案实测对apk的瘦身优化不一定会起到正向效果,可以酌情使用。但使用的时候要和上一步开启混淆同时使用,单独使用无效。
android {
buildTypes {
release {
shrinkResources true
}
}
}
使用zipAlign
资源对齐处理的工具(对齐到四字节边界并以此为单位进行访问),使得资源在被访问时候更有效率 能够对打包的应用程序进行优化,是的系统和APP之间的交互更加有效率,但对apk瘦身起到的效果不显著。
android {
buildTypes {
release {
zipAlignEnabled true
}
}
}
使用AndResGuard对资源文件压缩
以上几种方式,主要对资源本身进行处理和代码压缩,但是apk中的资源并没有进行过多的操作。 AndResGuard微信退出的一个帮助你缩小APK大小的工具,但是只针对资源(主要是res)并不涉及编译过程。他会将原本冗长的资源路径变短,例如将res/drawable/wechat变为r/d/a
添加混淆白名单:指定不需要进行混淆的资源路径规则,主要是一些第三方SDK,因为有些SDK的代码中引用到对应的资源文件,如果对其进行混淆,会导致找不到对应资源文件,出现crash,所以不能对其资源文件进行混淆。白名单链接如下;
引入时也可以选择自定义gradle的方式
配置完成后使用如下方式打出需要的apk
效果就类似于微信的apk压缩后的效果。把res文件改成了如下r文件夹的形式,并且压缩效果不错。
编译webp解码器
手机厂商自带的webp解码器基本上只针对Android 4.3 以上的机型,4.3以下是有问题的,所以我们可以对webp的源码进行编译,打包成so库,使用自己的解码器。
下载webp源码放在创建的jni文件夹下
引入jar包
将libwebp.jar引入到工程中
Android.mk配置
- 配置允许对当前源码目录进行编译
ENABLE_SHARED:=1
- 由于libwebp.jar需要用到wig/libwebp_java_wrap.c 文件中的jni方法,所以需要将该文件也引入到Android.mk文件中进行配置
# libwebp
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
$(dsp_enc_srcs) \
$(enc_srcs) \
$(utils_enc_srcs) \
swig/libwebp_java_wrap.c \
创建Application.mk
APP_ABI := armeabi-v7a
APP_PLATFORM := android-14
编译解码器的so库
在libwebp源码目录下打开cmd,使用ndk去对目录下的文件进行编译,生成so库
C:\SDK\ndk\21.3.6528147\ndk-build.cmd NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk APP_BUILD_SCRIPT=Android.mk
编译成功后导入so库文件
对比png,jpeg,webp的编解码速度
相关代码如下
defaultConfig {
applicationId "com.bliss.yang.webpapp"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters "armeabi-v7a"//,添加ndk的支持
}
}
companion object{
init {
System.loadLibrary("webp")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// webp 解码速度 编码速度
var l = System.currentTimeMillis()
BitmapFactory.decodeResource(resources, R.drawable.splash_bg_webp)
Log.e(TAG, "解码webp图片耗时:" + (System.currentTimeMillis() - l))
l = System.currentTimeMillis()
BitmapFactory.decodeResource(resources, R.drawable.splash_bg_jpeg)
Log.e(TAG, "解码jpeg图片耗时:" + (System.currentTimeMillis() - l))
l = System.currentTimeMillis()
var bitmap = BitmapFactory.decodeResource(resources, R.drawable.splash_bg_png)
Log.e(TAG, "解码png图片耗时:" + (System.currentTimeMillis() - l))
//编码 png
var bitmap1 = BitmapFactory.decodeResource(resources, R.drawable.splash_bg_png)
l = System.currentTimeMillis()
compressBitmap(bitmap1, CompressFormat.PNG, Environment
.getExternalStorageDirectory().toString() + "/test.png")
Log.e(TAG, "------->编码png图片耗时:" + (System.currentTimeMillis() - l))
//编码 jpeg
bitmap1 = BitmapFactory.decodeResource(resources, R.drawable.splash_bg_jpeg)
l = System.currentTimeMillis()
compressBitmap(bitmap1, CompressFormat.JPEG, Environment
.getExternalStorageDirectory().toString() + "/test.jpeg")
Log.e(TAG, "------->编码jpeg图片耗时:" + (System.currentTimeMillis() - l))
//编码 webp
bitmap1 = BitmapFactory.decodeResource(resources, R.drawable.splash_bg_webp)
l = System.currentTimeMillis()
compressBitmap(bitmap1, CompressFormat.WEBP, Environment
.getExternalStorageDirectory().toString() + "/test.webp")
Log.e(TAG, "------->编码webp图片耗时:" + (System.currentTimeMillis() - l))
// 编译的解码器
l = System.currentTimeMillis()
decodeWebp()
Log.e(TAG, "libwebp解码图片耗时:" + (System.currentTimeMillis() - l))
l = System.currentTimeMillis()
encodeWebp(bitmap)
Log.e(TAG, "libwebp编码图片耗时:" + (System.currentTimeMillis() - l))
}
private fun encodeWebp(bitmap: Bitmap) {
//获取bitmap 宽高
val width = bitmap.width
val height = bitmap.height
//获得bitmap中的 ARGB 数据 nio
val buffer: ByteBuffer = ByteBuffer.allocate(bitmap.byteCount)
bitmap.copyPixelsToBuffer(buffer)
//编码 获得 webp格式文件数据 4 *width
val bytes: ByteArray = libwebp.WebPEncodeRGBA(buffer.array(), width, height, width * 4, 75F)
var fos: FileOutputStream? = null
try {
fos = FileOutputStream(Environment
.getExternalStorageDirectory().toString() + "/libwebp.webp")
fos.write(bytes)
} catch (e: Exception) {
e.printStackTrace()
} finally {
if (null != fos) {
try {
fos.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
private fun decodeWebp(): Bitmap? {
@SuppressLint("ResourceType") val `is`: InputStream = resources.openRawResource(R.drawable.splash_bg_webp)
val bytes = stream2Bytes(`is`)
//将webp格式的数据转成 argb
val width = IntArray(1)
val height = IntArray(1)
try {
`is`.close()
} catch (e: IOException) {
e.printStackTrace()
}
val argb: ByteArray = libwebp.WebPDecodeARGB(bytes, bytes.size.toLong(), width, height)
//将argb byte数组转成 int数组
val pixels = IntArray(argb.size / 4)
ByteBuffer.wrap(argb).asIntBuffer().get(pixels)
//获得bitmap
return Bitmap.createBitmap(pixels, width[0], height[0], Bitmap.Config.ARGB_8888)
}
fun stream2Bytes(`is`: InputStream): ByteArray {
val bos = ByteArrayOutputStream()
val buffer = ByteArray(2048)
var len: Int
try {
while (`is`.read(buffer).also { len = it } != -1) {
bos.write(buffer, 0, len)
}
} catch (e: IOException) {
e.printStackTrace()
}
return bos.toByteArray()
}
private fun compressBitmap(bitmap: Bitmap, format: CompressFormat, file: String) {
var fos: FileOutputStream? = null
try {
fos = FileOutputStream(file)
} catch (e: FileNotFoundException) {
e.printStackTrace()
}
bitmap.compress(format, 75, fos)
if (null != fos) {
try {
fos.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
标签:bitmap,压缩,System,currentTimeMillis,Apk,webp,libwebp,瘦身,资源 来源: https://blog.51cto.com/u_15249199/2858415