Android性能优化汇总

Android性能优化可以从以下几个方面展开

  • 流畅度优化
  • 内存优化
  • 电量优化
  • 安装包优化

流畅度优化:

Android设备的刷新频率一般是是60HZ也就是一秒60帧,每一帧16ms左右。每一帧数据都会经过CPU计算,GPU合成格栅化然后显示出来

首先需要明白为什么会卡:

Android 4.1后引入了VSync机制(垂直同步),可以简单理解为定时中断,每一个VSync间隔是16ms左右,每一个VSync开始时会通知CPU,GPU开始工作,完成后显示当前帧。假如第二帧时CPU或GPU无法在16ms内完成工作,那么第二帧的内容就无法及时显示出来,造成丢帧,卡顿。

所以解决流畅度的关键在于降低CPU和GPU的工作量,避免不必要的计算和过度绘制

  • 布局层次一样的情况下尽量使用LinearLayout,因为RelativeLayout需要测量2次。
  • 复杂布局尽量使用ConstraintLayout或RelativeLayout代替LinearLayout来减少嵌套(可使用Hierarchy Viewer来查看布局结构)
  • 自定义view时使用clipRect裁剪view被遮挡的部分,避免过度绘制
  • 将可复用的组件抽取出来并通过incloud标签使用
  • 使用ViewStub标签来加载一些不常用的布局。view在inflate后就会创建对象,实例化,设置属性也就是说会消耗内存,ViewStub只有在使用的时候才会inflate。
  • 使用merge标签减少嵌套层次
  • 去掉多余的背景颜色,减少过度绘制
  • 在ListView,GridView等多条目view时,因为子组件会被重复创建,所以尽量避免RelativeLayout及LinearLayout的weight属性,它们都会被测量2次。
  • 避免在ListView的getView,自定义view的onDraw中进行复杂的逻辑计算,因为这些都是会频繁执行的方法,会消耗过多的CPU性能

内存优化

  • 能使用IntentService时尽量使用IntentService,防止Service长时间在后台运行,消耗不必要的资源

  • 当界面不可见(用户切换到其他应用),或内存紧张时,释放不必要的内存。Android在Activity中提供了onTrimMemory(),这个方法会在内存降低时通过回调的方式通知

    • TRIM_MEMORY_RUNNING_MODERATE(程序运行时回调):表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经有点低了,系统可能会开始根据LRU缓存规则来去杀死进程了。
    • TRIM_MEMORY_RUNNING_LOW(程序运行时回调):表示应用程序正常运行,并且不会被杀掉。但是目前手机的内存已经非常低了,我们应该去释放掉一些不必要的资源以提升系统的性能,同时这也会直接影响到我们应用程序的性能。
    • TRIM_MEMORY_RUNNING_CRITICAL(程序运行时回调):表示应用程序仍然正常运行,但是系统已经根据LRU缓存规则杀掉了大部分缓存的进程了。这个时候我们应当尽可能地去释放任何不必要的资源,不然的话系统可能会继续杀掉所有缓存中的进程,并且开始杀掉一些本来应当保持运行的进程,比如说后台运行的服务。
    • TRIM_MEMORY_BACKGROUND(程序被缓存时回调):表示手机目前内存已经很低了,系统准备开始根据LRU缓存来清理进程。这个时候我们的程序在LRU缓存列表的最近位置,是不太可能被清理掉的,但这时去释放掉一些比较容易恢复的资源能够让手机的内存变得比较充足,从而让我们的程序更长时间地保留在缓存当中,这样当用户返回我们的程序时会感觉非常顺畅,而不是经历了一次重新启动的过程。
    • TRIM_MEMORY_MODERATE(程序被缓存时回调):表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的中间位置,如果手机内存还得不到进一步释放的话,那么我们的程序就有被系统杀掉的风险了。
    • TRIM_MEMORY_COMPLETE(程序被缓存时回调):表示手机目前内存已经很低了,并且我们的程序处于LRU缓存列表的最边缘位置,系统会最优先考虑杀掉我们的应用程序,在这个时候应当尽可能地把一切可以释放的东西都进行释放。
  • 加载图片优化:首先图片的大小计算公式=长 * 宽 * 每个像素占用的字节数。那么图片的优化可以从以下几个方面进行:

    正常的RGB24是由24位即3个字节来描述一个像素,R、G、B各8位。

    RGB565 就是R-5bit,G-6bit,B-5bit
    RGB888 就是R-8bit,G-8bit,B-8bit

    • 图片大小:加载图片时Android提供了BitmapFactory类,这个类提供了多个解析方法decodeByteArray, decodeFile, decodeResource等来从多个源创建bitmap对象。每一个方法都提供了BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。那么就可以根据控件的大小,再通过BitmapFactory.Options中inSampleSize属性对位图进行等比缩放。
    • 图片质量:对与一些无高清需求的图片可以使用RGB565来降低占用的内存
    • 图片缓存:当图片存在重复利用的情况时,可以使用LruCache对图片进行缓存。
      • LruCache:LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在集合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头。
  • 使用SparseArray代替HashMap。

    • SparseArray内部采用2个一维数组分别存储键和值,键和值的下标是一一对应的。键只能时int类型,所以可以采用二分查找来存储和取出数据。
    • SparseArray的key是int类型,而HashMap则是一个对象,同时HashMap存储时需要一个实体类进行包装。所以SparseArray的内存使用要优于HashMap
  • 在Android中分配给每个进程的内存是有限的,所以业务较复杂时,独立性比较强的功能可以使用多进程,放在单独的一个进程里。缺点是需要进行进程间通讯。

  • 使用内部类时,如果有耗时操作,尽量使用静态内部类,因为非静态内部类会隐式地持有外部类引用,会有内存泄露风险。如果要使用外部类对象,可以通过构造传递进去,然后通过弱引用保存,防止内存泄露。

  • 多线程时使用线程池,减少不必要的创建销毁线程的开销。

  • 字符串拼接使用StringBuilder(线程不安全)或StringBuffer(线程安全)。String在Java中是不可变的,一旦初始化就不能修改,简单的拼接其实是创建新的String对象,再把拼接后的内容赋值给结果。所以频繁的使用+号拼接,会频繁创建对象,内存消耗较高。而StringBuilder内部使用一个char数组存储2个要拼接的字符串的每一个字符,最后再把char数组转为String对象。

  • 尽量使用Application Context而不是Activity Context,以防止内存泄露。

  • 释放资源:如操作文件后要close,自定义view时使用TypedArray后进行recycle等。

