Google官方Android性能优化典范第3季

   2016-09-08 0
核心提示:原文转自:http://www.cnblogs.com/yezhennan/p/5443580.html (1)Fun with ArrayMaps 程序内存的管理是否合理高效对应用的性能有着很大的影响,有的时候对容器的使用不当也会导致内存管理效率低下。Android为移动操作系统特意编写了一些更加高效的容器,例如

原文转自:http://www.cnblogs.com/yezhennan/p/5443580.html

(1)Fun with ArrayMaps

程序内存的管理是否合理高效对应用的性能有着很大的影响,有的时候对容器的使用不当也会导致内存管理效率低下。Android为移动操作系统特意编写了一些更加高效的容器,例如SparseArray,今天要介绍的是一个新的容器,叫做 ArrayMap

我们经常会使用到HashMap这个容器,它非常好用,但是却很占用内存。下图演示了HashMap的简要工作原理:

Google官方Android性能优化典范第3季

为了解决HashMap更占内存的弊端,Android提供了内存效率更高的 ArrayMap 。它内部使用两个数组进行工作,其中一个数组记录key hash过后的顺序列表,另外一个数组按key的顺序记录Key-Value值,如下图所示:

Google官方Android性能优化典范第3季

当你想获取某个value的时候,ArrayMap会计算输入key转换过后的hash值,然后对hash数组使用二分查找法寻找到对应的index,然后我们可以通过这个index在另外一个数组中直接访问到需要的键值对。如果在第二个数组键值对中的key和前面输入的查询key不一致,那么就认为是发生了碰撞冲突。为了解决这个问题,我们会以该key为中心点,分别上下展开,逐个去对比查找,直到找到匹配的值。如下图所示:

Google官方Android性能优化典范第3季

随着数组中的对象越来越多,查找访问单个对象的花费也会跟着增长,这是在内存占用与访问时间之间做权衡交换。

既然ArrayMap中的内存占用是连续不间断的,那么它是如何处理插入与删除操作的呢?请看下图所示,演示了Array的特性:

Google官方Android性能优化典范第3季

Google官方Android性能优化典范第3季

很明显,ArrayMap的插入与删除的效率是不够高的,但是如果数组的列表只是在一百这个数量级上,则完全不用担心这些插入与删除的效率问题。HashMap与ArrayMap之间的内存占用效率对比图如下:

Google官方Android性能优化典范第3季

与HashMap相比,ArrayMap在循环遍历的时候也更加简单高效,如下图所示:

Google官方Android性能优化典范第3季

前面演示了很多ArrayMap的优点,但并不是所有情况下都适合使用ArrayMap,我们应该在满足下面2个条件的时候才考虑使用ArrayMap:

  • 对象个数的数量级最好是千以内
  • 数据组织形式包含Map结构

我们需要学会在特定情形下选择相对更加高效的实现方式。

(2)Beware Autoboxing

有时候性能问题也可能是因为那些不起眼的小细节引起的,例如在代码中不经意的“自动装箱”。我们知道基础数据类型的大小:boolean(8 bits), int(32 bits), float(32 bits),long(64 bits),为了能够让这些基础数据类型在大多数Java容器中运作,会需要做一个autoboxing的操作,转换成Boolean,Integer,Float等对象,如下演示了循环操作的时候是否发生autoboxing行为的差异:

Google官方Android性能优化典范第3季

Google官方Android性能优化典范第3季

Autoboxing的行为还经常发生在类似HashMap这样的容器里面,对HashMap的增删改查操作都会发生了大量的autoboxing的行为。

Google官方Android性能优化典范第3季

为了避免这些autoboxing带来的效率问题,Android特地提供了一些如下的Map容器用来替代HashMap,不仅避免了autoboxing,还减少了内存占用:

Google官方Android性能优化典范第3季

(3)SparseArray Family Ties

