ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Apk瘦身压缩体验

2021-06-04 15:06:54  阅读:289  来源: 互联网

标签:bitmap 压缩 System currentTimeMillis Apk webp libwebp 瘦身 资源


文章目录

资源统一

尽量一个项目使用同一套资源,对于绝大对数APP来说,只需要取一套设计图就足够了。鉴于现在分辨率的趋势,建议取720p的资源,放到xhdpi目录。
相对于多套资源,只使用720P的一套资源,在视觉上差别不大,很多大公司的产品也是如此,但却能显著的减少资源占用大小,顺便也能减轻设计师的出图工作量了。不是xhdpi的目录都删除,而是强调保留一套设计资源就够了。
资源图片引入前先进行压缩,不使用原图(不要直接使用UI切的原图)

使用jpg格式图片

如果对于非透明的大图,jpg将会比png的大小有显著的优势,虽然不是绝对的,但是通常会减小到一半都不止。在启动页,活动页等之类的大图展示区采用jpg将是非常明智的选择。

使用webp格式图片

  1. WebP是一种支持有损压缩和无损压缩的图片文件格式,根据Google的测试,无损压缩后的WebP比PNG文件少了26%的体积,有损压缩后的WebP图片相比于等效质量指标的JPEG图片减少了25%~34%的体积。
  2. webp支持透明度,压缩比比jpg更高但显示效果却不输于jpg,官方评测quality参数等于75均衡最佳。
    相对于jpg、png,webp作为一种新的图片格式,限于android的支持情况暂时还没用在手机端广泛应用起来。从Android 4.0+开始原生支持,但是不支持包含透明度,直到Android 4.2.1+才支持显示含透明度的webp,使用的时候要特别注意。
  3. 根据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文件夹下

libwebp源码下载地址
下载位置
jni文件夹

引入jar包

将libwebp.jar引入到工程中
在这里插入图片描述
jar包引入后

Android.mk配置

  1. 配置允许对当前源码目录进行编译
ENABLE_SHARED:=1

允许编译设置

  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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有