Android之Apk优化九道工序

针对安卓APP开发,打包的安卓apk之后发布应用。但在实现原有功能的前提下,怎么样保证apk体量尽可能的小安全是我们需要追求的极致。

第一步:

使用svg转vector

可缩放矢量图形,SVG不会像位图一样因为缩放而让图片质量下降。节约空间与内存,常用于简单小图标。

1.在Android Studio中使用:

 

将svg图片转换为VectorDrawable。

2.使用注意兼容性

在app/build.gradle中添加:

// 使用support-v7兼容
defaultConfig {
   vectorDrawables.useSupportLibrary = true
}

implementation 'com.android.support:appcompat-v7:28.0.0'

在布局中引用:

app:srcCompat="@drawable/icon"

3.静态VectorDrawable在XML中的定义

官方例子:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:name="triangle"//定义矢量图的名称
     android:height="64dp"//drawable的固定高度,支持所有的尺寸单位,一般使用dp
     android:width="64dp"//drawable的固定宽度,支持所有的尺寸单位,一般使用dp
     android:viewportHeight="600"//视图的高度,可以理解为画布的高度
     android:viewportWidth="600" >//视图的宽度,下面的pathData中的内容便会在600宽高的画布内操作
     <group //定义一个组,可以包含path 及子group, 同时可以定义转换信息,如旋转,伸缩,位移
         android:name="rotationGroup"//组名
         android:pivotX="300.0"//X坐标中心点,默认为0
         android:pivotY="300.0"//Y坐标中心点,默认为0
         android:rotation="45.0" >//旋转角度,顺时针
         <path
             android:name="v"//路径的名称
             android:fillColor="#000000"//填充颜色
             android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />//路径的数据
     </group>
 </vector>

 效果:

解析一下数据:M300,70 l 0,-70 70,70 0,0 -70,70z

相关的指令:
M = moveto 相当于 android Path 里的moveTo(),用于移动起始点
L = lineto 相当于 android Path 里的lineTo(),用于画线
H = horizontal lineto 用于画水平线
V = vertical lineto 用于画竖直线
C = curveto 相当于cubicTo(),三次贝塞尔曲线
S = smooth curveto 同样三次贝塞尔曲线,更平滑
Q = quadratic Belzier curve quadTo(),二次贝塞尔曲线
T = smooth quadratic Belzier curveto 同样二次贝塞尔曲线,更平滑
A = elliptical Arc 相当于arcTo(),用于画弧
Z = closepath 相当于closeTo(),关闭path

坐标轴为以(0,0)为中心,X轴水平向右,Y轴水平向下;
所有指令大小写均可。大写绝对定位,参照全局坐标系;小写相对定位,参照父容器坐标系;
指令和数据间的空格可以省略;同一指令出现多次可以只用一个。

(还有很多高级动画操作,请自行百度)

第二步:

使用着色器Tint

实现目的:

  • 一张矢量图适配所有颜色
  • 更优雅的selector实现方式

1.适配所有颜色:

  • XML:直接添加android:tint="@color"属性。
 <ImageView
        android:id="@+id/image1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:src="@mipmap/logo" />

<ImageView
        android:id="@+id/image2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:src="@mipmap/logo"
        android:tint="#3F51B5" />
  • 代码实现
Drawable drawable = ContextCompat.getDrawable(this, R.mipmap.icon);

// 保留原来的图片
Drawable.ConstantState state = drawable.getConstantState();
/**
* DrawableCompat类:是Drawable的向下兼容类,我们为了在6.0一下兼容tint属性而使用
* wrap方法:使用tint就必须调用该方法对Drawable进行一次包装
* 调用mutate后会对ConstantState进行一次拷贝
*/
Drawable drawable1 = DrawableCompat.wrap(state == null ? drawable : state.newDrawable()).mutate();
drawable1.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());

//设置Tint属性
DrawableCompat.setTint(drawable, ContextCompat.getColor(this, R.color.colorAccent));

my_image.setImageDrawable(drawable);// 改变后的图片
my_image1.setImageDrawable(drawable1);// 原来的图片