为了避免HashMap的autoboxing行为,Android系统提供了SparseBoolMap,SparseIntMap,SparseLongMap,LongSparseMap等容器。关于这些容器的基本原理请参考前面的ArrayMap的介绍,另外这些容器的使用场景也和ArrayMap一致,需要满足数量级在千以内,数据组织形式需要包含Map结构。

(4)The price of ENUMs

在StackOverFlow等问答社区常常出现关于在Android系统里面使用枚举类型的性能讨论,关于这一点,Android官方的Training课程里面有下面这样一句话:

Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.

Google官方Android性能优化典范第3季

关于enum的效率,请看下面的讨论。假设我们有这样一份代码,编译之后的dex大小是2556 bytes,在此基础之上,添加一些如下代码,这些代码使用普通static常量相关作为判断值:

Google官方Android性能优化典范第3季

增加上面那段代码之后,编译成dex的大小是2680 bytes,相比起之前的2556 bytes只增加124 bytes。假如换做使用enum,情况如下:

Google官方Android性能优化典范第3季

使用enum之后的dex大小是4188 bytes,相比起2556增加了1632 bytes,增长量是使用static int的13倍。不仅仅如此,使用enum,运行时还会产生额外的内存占用,如下图所示:

Google官方Android性能优化典范第3季

Android官方强烈建议不要在Android程序里面使用到enum。

(5)Trimming and Sharing Memory

Android系统的一大特色是多任务,用户可以随意在不同的app之间进行快速切换。为了确保你的应用在这种复杂的多任务环境中正常运行,我们需要了解下面的知识。

为了让background的应用能够迅速的切换到forground,每一个background的应用都会占用一定的内存。Android系统会根据当前的系统内存使用情况,决定回收部分background的应用内存。如果background的应用从暂停状态直接被恢复到forground,能够获得较快的恢复体验,如果background应用是从Kill的状态进行恢复,就会显得稍微有点慢。

Google官方Android性能优化典范第3季

Android系统提供了一些回调来通知应用的内存使用情况,通常来说,当所有的background应用都被kill掉的时候,forground应用会收到 onLowMemory() 的回调。在这种情况下,需要尽快释放当前应用的非必须内存资源,从而确保系统能够稳定继续运行。Android系统还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递以下的参数,代表不同的内存使用情况,下图介绍了各种不同的回调参数:

Google官方Android性能优化典范第3季

关于每个参数的更多介绍,请参考这里 http://hukai.me/android-training-managing_your_app_memory/ ,另外onTrimMemory()的回调可以发生在Application,Activity,Fragment,Service,Content Provider。

从Android 4.4开始,ActivityManager提供了 isLowRamDevice() 的API,通常指的是Heap Size低于512M或者屏幕大小<=800*480的设备。

(6)DO NOT LEAK VIEWS

内存泄漏的概念,下面一张图演示下:

Google官方Android性能优化典范第3季

通常来说,View会保持Activity的引用,Activity同时还和其他内部对象也有可能保持引用关系。当屏幕发生旋转的时候,activity很容易发生泄漏,这样的话,里面的view也会发生泄漏。Activity以及view的泄漏是非常严重的,为了避免出现泄漏,请特别留意以下的规则:

(6.1)避免使用异步回调

异步回调被执行的时间不确定,很有可能发生在activity已经被销毁之后,这不仅仅很容易引起crash,还很容易发生内存泄露。

Google官方Android性能优化典范第3季

(6.2)避免使用Static对象

因为static的生命周期过长,使用不当很可能导致leak,在Android中应该尽量避免使用static对象。

Google官方Android性能优化典范第3季

(6.3)避免把View添加到没有清除机制的容器里面

假如把view添加到 WeakHashMap ,如果没有执行清除操作,很可能会导致泄漏。

Google官方Android性能优化典范第3季

(7)Location & Battery Drain

开启定位功能是一个相对来说比较耗电的操作,通常来说,我们会使用类似下面这样的代码来发出定位请求:

Google官方Android性能优化典范第3季

