Android性能优化

以下内容整理自互联网,仅用于个人学习


1. 合理管理内存

1.1 节制的使用Service

如果应用程序需要使用Service来执行后台任务的话,只有当任务正在执行的时候才应该让Service运行起来。当启动一个Service时,系统会倾向于将这个Service所依赖的进程进行保留,系统可以在LRUcache当中缓存的进程数量也会减少,导致切换程序的时候耗费更多性能。我们可以使用IntentService,当后台任务执行结束后会自动停止,避免了Service的内存泄漏。

1.2 当视图变为隐藏状态后释放内存

当用户跳转到不同的应用并且你的视图不再显示时,你应该释放应用视图所占的资源。 这时释放所占用的资源能显著的提高系统的缓存处理容量,并且对用户的体验质量有直接的影响。

重写Activity的onTrimMemory()方法,然后在这个方法中监听TRIM_MEMORY_UI_HIDDEN这个级别,一旦触发说明用户离开了程序,此时就可以进行资源释放操作了。

注意只有当你应用的所有视图元素变为隐藏状态时你的应用才能收到onTrimMemory()回调方法的TRIM_MEMORY_UI_HIDDEN。这个和onStop()回调方法不同,该方法只有当Activity的实例变为隐藏状态,或者有用户移动到应用中的另外的activity才会引发。所以说你虽然实现了onStop()去释放activity的资源例如网络连接或者未注册的广播接收者,但是应该直到你收到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)才去释放视图资源,否则不应该释放视图所占用的资源。这里可以确定的是如果用户通过后退键从另外的activity进入到你的应用中,视图资源会一直处于可用的状态可以用来快速的恢复activity。

1.3 当内容紧张时释放内存

onTrimMemory()方法还有很多种其他类型的回调,可以在手机内存降低的时候及时通知我们,我们应该根据回调中传入的级别来去决定如何释放应用程序的资源。

//应用处于运行状态并且不会被杀掉, 设备使用的内存比较低,  
//系统级会杀掉一些其它的缓存应用. 
TRIM_MEMORY_RUNNING_CRITICAL 
 
//应用处于运行状态并且不会被杀掉, 设备可以使用的内存非常低,  
//可以把不用的资源释放一些提高性能(会直接影响程序的性能) 
 
TRIM_MEMORY_RUNNING_LOW 
 
//应用处于运行状态但是系统已经把大多数缓存应用杀掉了,  
//你必须释放掉不是非常关键的资源, 如果系统不能回收足够的运行内存,  
//系统会清除所有缓存应用并且会把正在活动的应用杀掉. 
TRIM_MEMORY_RUNNING_CRITICAL 
 
//还有, 当你的应用被系统正缓存时,  
//通过 onTrimMemory() 回调方法可以收到以下几个内存级别: 
TRIM_MEMORY_BACKGROUND 
 
//系统处于低内存的运行状态中并且你的应用处于缓存应用列表的初级阶段. 
//虽然你的应用不会处于被杀的高风险中, 但是系统已经开始清除缓存列表中的其它应用, 
// 所以你必须释放资源使你的应用继续存留在列表中以便用户再次回到你的应用时能快速恢复进行使用. 
TRIM_MEMORY_MODERATE 
 
//系统处于低内存的运行状态中并且你的应用处于缓存应用列表的中级阶段. 
// 如果系运行内存收到限制, 你的应用有被杀掉的风险. 
TRIM_MEMORY_COMPLETE

系统处于低内存的运行状态中如果系统现在没有内存回收你的应用将会第一个被杀掉,你必须释放掉所有非关键的资源从而恢复应用的状态。

当系统开始清除缓存应用列表中的应用时,虽然系统的主要工作机制是自下而上,但是也会通过杀掉消费大内存的应用从而使系统获得更多的内存,所以在缓存应用列表中消耗更少的内存将会有更大的机会留存下来以便用户再次使用时进行快速恢复。

1.4 检查可以使用多大的内存

前面提到,不同的android设备系统拥有的运行内存各自都不同,从而不同的应用堆内存的限制大小也不一样。你可以通过调用ActivityManager中的getMemoryClass()函数可以通过以兆为单位获取当前应用可用的内存大小, 如果你想获取超过最大限度的内存则会发生OutOfMemoryError。

有一个特别的情况,可以在manifest文件中的标签中设置largeHeap属性的值为true时,当前应用就可以获取到系统分配的最大堆内存。如果你设置了该值,可以通过ActivityManager的getLargeMemoryClass()函数获取最大的堆内存。

然后,,只有一小部分应用需要消耗大量堆内存(比如大照片编辑应用).。从来不需要使用大量内存仅仅是因为你已经消耗了大量的内存并且必须快速修复它,你必须使用它是因为你恰好知道所有的内存已经被分配完了而你必须要保留当前应用不会被清除掉。甚至当你的应用需要消耗大量内存时,你应该尽可能的避免这种需求。使用大量内存后,当你切换不同的应用或者执行其它类似的操作时,因为长时间的内存回收会导致系统的性能下降从而渐渐的会损害整个系统的用户体验。

另外,大内存不是所有的设备都相同。当跑在有运行内存限制的设备上时,大内存和正常的堆内存是一样的。所以如果你需要大内存,你就要调用getMemoryClass()函数查看正常的堆内存的大小并且尽可能使内存使用情况维护在正常堆内存之下。

1.5 避免在Bitmap上浪费内存

