Android 性能优化那些事

1,681 阅读8分钟
原文链接: www.jianshu.com

在Android应用程序开发中关于性能的优化是一个永恒的话题,以下是在实际开发和学习中关于性能优化的一些见解。

1:内存管理

在Android系统上并没有为内存提供交换区,它是通过分页和内存映射的机制来管理内存,这就说明任何你修改的内存都会存在RAM中,因此唯一完整释放内存的方法是释放那些对象的引用,当这个对象没有被任何其他对象所引用的时候它就能够被GC回收。

(1):限制应用的内存

为了维持多任务的功能环境,Android为每一个app都设置了一个硬性的heap size限制。heap size的大小会因为不同设备的不同RAM大小而各有差异。如果你的app已经到了heap的限制大小并且再尝试分配内存的话,会引起OutOfMemoryError的错误。

你也许想要查询当前设备的heap size限制大小是多少,然后决定cache的大小。可以通过getMemoryClass()来查询。这个方法会返回一个整数,表明你的应用的heap size限制。

(2):切换应用

Android不会在用户切换应用时候做交换内存的操作。当用户刚开始启动了一个应用,系统会为它创建了一个进程,但是当用户离开这个应用,此进程并不会立即被销毁。系统会把这个进程放到cache中,如果用户后来再回到这个应用,此进程就能够被完整恢复,从而实现应用的快速切换。

如果你的应用中有一个被缓存的进程,这个进程会占用暂时不需要使用到的内存,会被保留在内存中,这会对系统的整体性能有影响。因此当系统开始进入低内存状态时,它会由系统根据特定的算法来kill进程。

(3):慎用Services

service用于在后台执行一些耗时操作,只用当它执行任务的时候才开启,否则其他时刻都应该不工作,service完成任务之后要主动停止,否则如果用户发现有常驻后台行为的应用并且可能卸载它甚至引起内存泄漏。

当你开启一个service,系统会倾向为了保留这个service而一直保留service所在的进程。这使得进程的运行代价很高,因为系统没有办法把service所占用的RAM空间腾出来让给其他组件。

推荐使用IntentService, 它会在工作线程处理完交代给它的intent任务之后自动停止。

(4):UI隐藏时释放内存

当应用 UI处于可见时,你应该释放你的应用UI上所占用的所有内存资源。在这个时候释放UI资源可以显著的增加系统缓存进程的能力,它会对用户体验有着很直接的影响。例如当用户从你的app的某个activity跳转到另外一个activity时前面activity的onStop()会被执行。因此你应该实现onStop回调,并且在此回调里面释放activity的资源,例如释放网络连接,注销监听广播接收者。

(5):避免盲目获取heap

可以通过在清单文件下的application标签下添加largeHeap=true的属性来声明一个更大的heap空间。如果你这样做,你可以通过getLargeMemoryClass())来获取到一个更大的heap size。

然而,能够获取更大heap的设计本意不是提供给整个应用,而是为了一小部分会消耗大量RAM的应用(例如一张图片的编辑)。不要盲目因为你需要使用大量的内存而去请求一个大的heap size。只有当你清楚的知道哪里会使用大量的内存并且为什么这些内存必须被保留时才去使用large heap。

需要说明的是通过设置large heap属性并不一定能够获取到更大的heap。在某些机器上,large heap的大小和通常的heap size是一样的。

(6):避免Bitmap浪费

加载一个bitmap时,仅仅需要保留适配当前屏幕设备分辨率的数据即可,如果原图高于你的设备分辨率,需要做缩小的动作。值得注意的是bitmap的尺寸会对内存呈现出2次方的增加。

(7):使用定制的数据容器

如SparseArray, SparseBooleanArray, 与 LongSparseArray。 通常的HashMap的实现方式更加消耗内存。SparseArray避免了key与value的自动装箱和拆箱。

(8):警惕内存开销