上面演示中有一个方法是 setInterval() 指的意思是每隔多长的时间获取一次位置更新,时间相隔越短,自然花费的电量就越多,但是时间相隔太长,又无法及时获取到更新的位置信息。其中存在的一个优化点是,我们可以通过判断返回的位置信息是否相同,从而决定设置下次的更新间隔是否增加一倍,通过这种方式可以减少电量的消耗,如下图所示:

Google官方Android性能优化典范第3季

在位置请求的演示代码中还有一个方法是 setFastestInterval() ,因为整个系统中很可能存在其他的应用也在请求位置更新,那些应用很有可能设置的更新间隔时间很短,这种情况下,我们就可以通过setFestestInterval的方法来过滤那些过于频繁的更新。

通过GPS定位服务相比起使用网络进行定位更加的耗电,但是也相对更加精准一些,他们的图示关系如下:

Google官方Android性能优化典范第3季

为了提供不同精度的定位需求,同时屏蔽实现位置请求的细节,Android提供了下面4种不同精度与耗电量的参数给应用进行设置调用,应用只需要决定在适当的场景下使用对应的参数就好了,通过LocationRequest.setPriority()方法传递下面的参数就好了。

Google官方Android性能优化典范第3季

(8)Double Layout Taxation

布局中的任何一个View一旦发生一些属性变化,都可能引起很大的连锁反应。例如某个button的大小突然增加一倍,有可能会导致兄弟视图的位置变化,也有可能导致父视图的大小发生改变。当大量的layout()操作被频繁调用执行的时候,就很可能引起丢帧的现象。

Google官方Android性能优化典范第3季

例如,在RelativeLayout中,我们通常会定义一些类似alignTop,alignBelow等等属性,如图所示:

Google官方Android性能优化典范第3季

为了获得视图的准确位置,需要经过下面几个阶段。首先子视图会触发计算自身位置的操作,然后RelativeLayout使用前面计算出来的位置信息做边界的调整的操作,如下面两张图所示:

Google官方Android性能优化典范第3季

Google官方Android性能优化典范第3季

经历过上面2个步骤,relativeLayout会立即触发第二次layout()的操作来确定所有子视图的最终位置与大小信息。

除了RelativeLayout会发生两次layout操作之外,LinearLayout也有可能触发两次layout操作,通常情况下LinearLayout只会发生一次layout操作,可是一旦调用了measureWithLargetChild()方法就会导致触发两次layout的操作。另外,通常来说,GridLayout会自动预处理子视图的关系来避免两次layout,可是如果GridLayout里面的某些子视图使用了weight等复杂的属性,还是会导致重复的layout操作。

如果只是少量的重复layout本身并不会引起严重的性能问题,但是如果它们发生在布局的根节点,或者是ListView里面的某个ListItem,这样就会引起比较严重的性能问题。如下图所示:

Google官方Android性能优化典范第3季

我们可以使用Systrace来跟踪特定的某段操作,如果发现了疑似丢帧的现象,可能就是因为重复layout引起的。通常我们无法避免重复layout,在这种情况下,我们应该尽量保持View Hierarchy的层级比较浅,这样即使发生重复layout,也不会因为布局的层级比较深而增大了重复layout的倍数。另外还有一点需要特别注意,在任何时候都请避免调用 requestLayout() 的方法,因为一旦调用了requestLayout,会导致该layout的所有父节点都发生重新layout的操作。

Google官方Android性能优化典范第3季

(9)Network Performance 101

在性能优化第一季与第二季的课程里面都介绍过,网络请求的操作是非常耗电的,其中在移动蜂窝网络情况下执行网络数据的请求则尤其比较耗电。关于如何减少移动网络下的网络请求的耗电量,有两个重要的原则需要遵守:第一个是减少移动网络被激活的时间与次数,第二个是压缩传输数据。

(9.1)减少移动网络被激活的时间与次数

通常来说,发生网络行为可以划分为如下图所示的三种类型,一个是用户主动触发的请求,另外被动接收服务器的返回数据,最后一个是数据上报,行为上报,位置更新等等自定义的后台操作。