读取一个Bitmap图片的时候,不要去加载不需要的分辨率,可以进行压缩图片等操作。

1.6 使用优化后的数据集合

利用Android框架优化后的数据容器,比如SparseArray,SparseBooleanArray和LongSparseArray。传统的HashMap
在内存上的实现十分的低效,因为它需要为map中每一项在内存中建立映射关系。另外,SparseArray类非常高效因为它避免系统中需要自动封箱(autobox)的key和有些值。

注意:只是内存上进行优化,执行效率并没有优化。

1.7 知晓内存的开支情况

在你设计应用各个阶段都要很谨慎的考虑所使用的语言和库带来的内存上的成本和开销.通常情况下,表面上看起来无害的会带来巨大的开销,下面在例子说明:

  • 当枚举(enum)成为静态常量时超过正常两倍以上的内存开销,在android中你需要严格避免使用枚举
  • java中的每个类(包含匿名内部类)大约使用500个字节
  • 每个类实例在运行内存(RAM)中占用12到16个字节
  • 在hashmap中放入单项数据时, 需要为额外的其它项分配内存, 总共占用32个字节

使用很多的不必要类和对象时,增加了分析堆内存问题的复杂度。

1.8 谨慎使用抽象编程

通常来说,使用简单的抽象是一种好的编程习惯,因为一定程度上的抽象可以提供代码的伸缩性和可维护性。

然而抽象会带来非常显著的开销:需要执行更多的代码,需要更长时间和更多的运行内存把代码映射到内存中,所以如果抽象没有带来显著的效果就尽量避免。

1.9 尽量避免使用依赖注入框架

使用依赖注入框架貌似看上去把findViewById()这一类的繁琐操作去掉了,但是这些框架为了要搜寻代码中的注解,通常都需要经历较长的初始化过程,并且将一些你用不到的对象也一并加载到内存中。这些用不到的对象会一直站用着内存空间,可能很久之后才会得到释放,所以可能多敲几行代码是更好的选择。

1.10 使用多个进程

谨慎使用,多数应用程序不该在多个进程中运行的,一旦使用不当,它甚至会增加额外的内存而不是帮我们节省内存。这个技巧比较适用于哪些需要在后台去完成一项独立的任务,和前台是完全可以区分开的场景。比如音乐播放,关闭软件,已经完全由Service来控制音乐播放了,系统仍然会将许多UI方面的内存进行保留。在这种场景下就非常适合使用两个进程,一个用于UI展示,另一个用于在后台持续的播放音乐。关于实现多进程,只需要在Manifast文件的应用程序组件声明一个android:process属性就可以了。进程名可以自定义,但是之前要加个冒号,表示该进程是一个当前应用程序的私有进程。

2. 高性能编码优化

都是一些微优化,在性能方面看不出有什么显著的提升的。使用合适的算法和数据结构是优化程序性能的最主要手段。

2.1 避免创建不必要的对象

  • 如果有需要拼接的字符串,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低。
  • 在没有特殊原因的情况下,尽量使用基本数据类型来代替封装数据类型,int比Integer要更加有效,其它数据类型也是一样。

2.2 静态优于抽象

如果你并不需要访问一个对系那个对象中的某些字段,只是想调用它的某些方法来去完成一项通用的功能,那么可以将这个方法设置成静态方法,调用速度提升15%-20%,同时也不用为了调用这个方法去专门创建对象了,也不用担心调用这个方法后是否会改变对象的状态(静态方法无法访问非静态字段)。

2.3 对常量使用static final修饰符

final修饰之后,定义类不需要生成初始方法,因为所有的常量都在dex文件的初始化器种进行初始化。

2.4 多使用系统封装好的API

系统提供不了的Api完成不了我们需要的功能才应该自己去写,因为使用系统的Api很多时候比我们自己写的代码要快得多,它们的很多功能都是通过底层的汇编模式执行的。 举个例子,实现数组拷贝的功能,使用循环的方式来对数组中的每一个元素一一进行赋值当然可行,但是直接使用系统中提供的System.arraycopy()方法会让执行效率快9倍以上。

2.5 避免在内部调用Getters/Setters方法

面向对象中封装的思想是不要把类内部的字段暴露给外部,而是提供特定的方法来允许外部操作相应类的内部字段。但在Android中,字段搜寻比方法调用效率高得多,我们直接访问某个字段可能要比通过getters方法来去访问这个字段快3到7倍。但是编写代码还是要按照面向对象思维的,我们应该在能优化的地方进行优化,比如避免在内部调用getters/setters方法。

3. 布局优化技巧

3.1 重用布局文件

标签可以允许在一个布局当中引入另一个布局,那么比如说我们程序的所有界面都有一个公共的部分,这个时候最好的做法就是将这个公共的部分提取到一个独立的布局中,然后每个界面的布局文件当中来引用这个公共的布局。

标签是作为一种辅助扩展来使用的,它的主要作用是为了防止在引用布局文件时引用文件时产生多余的布局嵌套。布局嵌套越多,解析起来就越耗时,性能就越差。因此编写布局文件时应该让嵌套的层数越少越好。

3.2 仅在需要时才加载布局

某个布局当中的元素不是一起显示出来的,普通情况下只显示部分常用的元素,而那些不常用的元素只有在用户进行特定操作时才会显示出来。

可以使用ViewStub,ViewStub就是i一个宽高都为0的一个view,它默认不可见,只有通过调用setVisibility函数或者Inflate才会加载布局。

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