2.实现selector

  • XML实现:效果不好,Android6.0下不兼容。
  • 代码实现:Android6.0下兼容。
Drawable drawable = ContextCompat.getDrawable(this, R.mipmap.icon);
int[] colors = new int[]{ContextCompat.getColor(this, R.color.colorPrimary), ContextCompat.getColor(this, R.color.colorAccent)};
int[][] states = new int[2][];
// 注意顺序
states[0] = new int[]{android.R.attr.state_pressed};
states[1] = new int[]{};

ColorStateList colorList = new ColorStateList(states, colors);
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(states[0], drawable);
stateListDrawable.addState(states[1], drawable);
Drawable.ConstantState state = stateListDrawable.getConstantState();
drawable = DrawableCompat.wrap(state == null ? stateListDrawable : state.newDrawable()).mutate();
// 设置各个状态下的颜色
my_image.setImageDrawable(drawable);

 

第三步:

资源打包配置

当应用不需要支持几十种语言时,通过配置 resConfigs 去除无用的语言资源。可以减少apk体积100k左右。

defaultConfig {
        applicationId "com.zachary.util"
        minSdkVersion 18
        targetSdkVersion 26
        versionCode 101
        versionName "1.0.1"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        //只保留默认的资源和指定的资源:中文、英文
        resConfigs "zh-rCN", "en-rUS"
}

 

第四步:

动态库的打包配置(定制化设备除外)
当引入so库的时候,modle的build.gradle:

//将so库打包到apk内
sourceSets{
    main{
        jniLibs.srcDirs=['libs']
    }
}

 在modle的build.gradle中defaultConfig配置so库架构,减小apk体积

//配置so库架构(真机:arm  模拟器:x86)
ndk {
    abiFilters('armeabi', 'armeabi-v7a')
}

第五步:

  •  在modle的build.gradle中buildTypes配置,移除无用资源:
buildTypes {
    release {
        // 移除无用的resource文件
        shrinkResources true
    }
}

 注:打包时会删除没有用到的资源。若是动态获取资源id,在打包时也会将其删除。

  • 推荐在物理上的删除,移除无用的资源(Lint)