枚举类型的内存消耗通常是静态变量的2倍。你应该尽量避免在Android上使用枚举。

在Java中的每一个类(包括匿名内部类)都会使用大概500 bytes。

每一个类的实例开销大约是12-16 bytes。

向HashMap添加一个entry需要额一个额外占用的32 bytes的entry对象。

(9):谨慎使用第三方类库

如果一个类库与你的实际需求吻合度较小,你应该考虑自己去实现,而不是导入一个大而全的解决方案,因为它里面包含的各种资源也是对内存的消耗。

2:代码优化

高效的代码需要满足下面两个原则:

1.)不要做冗余的工作

2.)尽量避免过多的内存分配

(1):避免创建不必要的对象

避免创建更多的临时对象,更少的对象意味者更少的gc动作,即使gc支持并发操作但还是会对用户体验有比较直观的影响。

(2):选择使用Static

如果你不需要访问一个对象的值,请保证这个方法是static类型的,这样方法调用将快15%-20%。

(3):避免Getter和Setter方法

Getters和Setters方法在面向对象程序设计上是一个良好的习惯,但在Android上这不是一个好的写法,因为虚函数的调用比起直接访问变量要耗费更多。

(4):使用for-each 循环

for-each 循环可以被用在实现了 Iterable 接口的 collections 以及数组上,增强的for-each循环写法会和迭代器写法的效率一样,且更加清晰直观。

(5):避免使用float类型

Android系统中float类型的数据存取速度是int类型的一半,尽量优先采用int类型。

float 和 double 的速度是一样的,同样条件下使用 double 。

3:布局文件性能

Layout 是 Android 应用中直接影响用户体验的关键部分。如果实现的不好,你的 Layout 会导致程序非常占用内存并且 UI 运行缓慢。

(1):避免过多嵌套

 Layout转化为视图需要经过初始化、布局和绘制的过程,如果布局嵌套导致层级过深,上面的初始化,布局和绘制操作就更加耗时。

(2):使用include标签重用layouts

为了高效重用整个的 Layout,你可以使用include标签把其他 Layout 嵌入当前 Layout,比如可以把自定义的toolbar应用于每个需要的页面。

(3):ListView优化

ListView 滑动绘制Item时经常使用 findViewById(),这样会降低性能。即使是 Adapter 返回一个用于回收的 inflate 后的视图,你仍然需要查看这个元素并更新它。避免频繁调用 findViewById() 的y有效方法之一就是使用 ViewHolder。

4:多线程

(1):使用线程池

如果你只需要一个任务执行一次使用一个IntentService就可以。但是Android中的网络操作是相当频繁的,你需要提供一个管理线程的集合。 为了做这个管理线程的集合,使用一个ThreadPoolExecutor实例。

一个线程池能运行多个并行的任务实例,因此你要能保证你的代码是线程安全的,从而你需要给会被多个线程访问的变量加上同步代码块(synchronized block)。

(2):与UI线程通信

1.)Handler属于Android系统的线程管理框架的一部分,用于线程之间的通信。

2.)Rxjava能够很好的实现线程之间的相互切换。

5:ANR

ANR指应用程序无响应,发生原因通常是应用程序在UI线程执行耗时操作。比如:

Activity内对输入事件,5秒内都无响应。

BroadReceiver不能够在10秒内结束接收到任务。

(1):Android程序通常是执行在默认的UI线程中的。这意味着在UI线程中执行的任何长时间的操作都可能触发ANR,因此,任何执行在UI线程的方法都应该尽可能的快速。特别是,在activity的生命周期的方法onCreate()与onResume()方法中应该尽可能的做比较少的事情。类似网络或数据库操作等可能长时间执行的操作,都应该执行在工作线程中。

(2):BroadcastReceiver的超时时间是10秒,应用程序应该避免在BroadcastReceiver中执行费时的任务。如果需要的话可以启动一个IntentService来响应BroadcastReceiver的长时间任务。