Android布局、网络、电量、APP启动、APP瘦身分析和优化

Android布局、网络、电量、APP启动、APP瘦身分析和优化

一、布局优化

布局优化的一个目的也就是减少层级的嵌套,提高渲染,从而避免卡顿问题出现。我们都知道大部分设备屏幕刷新率是60HZ,也就是渲染一帧的时间是16.6ms,如果出现某一帧渲染时间超过这个时间,默认出现了掉帧情况。这样一来总的刷新帧率就下降了,比如由原来的60HZ降到了30HZ,那么就会感觉明显的卡顿。

1、避免过度绘制 overdraw

一般理想状态下就是屏幕像素只绘制一次,但是通常很多情况下重叠布局造成一些像素多余不必要绘制,这就出现了overdraw

  • 通过开发者选项-调试GPU过度绘制-显示过度绘制的区域,可以根据相应检查颜色区域来调整布局,减少过度绘制。)

原色 – 没有过度绘制 – 这部分的像素点只在屏幕上绘制了一次。
蓝色 – 1次过度绘制– 这部分的像素点只在屏幕上绘制了两次。
绿色 – 2次过度绘制 – 这部分的像素点只在屏幕上绘制了三次。
粉色 – 3次过度绘制 – 这部分的像素点只在屏幕上绘制了四次。
红色 – 4次过度绘制 – 这部分的像素点只在屏幕上绘制了五次。

一般情况下蓝色区域是可接受的,但是如果出现了红色,则需要去优化下布局,避免过度绘制overdraw.

2、减少布局层次嵌套,加快渲染速度

  • 1、尽量少的使用RelativeLayout,可以使用FrameLayout替代或约束布局替代;避免RelativeLayout嵌套RelativeLayout.

  • 2、减少嵌套层次以及控件个数,比如使用TextView的DrawableLeft替代一个图片和文字渲染,避免LinearLayout+ImageView+TextView的使用。

  • 3、Android中的布局文件加载是LayoutInflater利用pull解析XML,然后根据节点名反射方式创建View对象实例。那么随着控件数量越多、布局嵌套层次越深,展开布局花费的时间几乎是线性增长,性能也就越差。

3、使用检测工具Hierarchy Viewer和Profilling GPU Rendering,TraceView排查卡顿,从而优化布局。

可以使用Hierarchy Viewer来查看布局嵌套情况预计View控件的个数,此外还会给出measure测量、layout布局、draw绘制的时间,可根据相关检测做出相应的布局优化。

或者使用AndroidStudio中的Profiling GPU Rendering

从Android M版本开始,GPU Profiling工具把渲染操作拆解成如下8个详细的步骤进行显示。

  • Swap Buffers:表示处理任务的时间,也可以说是CPU等待GPU完成任务的时间,线条越高,表示GPU做的事情越多;
  • Command Issue:表示执行任务的时间,这部分主要是Android进行2D渲染显示列表的时间,为了将内容绘制到屏幕上,Android需要使用Open GL ES的API接口来绘制显示列表,红色线条越高表示需要绘制的视图更多;
  • Sync & Upload:表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减少该段区域的执行时间,我们可以减少屏幕上的图片数量或者是缩小图片的大小;
  • Draw:表示测量和绘制视图列表所需要的时间,蓝色线条越高表示每一帧需要更新很多视图,或者View的onDraw方法中做了耗时操作;
  • Measure/Layout:表示布局的onMeasure与onLayout所花费的时间,一旦时间过长,就需要仔细检查自己的布局是不是存在严重的性能问题;
  • Animation:表示计算执行动画所需要花费的时间,包含的动画有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦这里的执行时间过长,就需要检查是不是使用了非官方的动画工具或者是检查动画执行的过程中是不是触发了读写操作等等;
  • Input Handling:表示系统处理输入事件所耗费的时间,粗略等于对事件处理方法所执行的时间。一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操作;
  • Misc Time/Vsync Delay:表示在主线程执行了太多的任务,导致UI渲染跟不上vSync的信号而出现掉帧的情况;出现该线条的时候,可以在Log中看到这样的日志:

虽然GPU Profiling能定位问题大概发生在哪个步骤,但是不能准确定位是哪个方法比较耗时,所以最准确的方法还是通过TraceView来精准定位出方法执行耗时时间是哪个,然后做相应排查和优化。

4、使用一些特殊标签、控件优化布局

  • 1、<include>和<merge>配合使用来减少视图层级结构

    <include>标签可以在多处复用布局,而<merge>标签则会减少视图层级,比如在主布局中是LinearLayout,而include进来也是一个LinearLayout,这时候include进来的LinearLayout就是多余的,此时就<include>进来布局中使用<merge>替代LinearLayout.

    <!-- include_title.xml -->
    <merge <!-- 使用merge替代LinearLayout -->
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:scaleType="center"
            android:src="@mipmap/girl"/>
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal|bottom"
            android:background="#aa000000"
            android:gravity="center"
            android:paddingBottom="20dp"
            android:textSize="18sp"
            android:paddingTop="20dp"
            android:text="美丽的女孩"
            android:textColor="#ffffffff"/>
    </merge>
    
    <-- main.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <include layout="@layout/title"/>
    
    </LinearLayout>
  • 2、ViewStub标签的使用

    ViewStub 是一个轻量级的View,宽和高都为0,实现View的延迟加载,避免资源的浪费,减少渲染时间,在需要的时候才加载View。比如有些布局在实际需求中,是需要满足某个条件才出现,此时就可以使用ViewStub引入。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center"
        android:orientation="vertical">
    
        <ViewStub
            android:id="@+id/stub"
            android:inflatedId="@+id/panel_import"
            android:layout="@layout/common_title1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    
    </LinearLayout>
    <!-- 下面布局只有在网络失败情况才显示,所以在引入主布局的时候使用ViewStub -->
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:background="#55000000"
        android:orientation="vertical"
        android:paddingBottom="20dp"
        android:paddingLeft="40dp"
        android:paddingRight="40dp"
        android:paddingTop="20dp">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        android:text="当前无网络"
        android:textSize="15sp"/>
    
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="10dp"
        android:text="点击重试"
        android:textSize="16sp"/>