电量优化

Android系统上App的电量消耗由cpu,wake lock,数据传输(移动网络&Wi-Fi),Wi-Fi运行,gps,other sensors组成

  • 网络传输优化:虽然3G芯片比Wifi芯片耗电低,但Wifi的速率可以让数据在较短时间内完成传输,从而降低电量消耗。
    • 在蜂窝移动网络下,最好做到批量执行网络请求,尽量避免频繁的间隔网络请求。
    • 尽量压缩传输数据
    • 分析和统计之类的非重要操作,可以在何时的状态(电量充足或Wi-Fi)下集中发送(可通过JobScheduler),避免频繁发送
    • 无网络状态在避免网络请求失败后的重试机制。
    • 避免不必要的长连接
  • 定位:定位是App中常用的功能,但是定位不能千篇一律,不同的场景以及不同类型的App对定位更加需要个性化的区分,多个模块使用定位,尽量复用上一次的结果,而不是都重新走定位的过程,节省电量损耗;例如:在应用启动的时候获取一次定位,保存结果,之后再用到定位的地方都直接去取。定位完成后及时注销监听。
    • GPS定位,利用GPS芯片通过卫星获得自己的位置信息。定位精准度高,一般在10米左右,耗电量大;但是在室内,GPS定位基本没用。
    • 网络定位,利用手机基站和WIFI节点的地址来大致定位位置,这种定位方式取决于服务器,即取决于将基站或WIF节点信息翻译成位置信息的服务器的能力
    • 被动定位,就是用现成的,当其他应用使用定位更新了定位信息,系统会保存下来,该应用接收到消息后直接读取就可以了。比如如果系统中已经安装了百度地图,高德地图(室内可以实现精确定位),你只要使用它们定位过后,再使用这种方法在你的程序肯定是可以拿到比较精确的定位信息。
  • Wake Lock:Android系统为了节省电量,会在用户无操作一段时间后进入休眠状态。wack lock是一种锁机制,只要有人拿着这个锁,系统就无法进入休眠状态
    • App在前台时不要申请wake lock,此时无需申请,申请的话会计算到应用电量消耗
    • App申请使用WakeLock,任务结束之后及时释放,让系统再次进入休眠状态。App在后台由于业务需要必须要申请WakeLock时使用带有超时参数的方法,防止由于忘记或者异常情况下没有释放。
    • 备注:如果只是需要屏幕常亮的话,可以使用FLAG_KEEP_SCREEN_ON,无需考虑释放WakeLock的问题。
  • 传感器使用:使用传感器,选择合适的采样率,越高的采样率类型则越费电;
  • JobScheduler:使用JobScheduler,一些任务通过JobScheduler来触发,例如可推迟的网络请求、下载、GPS等,可以在特定场景:连接Wifi、连接电源等场景触发。既完成了任务,也无需考虑由于一些任务导致的电量消耗。