Google官方Android性能优化典范第3季

我们绝对坚决肯定不应该使用Polling(轮询)的方式去执行网络请求,这样不仅仅会造成严重的电量消耗,还会浪费许多网络流量,例如:

Google官方Android性能优化典范第3季

Android官方推荐使用 Google Cloud Messaging (在大陆,然并卵),这个框架会帮助把更新的数据推送给手机客户端,效率极高!我们应该遵循下面的规则来处理数据同步的问题:

首先,我们应该使用回退机制来避免固定频繁的同步请求,例如,在发现返回数据相同的情况下,推迟下次的请求时间,如下图所示:

Google官方Android性能优化典范第3季

其次,我们还可以使用 Batching (批处理)的方式来集中发出请求,避免频繁的间隔请求,如下图所示:

Google官方Android性能优化典范第3季

最后,我们还可以使用 Prefetching (预取)的技术提前把一些数据拿到,避免后面频繁再次发起网络请求,如下图所示:

Google官方Android性能优化典范第3季

Google Play Service中提供了一个叫做 GCMNetworkManager 的类来帮助我们实现上面的那些功能,我们只需要调用对应的API,设置一些简单的参数,其余的工作就都交给Google来帮我们实现了。

Google官方Android性能优化典范第3季

(9.2)压缩传输数据

关于压缩传输数据,我们可以学习以下的一些课程(真的够喝好几壶了):

(10)Effective Network Batching

在性能优化课程的第一季与第二季里面,我们都有提到过下面这样一个网络请求与电量消耗的示意图:

Google官方Android性能优化典范第3季

发起网络请求与接收返回数据都是比较耗电的,在网络硬件模块被激活之后,会继续保持几十秒的电量消耗,直到没有新的网络操作行为之后,才会进入休眠状态。前面一个段落介绍了使用Batching的技术来捆绑网络请求,从而达到减少网络请求的频率。那么如何实现Batching技术呢?通常来说,我们可以会把那些发出的网络请求,先暂存到一个PendingQueue里面,等到条件合适的时候再触发Queue里面的网络请求。

Google官方Android性能优化典范第3季

可是什么时候才算是条件合适了呢?最简单粗暴的,例如我们可以在Queue大小到10的时候触发任务,也可以是当手机开始充电,或者是手机连接到WiFi等情况下才触发队列中的任务。手动编写代码去实现这些功能会比较复杂繁琐,Google为了解决这个问题,为我们提供了GCMNetworkManager来帮助实现那些功能,仅仅只需要调用API,设置触发条件,然后就OK了。

(11)Optimizing Network Request Frequencies

前面的段落已经提到了应该减少网络请求的频率,这是为了减少电量的消耗。我们可以使用Batching,Prefetching的技术来避免频繁的网络请求。Google提供了GCMNetworkManager来帮助开发者实现那些功能,通过提供的API,我们可以选择在接入WiFi,开始充电,等待移动网络被激活等条件下再次激活网络请求。

(12)Effective Prefetching

假设我们有这样的一个场景,最开始网络请求了一张图片,隔了10秒需要请求另外一张图片,再隔6秒会请求第三张图片,如下图所示:

Google官方Android性能优化典范第3季

类似上面的情况会频繁触发网络请求,但是如果我们能够预先请求后续可能会使用到网络资源,避免频繁的触发网络请求,这样就能够显著的减少电量的消耗。可是预先获取多少数据量是很值得考量的,因为如果预取数据量偏少,就起不到减少频繁请求的作用,可是如果预取数据过多,就会造成资源的浪费。

Google官方Android性能优化典范第3季

