flutter瘦身实践

首先在Android studio双击打开我们要瘦身的apk,在file栏目可以看到各种文件的具体信息,按照大小排序。


image.png

gif线上拉取展示

从上图可以知道图片是首要优化任务。展开具体的assets目录,可以看到,首要的是gif太大了,这部分全部放到后台,直接请求,从线上拉取。

【减少53M】
image.png

字体从线上拉取,动态设置

【减少6.9M】
image.png

将图片转为webp格式

webp格式类似与jpeg、png,但是体积要远比jpeg和png小。下面是百度百科对#### webp的解释:

WebP编码对于哈夫曼压缩性能更优异些,哈夫曼与WebP本质上都是从编码来解决图像压缩,哈夫曼是对rgb的元数据进行变频压缩而WebP编码是通过预测技术对图片压缩。

WebP 压缩使用的图像编码方式与 VP8 视频编码对关键帧压缩方式相同,换句话解释:Google将视频编码技术搬到了图片上 形成了Webp编码格式。

再详细不说了,就偏离主题了,今天的主题是apk的压缩,所以我们要做的就是---将我们应用中的png和jpeg图片全部转换成WebP格式的图片。

你的内心可能要想:啊~我的项目中这么多图片,我该怎么去转啊。

不要担心,你想的谷歌已经替你想好了,在Android Studio中一键就可以进行转换了,只需要将图片全选,然后点击右键,选择如下图的按钮即可:


image.png

点击之后就进入了预览界面,你可以随意选择需要的质量,默认的是原图的75%。


image.png

然后点击Finish即可完成,图上显示不全,我的最新版本号是19。
注意:
  • 转换会默认把原图删除,请提前做好备份
  • Android4.3之前无法识别WebP格式的图片,需要进行(libwebp库)编译,不过现在的手机基本没有4.3以下的了,基本默认最低开发版本都是19(Android4.4)。但如果开发电视或者固定设备就必须进行兼容编译,这里就不详细说明了。

去除多语言

好多人没有注意过这一点,的确,这一步优化的效果可能不是特别大,但----蚊子再小也是肉啊!

大家可以在Android Studio中打开自己的apk文件,找见resources.arsc,在下面找见string,如下图所示:

image.png

AS默认将所有语言全部打包进入咱们的apk,但是咱们基本使用的只有中文和英语,默认是英语,所以无需配置,只需配置中文。

那么,需要怎样配置呢?且听我娓娓道来:首先,打开moudle的build.gradle,在android中的defaultConfig里加入下面的代码即可:

resConfigs "zh", "zh-rCN"
配置了之后在进行打APK包就不会有那些乱七八糟的语言存在了。

ndk {
abiFilters 'armeabi'/,'x86', 'armeabi-v7a'/
// 还可以添加 'x86', 'x86_64', 'mips', 'mips64'
}

第三步:去除无用动态库

image.png

千万要注意:这里说的是去除无用动态库,不是不使用动态库。动态库在咱们的项目中使用的非常多,比如使用三方地图服务:百度地图、高德地图、腾讯地图等等,都需要导入动态库,都会有几个不同的版本:armeabi、armeabi-v7a、x86、x86_64、mips等,而且每一个版本都很大,但目前市面上的手机有99%以上都是用的armeabi-v7a,咱们可以只保留armeabi-v7a的即可,大家如果不信的话可以解压市面上比较流行的APK,你会发现他们的包也都是只有armeabi和armeabi-v7a,包括微信、支付宝,所以咱们这么做也是没有问题的。

这一步是减少应用体积最明显的,但具体该怎么做呢?很简单,还是在moudle的build.gradle中,还是上一步的位置:android中的defaultConfig中,修改以下代码即可:

ndk {
       abiFilters 'armeabi'/*,'x86', 'armeabi-v7a'*/
            // 还可以添加 'x86', 'x86_64', 'mips', 'mips64'
    }

简单说一下armeabi和armeabi-v7a的区别吧:armeabi和armeabi-v7a都表示cpu的类型,不同的cpu的特性不一样,armeabi就是针对普通的或旧的arm cpu,armeabi-v7a是针对有浮点运算或高级扩展功能的arm cpu。

第四步:移除无用资源
我们的应用在开发过程中往往会有很多忘记清理的垃圾图片、布局或者其他资源,所以要进行清理,这也是APK极限压缩的一大步。有两种方式可以进行移除无用资源。

第一种(不推荐):一键移除,如果出现使用动态id使用资源会出现问题(比如使用反射调用或者使用getIdentifier)


image.png

第二种(推荐):使用Android Studio自带的Lint工具,有人可能不知道Lint是什么,Lint 是Android Studio 提供的 代码扫描分析工具,它可以帮助我们发现代码结构/质量 问题,同时提供一些解决方案,而且这个过程不需要我们手写测试用例。下面是操作方式:


image.png

然后输入unused resource


image.png

貌似flutter项目的作用不大,具体原因有待研究。

然后点击回车进入选择Lint界面,可以选择是整个项目或者某个moudle:


image.png

点击OK进入Lint详情界面,这里竟然没有??难道是flutter支持不是很好。


image.png

第五步:开启混淆ProGuard
这一步很关键,并不是为了减小APK的体积才开启的混淆,最主要是为了安全,如果你不想你的应用代码直接被别人很轻松的看到的话(当然混淆后也可通过某些技术进行查看,但起码会比不开启混淆要安全一些)

混淆有三大作用:

1、压缩:移除未被使用的类、属性、方法等,并且会在优化动作执行之后再次执行(因为优化后可能会再次暴露一些未被使用的类和成员。

2、优化:优化字节码,并删除未使用的结构。

3、混淆:将类名、属性名、方法名混淆为难以读懂的字母

混淆开启的方式也很简单,还是在moudle的builg.gradle中,还是android中,不过这次不是defaultConfig中了,而是buildTypes中:

buildTypes {
        release {
            minifyEnabled true // 开启混淆
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

我只在release包中开启了混淆,大家如果有需要也可以在debug中加上混淆。

这样就完了?不不不,混淆没有这么简单,上面说到:混淆将类名、属性名、方法名混淆为了难懂的字母。这很好,可以保证安全的情况下又减小了APK的体积,但是-----如果你的项目中用到了反射呢?通过你的包名和类名找不到你的文件(因为类名属性名已经改变),那么肯定会崩溃,不只是你的项目中自己写的代码,还有你所使用的三方库中如果也使用了反射的话也需要进行处理。

那么问题来了,在哪里进行处理呢?上面的配置文件中其实已经写到了:proguard-rules.pro中,在里面需要写上不需要进行混淆的类或属性、或者是你的native方法也要注意加上。使用的三方库一般在Github介绍中会写明混淆需要添加的内容。在这里就,简单贴一段常用的混淆规则吧:

#默认的proguard-android.txt已经增加了Annotation、native、view的setget方法、Activity参数为view的  方法、Enum枚举、Parcelable、R,此处不再写
#------------------------------------------通用区域----------------------------------------------------
#----------------------基本指令------------------------
# 代码混淆压缩比,在0~7之间,默认为5,一般不做修改
-optimizationpasses 5
# 混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames
# 指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses
# 指定不去忽略非公共库的类成员
-dontskipnonpubliclibraryclassmembers
# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
-dontpreverify
# 这句话能够使我们的项目混淆后产生映射文件
# 包含有类名->混淆后类名的映射关系
-verbose
-printmapping proguardMapping.txt
# 指定混淆是采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*
# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable

#如果引用了v4或者v7包
-dontwarn android.support.**
-keep class android.support.** { *; }
-keep interface android.support.** { *; }
-keep public class * extends android.support.**
-dontwarn android.support.**

#如果引用了androidx包
-keep class com.google.android.material.** {*;}
-keep class androidx.** {*;}
-keep public class * extends androidx.**
-keep interface androidx.** {*;}
-dontwarn com.google.android.material.**
-dontnote com.google.android.material.**
-dontwarn androidx.**

#---------------------默认保留-------------------------
## 基础保留 ##
# 保留我们使用的四大组件,自定义的Application等等这些类不被混淆
# 因为这些子类都有可能被外部调用
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService

-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
}
# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
}
#保持自定义控件类不被混淆
-keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);
}


# 保留support下的所有类及其内部类
-keep class android.support.** {*;}

# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**

# 保持枚举 enum 类不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