安装包优化

Android的安装包Apk由以下几部分组成:

res:存放资源文件,drawable,layout等

lib:一般时存储so文件

assets:存放一些静态文件,如字体等。可以通过AssertManager访问

dex:java class被编译后可供Dalvik/ART虚拟机所理解的文件格式

META-INF:存放签名信息,用来保证APK包的完整性和系统的安全性,帮助用户避免安装来历不明的盗版APK。

resources.arsc:保存着d和资源名字的映射关系。

androidManifest:程序全局配置文件

  • res优化:

    • 移除无用文件:可用lint扫描出没有使用到的文件,也可以在Refactor选项的子选项中选择Remove Unused Resources选项清除无用的资源。也可在打包时在build.gradle中设置shrinkResources为true,每次打包的时候就会自动排除无用的资源。

    • 只保留一份Drawable资源

    • 控制raw文件大小,一般raw文件可能会放音频文件等。如非必要,尽量不要使用无损音频格式(如wav),考虑同等质量但文件更小的格式(如ogg)

    • 统一界面风格,复用layout和shape等文件,如通用的List,header等布局同步include复用

    • 图片优化:

      PNG是一种无损格式,JPG是有损格式。JPG在处理颜色很多的图片时,根据压缩率的不同,有时会去掉一些肉眼识别差距较小的中间颜色。但是PNG对于无损这个基本要求,会严格保留所有的色彩数。所以图片尺寸大,或者色彩数量多特别是渐变色的多的时候,PNG的体积会明显大于JPG。

      • 对图片进行压缩,要求不高的图片可以考虑有损压缩。(aapt默认会在打包时进行图片的压缩工作(无论你知不知道,它一直在默默的工作),如果你已经做了图片压缩了,那么建议手动禁止这个功能,否则“可能会”出现图片二次压缩后反而变大的情况)
      • 尽量使具有高清要求,色少较少,有alpha通道的图片使用png,大尺寸,色彩渐变多的使用jpg
      • 使用矢量图svg,svg在不损伤图片质量的情况下,图片非常小,而且一套适配所有分辨率。缺点是会消耗更多的cpu周期,总体性能持平。主要适合单色调的icon
      • 使用webp,webp比jpg同画质下体积更小。缺点是4.0以后才支持,4.2.1以后才支持aplha通道,也不好预览
      • 复用图片,使用.9图,服务器下发图片等
  • lib:删除不需要的so文件,一般只支持arm平台就够了。(配置abiFilters即可: abiFilters "armeabi", "armeabi-v7a" ,"x86")

  • assets:

    • 删除无用的语言,大部分应用并不需要支持多种语言,如只支持中文的话配置resConfigs "zh",这样在打包时就会排出私有项目,android系统库和第三方库中非中文的资源文件了。
    • 删除无用的字体:有些字体只在logo等少数地方使用,将整个字体包放进去会很占空间,可以把使用到的字通过工具(FontZip)提取取出来。
    • 对assets下的资源文件进行压缩(zip),使用时再解压缩
    • 动态下载需要的文件
  • dex:

    • 利用lint扫描并删除无用代码
    • 避免过度设计带来的类及方法膨胀
    • 多人合作时避免引入功能相同的多个库
    • 使用插件化等技术,将不常用的功能通过插件化动态加载
    • 减少方法数
      • 避免在内部类中访问外部类的私有方法、变量。当在Java内部类(包含匿名内部类)中访问外部类的私有方法、变量的时候,编译器会生成额外的方法,会增加方法数
      • 避免调用派生类中的未被覆写的方法,避免在派生类中调用未覆写的基类的方法;避免用派生类的对象调用派生类中未覆盖的基类的方法。调用派生类中的未被覆盖的方法时,会多产生一个方法数
      • 去掉部分类的get、set方法;当然这样会牺牲一些面向对象的观念
    • 对代码进行混淆
  • resources.arsc:这个文件保存着id和资源名字的映射关系。如果要混淆资源文件,那么同时必须修改resources.arsc中的映射名。推荐微信的AndResGuard。资源混淆能减小安装包的原因如下:

    • resources.arsc文件变小
    • 文件信息变小,采用了超短路径,res/drawable-xhdpi/icon.png被修改为R/o/f.png
    • 签名信息变小。由于采用了超短路径,签名过程对每个文件使用SHA1算法生成的摘要信息也变小
    • 如果是通过getIdentifier方式获取资源,那么这些资源需要放在白名单中

你可能感兴趣的:(Android性能优化汇总)