通过Android Studio中:Refactor-->Remove Unused Resources...快速删除(慎用

通过Android Studio中:Analyze-->Run Inspection by Name-->unused resources

根据自己的实际需求进行选择,直接删除不需要的资源文件即可。

第六步:

开启代码混淆

在modle的build.gradle中buildTypes配置

buildTypes {
        release {
            // 开启混淆(反编译)
            minifyEnabled true
            // 移除无用的resource文件
            shrinkResources false
            debuggable false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
}

可以查看文章:Android之傻瓜混淆

注:将所需要的混淆都加进去,发现哪个class混淆有问题就将其keep,重复的删掉:
为保持第三方库中的类而不乱,-dontwarn和-keep 结合使用,意思是保持com.xx.bbb.**这个包里面的所有类和所有方法而不混淆,接着还叫ProGuard不要警告找不到com.xx.bbb.**这个包里面的类的相关引用。

-dontwarn com.xx.bbb.**
-keep class com.xx.bbb.** { *;}

第七步:

启用资源缩减

在第五步中有介绍,在modle的build.gradle中buildTypes配置,移除无用资源:

buildTypes {
    release {
        // 移除无用的resource文件
        shrinkResources true
    }
}

使我们保留了代码文件,只是不打包进入apk,如果没有动态获取资源id,打包没有问题的话,请开启。因为总是会因为各种需求的变化,我们的项目中留有暂时没有用到的资源。

注:混淆文件proguard-rules.pro里,添加以下命令,启用资源缩减无效。

-dontshrink

第八步:

将png格式大图转webp格式

WebP是一种同时提供了有损压缩与无损压缩的图片文件格式,这种图片格式相比png或者jpg格式的图片损失的质量几乎可以忽略不计,但是压缩后的体积却比png和jpg要小很多。

在Android Studio中进行转换:右键png图片,选择Convert to WebP

针对大图压缩还是很明显的。

 进行批量转换:

介绍一下工具iSparta,可以将png批量转换成webp格式、生成APNG、图片无损压缩、png有损压缩。

官网地址:iSparta我尝试过最新版本下载,下载过程很痛苦,下载之后也不能正常使用,具体原因不知。

因此我在百度云提供:win64、win32、mac的iSparta工具下载:

  • 链接:https://pan.baidu.com/s/1VdYYcQ92WzRoJAk9RHWUYg 
  • 提取码:8i0n 

第九步:

微信开源的AndResGuard进行资源混淆AndResGuard

1.在项目根目录下build.gradle中,添加插件的依赖:

dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'

        // 资源混淆
        classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.15'
    }

 2.在app目录下,创建and_res_guard.gradle文件(可以直接将内容写入build.gradle文件):

apply plugin: 'AndResGuard'
andResGuard {
    // mappingFile = file("./resource_mapping.txt")
    mappingFile = null
    use7zip = true
    useSign = true
    // 打开这个开关,会keep住所有资源的原始路径,只混淆资源的名字
    keepRoot = false
    // 白名单
    whiteList = [
            // for your icon
            "R.drawable.logo",
            // for fabric
            "R.string.com.crashlytics.*",
            // for google-services
            "R.string.google_app_id",
            "R.string.gcm_defaultSenderId",
            "R.string.default_web_client_id",
            "R.string.ga_trackingId",
            "R.string.firebase_database_url",
            "R.string.google_api_key",
            "R.string.google_crash_reporting_api_key",

            // 例如:友盟、融云在sdk里面写死了资源名,但是被我们混淆之后找不到指定资源,则会报崩溃
            // umeng share for sina
            "R.drawable.sina*",
            // for umeng update
            "R.string.umeng*",
            "R.string.UM*",
            "R.string.tb_*",
            "R.string.rc_*",
            "R.layout.umeng*",
            "R.layout.tb_*",
            "R.layout.rc_*",
            "R.drawable.umeng*",
            "R.drawable.tb_*",
            "R.drawable.rc_*",
            "R.drawable.u1*",
            "R.drawable.u2*",
            "R.anim.umeng*",
            "R.color.umeng*",
            "R.color.tb_*",
            "R.color.rc_*",
            "R.style.*UM*",
            "R.style.umeng*",
            "R.style.rc_*",
            "R.id.umeng*",
            "R.id.rc_*",

            // 项目中遇到第三方使用getIdentifier访问的资源的问题,只好全部id都放入白名单
             "R.id.*"
    ]
    compressFilePattern = [
            "*.png",
            "*.jpg",
            "*.jpeg",
            "*.gif",
    ]
    sevenzip {
        artifact = 'com.tencent.mm:SevenZip:1.2.15'
        //path = "/usr/local/bin/7za"
    }

    /**
     * 可选: 如果不设置则会默认覆盖assemble输出的apk
     **/
    // finalApkBackupPath = "${project.rootDir}/final.apk"

    /**
     * 可选: 指定v1签名时生成jar文件的摘要算法
     * 默认值为“SHA-1”
     **/
    // digestalg = "SHA-256"
}

3.在build.gradle文件中引用and_res_guard.gradle。

apply from: 'and_res_guard.gradle'

 集成完AndResGuard完毕,在app的gradle的tasks中执行指令。

双击执行resguardRelease指令,执行完毕后,可以在app目录下的/build/output/apk/release/AndResGuard_{apk_name}/ 文件夹中找到混淆后的Apk。 

注:

1.出现异常(Execution failed for task ':app:resguardRelease'. > can't the get signConfig for release build

解决:配置相应签名即可

    // 定义APK的签名信息.
    signingConfigs {
        debug { 
        }
        release {
            keyAlias '***'
            keyPassword '***'
            storeFile file('D:/**签名/**.jks')
            storePassword '***'
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release //增加签名
        }

        debug {    
        }
    }

2.若写死了资源名,则需要白名单使用正则表达式指定资源文件不被混淆。 

资源混淆后的效果

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值