```
  • 3、渲染图片和文字并排时,尽量使用DrawableLeft等去替代使用LinearLayout+TextView+ImageView的使用。

        <TextView
            android:id="@+id/txt_collect"
            android:layout_width="fill_parent"
            android:layout_height="50dp"
            android:background="@drawable/setting_item_selector"
            android:drawableLeft="@mipmap/icon_me_collect"
            android:drawablePadding="10dp"
            android:drawableRight="@mipmap/right"
            android:gravity="center_vertical"
            android:paddingLeft="16dp"
            android:paddingRight="16dp"
            android:text="@string/collection"
            android:textSize="16sp"/>
  • 4、使用Space控件来替代View标签实现分割线,Space空间的意思,表示该控件占据一定的空间,但是却不显示任何东西。已经是过时了。

     <android.support.v4.widget.Space
            android:layout_width="match_parent"
            android:layout_height="10dp"/>
  • 5、自定义View时需要注意onDraw不进行复杂计算以及大量对象创建,还有使用第三方UI库需要更加谨慎,避免引入性能开销比较大的组件。

5、总结

  • 调试GPU过度绘制,将Overdraw降低到合理范围内;
  • 减少嵌套层次及控件个数,保持view的树形结构尽量扁平(使用Hierarchy Viewer可以方便的查看),同时移除所有不需要渲染的view;
  • 使用GPU配置渲染工具,定位出问题发生在具体哪个步骤,使用TraceView精准定位代码;
  • 使用标签,Merge减少嵌套层次、ViewStub延迟初始化。

二、网络优化

1、网络监控

  • 1、AndroidStudio自带的Network Monitor可以大概看出某个时间段内网络请求的数量和访问速率

  • 2、Charles抓包工具

2、网络优化

网络优化主要从三个方面进行: 速度、成功率、流量

Gzip压缩

HTTP协议上的Gzip编码是一种用来改进WEB应用程序性能的技术,用来减少传输数据量大小,减少传输数据量大小有两个明显的好处

  • 可以减少流量消耗;
  • 可以减少传输的时间

IP直连与HttpDns

DNS解析的失败率占联网失败中很大一种,而且首次域名解析一般需要几百毫秒。针对此,我们可以不用域名,才用IP直连省去 DNS 解析过程,节省这部分时间。

网络图片处理

  • 1、图片下载:

    • 使用WebP格式;同样的照片,采用WebP格式可大幅节省流量,相对于JPG格式的图片,流量能节省将近 25% 到 35 %;相对于 PNG 格式的图片,流量可以节省将近80%。最重要的是使用WebP之后图片质量也没有改变。

    • 使用缩略图;App中需要加载的图片按需加载,列表中的图片根据需要的尺寸加载合适的缩略图即可,只有用户查看大图的时候才去加载原图不仅节省流量,同时也能节省内存!之前使用某公司的图片存储服务在原图链接之后拼接宽高参数,根据参数的不同返回相应的图片。

  • 2、图片上传

    • 避免整文件传输,采用分片传输;
    • 根据网络类型以及传输过程中的变化动态的修改分片大小;
    • 每个分片失败重传的机会。

协议层的优化

使用最新的协议,Http协议有多个版本:0.9、1.0、1.1、2等。新版本的协议经过再次的优化,例如:

  • Http1.1版本引入了“持久连接”,多个请求被复用,无需重建TCP连接,而TCP连接在移动互联网的场景下成本很高,节省了时间与资源;
  • Http2引入了“多工”、头信息压缩、服务器推送等特性。

请求打包

合并网络请求,减少请求次数对于一些接口类如统计,无需实时上报,将统计信息保存在本地,然后根据策略统一上传。这样头信息仅需上传一次,减少了流量也节省了资源。

网络缓存

对服务端返回数据进行缓存,设定有效时间,有效时间之内不走网络请求,减少流量消耗。对网络的缓存

其他

  • 弱网不仅仅指代网络不好,移动互联网的网络带宽很容易出现“跳变”,下一秒的传送速度可能降到前一秒的几十分之一;而且即便是信号满格也传不出一个字节;
  • 对于真正的弱网,可以使用抓包工具进行模拟,也有聪明的小伙伴使用wifi精灵进行限速;
  • Facebook的开源项目augmented-traffic-control可以模拟不同的网络环境,针对带宽、时延抖动、丢包率、错包率、包重排序率等方面,堪称弱网调试神器;

三、电量优化

关于电量的优化可能关注都比价低

1、电量功耗的检测

  • 注册电量监控的广播: ACTION_BATTERY_CHANGED,然后就可以获取电池电量、充电状态、电池状态等信息

    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_BATTERY_CHANGED);
    registerReceiver(filter,receiver);

    缺点:

    1. 获取到的是手机整体的耗电量,而不是特定App的耗电量;
    2. 实时性差,精度较低,只能接受被动通知电量余量以及跳变。
  • Battery Historian: 最强大、最推荐的工具:Battery Historian是Android5.0之后Google开源的一款用于检测与电池有关的信息和事件的工具,从设备中收集电池数据,然后使用Battery Historian可以可视化分析相关指标如耗电比例、Wifi、蜂窝数据量、WakeLock唤醒次数。随着Android6.0更新了Battery Historian 2.0加入引起手机状态变化的应用。

    通过Battery Historian可以方便的看到各耗电模块随着时间的耗电情况:包含操作类型、执行时间、对应App等;还可以进行筛选特定的App,给出一个总结性的说明,包括:Network Information、 Syncs、WakeLock、Services、Process info、Scheduled Job、Sensor Use等,查看每一个模块的总结,可以看出来每一项的耗时以及执行次数。当发现异常的时候可以针对性的进行排查。

    adb命令导出电量信息:

    adb shell dumpsys batterystats --reset(Android4.1到4.3 adb shell dumpsys batteryinfo)
    adb bugreport > bugreport.txt(Android7.0以上 adb bugreport bugreport.zip)

    安装Battery Historian后打开:http: //localhost:9999/, 上传bugreport.txt文件开始分析)

2、电量优化方法

Android系统的APP的电量消耗主要由: CPU、wakelock、数据传输(wifi和流量)、wifi运行、GPS、其他传感器等。

1、CPU时间片优化和检测

当检测到CPU时间片消耗异常时,需要使用TraceView,获取进程执行信息,定位CPU占用率异常的问题。

2、网络传输

通常情况下,使用3G移动网络传输数据,电量的消耗有三种状态:

Full power: 能量最高的状态,移动网络连接被激活,允许设备以最大的传输速率进行操作。

Low power: 一种中间状态,对电量的消耗差不多是Full power状态下的50%。

Standby: 最低的状态,没有数据连接需要传输,电量消耗最少。

  • 1、数据压缩 通过数据压缩等方式缩减传输时间,降低电量消耗,

  • 2、选择更快的传输方式 虽然3G芯片比Wifi芯片耗电低,但Wifi的速率可以让数据在较短时间内完成传输,从而降低电量消耗。

  • 3、请求集中发送 对于分析和统计打点的网络请求,可以在合适状态下发送。比如使用JobScheduler

  • 4、无网状态避免网络请求 网络请求失败之后的重试机制,但是要注意这个重试是在有网状态下的重试。否则无网状态下重试不会请求成功,只会消耗电量。尤其是与AlarmManager或者WakeLock连用的场景下,耗电量会更多。

3、GPS

定位是App中常用的功能,但是并不是所有场景下都保持定位的可用。

  • 1、选择合适的Location Provider,在Android中提供三种定位方式: GPS_PROVIDER(定位精准度高,一般在10米左右,耗电量大)NETWORK_PROVIDERPASSIVE_PROVIDER

  • 2、及时注销定位监听在获取到定位之后或者程序处于后台时,注销定位监听,此时监听GPS传感器相当于执行no-op(无操作指令),用户不会有感知但是却耗电

  • 3、多模块使用定位尽量复用,多个模块使用定位,尽量复用上一次的结果,而不是都重新走定位的过程,节省电量损耗;例如:在应用启动的时候获取一次定位,保存结果,之后再用到定位的地方都直接去取。

4、谨慎使用WakeLock

Android为了节省电量,会在用户无操作一段时间之后进入休眠状态。Wake Lock是一种锁的机制,只要有人拿着这个锁,系统就无法进入休眠。一些App为了能在后台持续做事情,就会持有一个WakeLock,那么手机就不会进入休眠状态,App要做的事情能做了,但是也更加耗电。

  • 1、App在前台不要申请WakeLock,此时无需申请,申请的话会计算到应用电量消耗;
  • 2、App在后台由于业务需要必须要申请WakeLock时使用带有超时参数的方法,防止由于忘记或者异常情况下没有释放;
  • 3、App申请使用WakeLock,任务结束之后及时释放,让系统再次进入休眠状态。

5、传感器的使用

  • 1、使用传感器,选择合适的采样率,越高的采样率类型则越费电;

  • 2、在后台时注意及时注销传感器监听

6、 JobScheduler的使用

使用JobScheduler,一些任务通过JobScheduler来触发,例如可推迟的网络请求、下载、GPS等,可以在特定场景:连接Wifi、连接电源等场景触发。既完成了任务,也无需考虑由于一些任务导致的电量消耗。

四、APP启动优化

要想优化APP启动速度,首先必须了解APP的启动流程以及启动类型。Android中把应用启动分为三大类: 冷启动、热启动、温启动。每种状态都会影响应用向用户显示所需的时间:冷启动、温启动或热启动。在冷启动中,应用从头开始启动。在其他状态下,系统需要将后台运行中的应用带入前台。建议您始终在假定冷启动的基础上进行优化。这样做也可以提升温启动和热启动的性能。

1、冷启动、热启动、温启动

  • 冷启动: 是指应用从头开始启动:系统进程在冷启动后才创建应用进程。发生冷启动的情况包括应用自设备启动后或系统终止应用后首次启动。这种启动给最大限度地减少启动时间带来了最大的挑战,因为系统和应用要做的工作比在其他启动状态下更多。

    在冷启动开始时,系统有三个任务。这三个任务是:

    1. 加载并启动应用。
    2. 在启动后立即显示应用的空白启动窗口。
    3. 创建应用进程

    系统一创建应用进程,应用进程就负责后续阶段:

    1. 创建应用对象。
    2. 启动主线程。
    3. 创建主 Activity。
    4. 扩充视图。
    5. 布局屏幕。
    6. 执行初始绘制。

    一旦应用进程完成第一次绘制,系统进程就会换掉当前显示的后台窗口,替换为主 Activity。此时,用户可以开始使用应用。)

  • 热启动: 应用的热启动比冷启动简单得多,开销也更低。在热启动中,系统的所有工作就是将您的 Activity 带到前台。如果应用的所有 Activity 都还驻留在内存中,则应用可以无须重复对象初始化、布局扩充和呈现。

    但是,如果一些内存为响应内存整理事件(如 [onTrimMemory()](https://developer.android.com/reference/android/content/ComponentCallbacks2.html#onTrimMemory(int)))而被完全清除,将需要重新创建这些对象,以响应热启动事件。

    热启动显示的屏幕上行为和冷启动场景相同:系统进程显示空白屏幕,直到应用完成 Activity 呈现。

  • 温启动: 涵盖在冷启动期间发生的操作的一些子集;同时,它的开销比热启动多。有许多潜在状态可视为温启动。例如:

    用户退出您的应用,但之后又重新启动。进程可能已继续运行,但应用必须通过调用 [onCreate()](https://developer.android.com/reference/android/app/Activity.html#onCreate(android.os.Bundle)) 从头开始重新创建 Activity。

    系统将您的应用从内存中逐出,然后用户又重新启动它。进程和 Activity 需要重启,但传递到 [onCreate()](https://developer.android.com/reference/android/app/Activity.html#onCreate(android.os.Bundle)) 的已保存实例状态包对于完成此任务有一定助益。

    作为普通应用,App进程的创建等环节我们是无法主动控制的,可以优化的也就是Application、Activity创建以及回调等过程

2、如何去优化

针对启动优化,Google官方给出启动加速优化的方向

  • 1、利用提前展示出的Window,快速展示出一个界面,给用户快速反馈的体验(这不是真正优化速度,而是给用户造成启动快的假象)

  • 2、避免在启动时做密集型的Application初始化工作,特别是一些第三方的初始化

  • 3、创建 Activity 通常需要进行大量的高开销工作。通常有机会优化这项工作以实现性能改进,耗时的IO操作、数据库读写操作、复杂嵌套的布局等

3、启动优化之主题切换

按照官方文档的说明:使用Activity的windowBackground主题属性来为启动的Activity提供一个简单的drawable。 Layout XML file:

    <layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
      <!-- The background color, preferably the same as your normal theme -->
      <item android:drawable="@android:color/white"/>
      <!-- Your product logo - 144dp color version of your app icon -->
      <item>
        <bitmap
          android:src="@drawable/product_logo_144dp"
          android:gravity="center"/>
      </item>
    </layer-list>

styles.xml

   <style name="SplashTheme" parent="AppTheme.NoActionBar">
        <item name="android:windowBackground">@drawable/bg_splash</item>
    </style>

AndroidManifest.xml

    <activity ...
    android:theme="@style/AppTheme.Launcher" />

要切回到正常主题背景,最简单的方式是先调用 [setTheme(R.style.AppTheme)](https://developer.android.com/reference/android/view/ContextThemeWrapper.html#setTheme(int)),再调用 super.onCreate()setContentView()

    class MyMainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            // Make sure this is before calling super.onCreate
            setTheme(R.style.Theme_MyApp)
            super.onCreate(savedInstanceState)
            // ...
        }
    }

4、启动优化之减少密集Application初始化

  • 考虑异步初始化三方组件,不阻塞主线程;比如X5的TBS服务自己就是在子线程中初始化的
  • 延迟部分三方组件的初始化;实际上我们粗粒度的把所有三方组件都放到异步任务里,可能会出现WorkThread中尚未初始化完毕但MainThread中已经使用的错误,因此这种情况建议延迟到使用前再去初始化;
  • 闪屏页的2秒停留可以利用,把耗时操作延迟到这个时间间隔里。

5、启动优化之定位卡顿问题影响启动优化速度

我们可以通过BlockCanary,ANRWatchDog,Method Tracing等检测工具,分别从不同粗细粒度去排查卡顿出现的问题。从而有针对性进行优化

比如常见问题:

  • 部分数据库及IO的操作发生在首屏Activity主线程;
  • Application中创建了线程池;
  • 首屏Activity网络请求密集;
  • 工作线程使用未设置优先级;
  • 信息未缓存,重复获取同样信息;

五、APP体积大小优化

APP瘦身实际上也就是我们常说缩小APK的体积,缩小包体积对于用户而言就意味更少流量消耗。

1、APK的组成

APK包主要由asstes目录、lib目录、META-INF目录、res目录、AndroidManifest.xml文件、classes.dex、resource.arsc文件组件成。

APK相关目录、文件的含义:

文件目录 说明
assets/ 存放一些静态文件,可以通过AssertManager访问
lib/ 编译以后一些so库文件存放于此
META-INF/ 保存APK签名文件
res/ APK资源目录,比如常用的drawable、layout、value等
AndroidManifest.xml APP程序全局配置文件
classes.dex Java或Kotlin编译后的Class,就是ART虚拟机所理解文件格式
resource.arsc 编译后生成二进制资源文件

我们还可以通过AndroidStudio中的Analyze APK来查看分析APK组成结构。

通过查看了APK组成之后,我们就知道从哪些方向着手缩小APK体积,主要针对以下几个方面

  • 1、代码层面: 移除无用的功能、冗余的代码、代码混淆、方法数减少等

  • 2、资源层面: 移除冗余资源、资源混淆压缩、图片处理等

  • 3、对于so库文件的处理

2、缩小APK体积之代码瘦身

  • 1、移除无用的代码和下线的功能,有助于减少代码量,可以直接较少dex的体积

  • 2、移除无用的库、避免功能相同的库,比如多份网络请求库或图片加载库等

  • 3、尽量最小程度上引入第三方SDK,如果仅仅使用SDK中一小部分功能且有相关接口可以实现的话,建议不要引入SDK

  • 4、引入第三方SDK时,也需要考量它的方法数等

  • 5、启用Proguard代码混淆,可以检测移除一些未使用的类、方法、属性,优化字节码以及冗余的代码,并且能缩短方法、属性名字。

  • 6、缩小方法数,我们都知道MultiDex是为了解决65536方法数的问题,但是这里可以使用MultiDex减少方法数,可以看到Dex文件的组成。而从header-item表中的method-ids-size字段可以看出,方法数缩减之后,可以减少方法列表的大小;同时,方法在Dex文件中的占用空间也减少了,App自然被瘦身。

  • 7、还有就是编写代码中也可以做一些优化,比如避免编译器生成一些额外方法或类、属性,Kotlin中lambda作用域变量捕获就会生成额外方法或类等,总之减少不必要的方法数。

3、缩小APK体积之资源瘦身

资源瘦身往往是APK瘦身的大头部分,往往效果会比代码瘦身来得更直接,比如一张图片压缩后就减少几MB左右。

  • 1、移除无用的资源文件,在开发阶段,我们可以使用AS中的Refactor-Remove Unused Resources,来选择清除无用资源文件。或者在build.gradle中设置shrinkResources为true后,每次打包的时候就会自动排除无用的资源。shrinkResources需要配合minifyEnabled一起使用。

  • 2、Drawable目录只保留一份资源,尽管官方说保留多份Drawable适合不同设备,但是为了APK体积考虑还是建议只保留一份适配主流设备机型的Drawable目录

  • 3、开发阶段对图片进行统一压缩,比如使用TinyPng

  • 4、PNG转换成JPG,PNG是一种无损格式,JPG是有损格式。JPG在处理颜色很多的图片时,根据压缩率的不同,有时会去掉一些肉眼识别差距较小的中间颜色。针对小尺寸、色彩较少具有alpha通道透明度使用PNG,对于色彩比较丰富,大尺寸的使用JPG

  • 5、对于色调单一的图片使用SVG矢量图,矢量图体积比一般图小很多,且能适配所有机型

  • 6、使用webp,webp支持透明alpha通道且压缩比比JPG更高,体积更小

  • 7、资源混淆,在Apk打包过程中,aapt会将每一个资源生成一个对应的int数值,而我们通过这个int值来查找使用资源。在Apk构成中,我们可以看到里面有一个resources.arsc文件,里面保存着资源id和资源key的映射关系。

    当调用图片时,先找到drawable分类,再根据当前的系统config找到匹配的config表,根据id找到对应的res数据。drawable在arsc中是当做string类型保存的,res数据中有这个资源在res string pool池中的索引。根据这个索引可以在字符串池中找到一个字符串。这个字符串其实就是一个路径,比如:res/drawable-xhdpi/icon.png;混淆就是将这个路径改为R/s/f.png;同时修改resources.arsc文件的映射关系。这样就能清楚的看出来资源混淆能减小Apk的原因:

    • resources.arsc变小;
    • 文件信息变小,采用了超短路径,res/drawable-xhdpi/icon.png被修改为R/s/f.png。

4、缩小APK体积之so库瘦身

so库(shared object,共享库)是机器可以直接运行的二进制代码库,是Android上的动态链接库,类似于Windows上的dll。so库一般用于:加解密算法、音视频、地图等,然后需要针对市面上手机CPU架构进行适配,分别是:armv7a、x86、armv8a、x86_64等。

  • 可以根据需要选择一种或几种主流设备的CPU架构进行适配支持,比如armeabi就能适配兼容别的平台,但是失去特定平台的优化,处于包体积考虑还是值得考虑,很明显so库占用APK体积还是很大的。

    //可以指定设置支持SO的CPU架构类型
    ndk {
                abiFilters "armeabi-v7a"
        }

5、缩小APK体积之7zip压缩

Apk文件实际上就是一个Zip文件。Android SDK的打包工具apkbuilder采用的是Deflate算法将Android App的代码、资源等文件进行压缩,压缩成Zip格式,然后签名发布。

既然是压缩,那能不能改进其压缩方式,获取更小的Apk文件?通过分析Apk打包的流程图我们可以发现SignedJarBuilder类对整个工程包括代码Dex和一些课压缩的资源、文件进行压缩,使用的是JDK中zip包下提供的算法。

  • 简单的方式我们可以在不改变App编译器工作的情况下,对生成的Apk文件进行二次压缩,同样使用Deflate算法,但是将压缩等级从标准提升到极限压缩。提高压缩级别可在不对Apk包本身的内容做任何修改的情况下得到更小的Apk。

  • 需要注意这样极限压缩之后的签名被破坏,需要重新签名。

  • Android平台对Apk安装包的解压算法只支持Deflate算法,其它算法如LZMA,虽然压缩率更好,但是由于Android平台默认不支持,所以如果采用这种算法压缩Apk,会导致Apk无法安装。

  • 目前在Mac上没发现好用的7Zip压缩软件,需要在Windows下使用。

6、总结

1、代码瘦身

  • 移除无用代码、功能;

  • 移除无用的库、避免功能雷同的库;

  • 启用Proguard;

  • 缩小方法数

2、资源瘦身

  • 移除无用的资源文件;
  • Drawable目录只保留一份资源;
  • 对图片进行压缩;
  • PNG转换JPG;
  • 使用矢量图;
  • 使用WebP;
  • 资源混淆;
  • 资源在线化;****

3、so库瘦身

  • 在允许的情况下,针对用户机型分布保留特定架构的So;

4、7Zip压缩

  • 使用7Zip对Apk进行极限压缩。

   转载规则


《Android布局、网络、电量、APP启动、APP瘦身分析和优化》 mikyou 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Android-H5首屏秒开优化方案 Android-H5首屏秒开优化方案
Android-H5首屏秒开优化方案一、遇到的问题 在实现阅读APP从原生渲染迁移到webview渲染过程中,发现WebView第一加载会出现白屏,但是后续打开WebView速度就会明显快很多。 二、具体实现方案 首先,通过客户端去请求A
2019-12-22
下一篇 
Android Native与Web JS双向通信框架-PieBridge Android Native与Web JS双向通信框架-PieBridge
Android Native与Web JS双向通信框架-PieBridge Android Native与Web JS双向通信框架 一、框架流程图 二、支持的功能点 1、支持Android Native调用JS中注册的方法,支持JS层数据
2019-12-21