# 保持 Serializable 不被混淆
-keep class * implements java.io.Serializable
#保持 Serializable 不被混淆并且enum 类也不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 保留R下面的资源
-keep class **.R$* {*;}
#不混淆资源类
-keepclassmembers class **.R$* {
    public static <fields>;
}
# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}
#EventBus的注解
-keepclassmembers class * {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
#WebView
-keepclassmembers class * extends android.webkit.WebView {*;}
-keepclassmembers class * extends android.webkit.WebViewClient {*;}
-keepclassmembers class * extends android.webkit.WebChromeClient {*;}
-keepclassmembers class * {
    @android.webkit.JavascriptInterface <methods>;
}

#-------------------------------------------项目定义区-------------------------------------------------
#sqflite
-keep class com.tekartik.sqflite.** { *; }
#Flutter Wrapper
-dontwarn io.flutter.**
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.**  { *; }
-keep class io.flutter.util.**  { *; }
-keep class io.flutter.view.**  { *; }
-keep class io.flutter.**  { *; }
-keep class io.flutter.plugins.**  { *; }
-dontwarn io.flutter.embedding.**

OK,混淆就说到这里,现在再进行打包的话你会发现应用APK的体积又减小了很大一部分。

应用混淆

代码混淆 (Obfuscated code) 亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的方式。通常针对的是二进制文件。实际使用中将可以代码中的各种元素,如变量,函数,类的名字改写成无意义的名字,防止攻击者会应用逆向。

—— 维基百科

要混淆 Flutter 应用程序,首先需要适配不同平台的版本:

  • android /iOS:Flutter 1.16.2 以后支持。
  • macOS:macOS(Flutter 1.13 发布测试版)也支持在 Flutter 1.16.2 版本后混淆。
  • Linux / Windows:不支持
  • Flutter Web:不支持

第六步:开启删除无用资源(和第四步的Lint不同)
这一步很简单,只要在上一步混淆下面添加下面的一行代码即可:

buildTypes {
        release {
            minifyEnabled true // 混淆
            shrinkResources true // 去除无用资源 与lint不同
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

需要注意的是,删除无用资源的配置必须在开启混淆下才有用,如果关闭混淆的话,那么这个配置即不起作用。

测试结果正常,但是发现混淆对泛型十分不友好,很可能因为泛型导致混淆之后APP不能正常使用,比如我在 Flutter Dio二次封装 这篇文章里面的那个json转换工厂 EntityFactory:

class EntityFactory {
  static T generateOBJ<T>(json) {
    if (json == null) {
      return null;
    }
    else if (T.toString() == "LoginEntity") {
      return LoginEntity.fromJson(json) as T;
    }
    else {
      return json as T;
    }
  }
}

这里需要使用 T.toString() == "LoginEntity" 对泛型进行具体的判断,但是混淆之后泛型 T 传进来的是混淆之后的符号,所以导致 T.toString() == "LoginEntity" 判断失败无法使用具体的实体去解析 json ,我想下一步需要找办法配置对某些文件进行混淆排除。

读取混淆堆栈

要调试混淆后的应用,可以执行以下两个步骤:

找到符号映射表文件。如在 Android arm64 下发生 crash,可以分析 app.android-arm64.symbols 文件。
运行 flutter symbolize 命令,并指定堆栈跟踪的文件和符号映射表文件即可:

flutter symbolize -i <stack trace file> -d /out/android/app.android-arm64.symbols

关于 symbolize 命令的详细信息,可以运行 flutter symbolize -h 查看。

优化包体积

Flutter 1.22 之后,官方发布了一款应用包大小的分析工具,可以帮助开发者直观地看到应用各个模块占用空间大小的详细信息。我们只需要在构建应用时传入 --analyze-size 参数即可使用该分析工具:

flutter build apk --analyze-size
flutter build appbundle --analyze-size
flutter build ios --analyze-size
flutter build linux --analyze-size
flutter build macos --analyze-size
flutter build windows --analyze-size

如果构建的是 Android APK 或 bundle,则还需要指定目标平台架构,可以如下这些命令:

flutter build apk --target-platform android-arm --analyze-size 
flutter build apk --target-platform android-arm64 --analyze-size 
flutter build apk --target-platform android-x64 --analyze-sizeflutter build appbundle --target-platform android-arm --analyze-size 
flutter build appbundle --target-platform android-arm64 --analyze-size 
flutter build appbundle --target-platform android-x64 --analyze-size

运行结果如下:


image.png

image.png

image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,252评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,886评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,814评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,869评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,888评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,475评论 1 312
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,010评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,924评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,469评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,552评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,680评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,362评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,037评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,519评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,621评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,099评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,691评论 2 361

推荐阅读更多精彩内容

  • 要想进行apk瘦身,首先我们得知道我们的apk是哪些部分体积比较大,我们可以借助android studio来分析...
    zivxia阅读 3,076评论 0 4
  • 一.概述 众所周知,App包体积大小的重要性不言而喻,对于客户来说,小体积的包意味着更少的消耗流量,更快的下载时间...
    晨心w阅读 1,106评论 0 3
  • 一、瘦身优化及 Apk 分析方案介绍 1.1 瘦身优势 我们首先来介绍下,为什么我们需要做 APK 的瘦身优化? ...
    凯玲之恋阅读 790评论 0 0
  • 1.将图片转换成webp 这个图片格式相比png或者jpg格式的图片损失的质量几乎可以忽略不计,但图片压缩后的体积...
    hxljy阅读 252评论 0 5
  • 今天青石的票圈出镜率最高的,莫过于张艺谋的新片终于定档了。 一张满溢着水墨风的海报一次次的出现在票圈里,也就是老谋...
    青石电影阅读 10,350评论 1 2