我们可以参考在WiFi,4G,3G等不同的网络下设计不同大小的预取数据量,也可以是按照图片数量或者操作时间来作为阀值。这需要我们需要根据特定的场景,不同的网络情况设计合适的方案。

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • Android开发艺术探索学习笔记(三)—Android性能优化之Bitmap导致的内存溢出
    Android开发艺术探索学习笔记(三)—Android性能
    原本计划是按照章节顺序学习《Android开发艺术探索》这本书的,Android性能优化这部分也是本书的最后一章。但是周末的时候,友盟线下反馈的公司项目的一个错误让我不得不提前学习这一块的知识。先看看线下反馈的错误吧:java.lang.OutOfMemoryError:应用程序
  • Android 性能优化之内存泄漏分析工具 LeakCanary
    Android 性能优化之内存泄漏分析工具 LeakCanar
    前言Android内存泄漏的分析与解决是每个Android程序员进阶路上的必备技能。今天就和大家分享一下我的一点点学习心得。开始使用首先在module的build.gradle添加依赖,不同的版本需要添加不同的依赖dependencies {compile fileTree(dir: 'libs', include: ['*.j
  • 携程移动端 UI 界面性能优化实践
    携程移动端 UI 界面性能优化实践
    人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯,其帧率通常为 24fps;那么,用手机当然也需要感知屏幕操作的连贯性(尤其是动画过渡),所以在手机领域 Android/iOS 索性就把达到这种流畅的帧率规定为 60fps。基
  • Android 性能优化:多线程系列开篇
    Android 性能优化:多线程系列开篇
    前言 Android Performance Patterns Season 5 主要介绍了 Android 多线程环境下的性能问题。通过介绍 Android 提供的多种多线程工具类 (AsyncTask, HandlerThread, IntentService, ThreadPool),让我们熟悉各个组件的适用场景,从而在特定场景下选择性能最好
  • Android性能优化-App后台优化
    原文链接Background Optimizations前言后台进程是内存和电池敏感的。一个隐式的broadcast可能会启动很多监听它的后台进程,即使这些进程可能做得工作不多。这可能丢设备性能和用户体验都有比较大的影响。为了缓解这种问题,7.0(API 24)做了以下限制:Target
  • Android应用性能优化系列视图篇——ListView自适应导致的严重性能问题
    Android应用性能优化系列视图篇——ListView自
    ListView是Android中最常用的视图之一,使用的频率仅仅次于三大基础布局,虽然由于使用性和扩展性等原因备受争议,且尽管后来出现了RecyclerView的替代方案,但是ListView仍然广泛地使用在我们的项目中。自从ListView出道至今,已经不知道衍生出了多少问题,
  • Android性能优化-减小APK大小
    Android性能优化-减小APK大小
    原文链接: Reduce APK Size 前言用户通常会避免下载比较大的应用,特别是连接到2G和3G网络,或者按流量收费的设备。这篇文章描述了如何减小apk的大小,帮助你让更多的用户下载你的app。一 理解APK的结构在讨论如何减小apk大小之前,理解apk的结构很有必要。
  • Android性能优化-内存优化
    前言在任何软件开发环境中,RAM都是比较珍贵的资源。在移动操作系统上更是这样,因为它们的物理内存通常受限。尽管在ART和Dalvik虚拟机都会进行垃圾回收的巡航,但这并不意味着你可以忽略何时,何地分配和释放内存。你应该避免内存泄露,通常此后又一些静态成
  • Android性能优化之UI渲染优化
    Android性能优化之UI渲染优化
    原文转自:http://www.cnblogs.com/yezhennan/p/5442031.htmlUI性能测试性能优化都需要有一个目标,UI的性能优化也是一样。你可能会觉得“我的app加载很快”很重要,但我们还需要了解终端用户的期望,是否可以去量化这些期望呢?我们可以从人机交互心理学的角
  • Google官方Android性能优化典范第1季
    Google官方Android性能优化典范第1季
    原文转自:http://www.cnblogs.com/yezhennan/p/5431738.html大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。从设计师的角度,他们希望App能够有更多的动画,图片等时尚元素来实现流畅的用户体验。但是Android系统很有可能无法及时完成那些
点击排行