Android,合理管理内存

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/42238627 android

有很多朋友都问过我,怎样才能写出高性能的应用程序,如何避免程序出现OOM,或者当程序内存占用太高的时候该怎么样去排查。确实,一个优秀的应用程序,不只仅要功能完成得好,性能问题也应该处理得恰到好处。为此,我也是阅读了很多Android官方给出的高性能编程建议,那么从本篇文章开始,我就准备开始写一个全新系列的博文,来把这些建议进行整理和分析,帮助你们可以写出更加出色的应用程序。 程序员

注意本系列文章的内容基本源于Android Doc,若是想要阅读更加详细的关于性能方面的资料,能够直接去阅读Android官方文档。 编程

内存(RAM)对于任何一个软件开发环境都是种很是珍贵的资源,而对于移动操做系统来说的话,则会显得更加珍贵,由于手机的硬件条件相对于PC毕竟是比较落后的。尽管Android系统的虚拟机拥有自动回收垃圾的机制,但这并不表明咱们就能够忽视应该在何时分配和释放内存。 缓存

为了使垃圾回收器能够正常释放程序所占用的内存,在编写代码的时候就必定要注意尽可能避免出现内存泄漏的状况(一般都是因为全局成员变量持有对象引用所致使的),而且在适当的时候去释放对象引用。对于大多数的应用程序而言,后面其它的事情就能够都交给垃圾回收器去完成了,若是一个对象的引用再也不被其它对象所持有,那么系统就会将这个对象所分配的内存进行回收。 网络

咱们在开发软件的时候应当自始至终都把内存的问题充分考虑进去,这样的话才能开发出更加高性能的软件。而内存问题也并非无规律可行的,Android系统给咱们提出了不少内存优化的建议技巧,只要按照这些技巧来编写程序,就可让咱们的程序在内存性能发面表现得至关不错,下面咱们就来一一学习一下这些技巧。 app

节制地使用Service

若是应用程序当中须要使用Service来执行后台任务的话,请必定要注意只有当任务正在执行的时候才应该让Service运行起来。另外,当任务执行完以后去中止Service的时候,要当心Service中止失败致使内存泄漏的状况。 框架

当咱们启动一个Service时,系统会倾向于将这个Service所依赖的进程进行保留,这样就会致使这个进程变得很是消耗内存。而且,系统能够在LRU cache当中缓存的进程数量也会减小,致使切换应用程序的时候耗费更多性能。严重的话,甚至有可能会致使崩溃,由于系统在内存很是吃紧的时候可能已没法维护全部正在运行的Service所依赖的进程了。 ide

为了可以控制Service的生命周期,Android官方推荐的最佳解决方案就是使用IntentService,这种Service的最大特色就是当后台任务执行结束后会自动中止,从而极大程度上避免了Service内存泄漏的可能性。关于IntentService更加详细的用法讲解,能够参考《第一行代码——Android》的9.5.2节。 工具

让一个Service在后台一直保持运行,即便它并不执行任何工做,这是编写Android程序时最糟糕的作法之一。因此Android官方极度建议开发人员们不要过于贪婪,让Service在后台一直运行,这不只可能会致使手机和程序的性能很是低下,并且被用户发现了以后也有可能直接致使咱们的软件被卸载(我我的就会这么作)。

当界面不可见时释放内存

当用户打开了另一个程序,咱们的程序界面已经再也不可见的时候,咱们应当将全部和界面相关的资源进行释放。在这种场景下释放资源可让系统缓存后台进程的能力显著增长,所以也会让用户体验变得更好。

那么咱们如何才能知道程序界面是否是已经不可见了呢?其实很简单,只须要在Activity中重写onTrimMemory()方法,而后在这个方法中监听TRIM_MEMORY_UI_HIDDEN这个级别,一旦触发了以后就说明用户已经离开了咱们的程序,那么此时就能够进行资源释放操做了,以下所示:

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
  1. @Override  
  2. public void onTrimMemory(int level) {  
  3.     super.onTrimMemory(level);  
  4.     switch (level) {  
  5.     case TRIM_MEMORY_UI_HIDDEN:  
  6.         // 进行资源释放操做  
  7.         break;  
  8.     }  
  9. }  
注意onTrimMemory()方法中的TRIM_MEMORY_UI_HIDDEN回调只有当咱们程序中的全部UI组件所有不可见的时候才会触发,这和onStop()方法仍是有很大区别的,由于onStop()方法只是当一个Activity彻底不可见的时候就会调用,好比说用户打开了咱们程序中的另外一个Activity。所以,咱们能够在onStop()方法中去释放一些Activity相关的资源,好比说取消网络链接或者注销广播接收器等,可是像UI相关的资源应该一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)这个回调以后才去释放,这样能够保证若是用户只是从咱们程序的一个Activity回到了另一个Activity,界面相关的资源都不须要从新加载,从而提高响应速度。

当内存紧张时释放内存

除了刚才讲的TRIM_MEMORY_UI_HIDDEN这个回调,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缓存列表的最边缘位置,系统会最优先考虑杀掉咱们的应用程序,在这个时候应当尽量地把一切能够释放的东西都进行释放。

避免在Bitmap上浪费内存

当咱们读取一个Bitmap图片的时候,有一点必定要注意,就是千万不要去加载不须要的分辨率。在一个很小的ImageView上显示一张高分辨率的图片不会带来任何视觉上的好处,但却会占用咱们至关多宝贵的内存。须要仅记的一点是,将一张图片解析成一个Bitmap对象时所占用的内存并非这个图片在硬盘中的大小,可能一张图片只有100k你以为它并不大,可是读取到内存当中是按照像素点来算的,好比这张图片是1500*1000像素,使用的ARGB_8888颜色类型,那么每一个像素点就会占用4个字节,总内存就是1500*1000*4字节,也就是5.7M,这个数据看起来就比较恐怖了。

至于如何去压缩图片,以及更多在图片方面节省内存的技术,你们能够去参考我以前写的一篇博客 Android高效加载大图、多图解决方案,有效避免程序OOM 。

使用优化过的数据集合

Android API当中提供了一些优化事后的数据集合工具类,如SparseArray,SparseBooleanArray,以及LongSparseArray等,使用这些API可让咱们的程序更加高效。传统Java API中提供的HashMap工具类会相对比较低效,由于它须要为每个键值对都提供一个对象入口,而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间。

知晓内存的开支状况

咱们还应当清楚咱们所使用语言的内存开支和消耗状况,而且在整个软件的设计和开发当中都应该将这些信息考虑在内。可能有一些看起来无关痛痒的写法,结果却会致使很大一部分的内存开支,例如:

  • 使用枚举一般会比使用静态常量要消耗两倍以上的内存,在Android开发当中咱们应当尽量地不使用枚举。
  • 任何一个Java类,包括内部类、匿名类,都要占用大概500字节的内存空间。
  • 任何一个类的实例要消耗12-16字节的内存开支,所以频繁建立实例也是会必定程序上影响内存的。
  • 在使用HashMap时,即便你只设置了一个基本数据类型的键,好比说int,可是也会按照对象的大小来分配内存,大概是32字节,而不是4字节。所以最好的办法就是像上面所说的同样,使用优化过的数据集合。

谨慎使用抽象编程

许多程序员都喜欢各类使用抽象来编程,认为这是一种很好的编程习惯。固然,这一点不能否认,由于的抽象的编程方法更加面向对象,并且在代码的维护和可扩展性方面都会有所提升。可是,在Android上使用抽象会带来额外的内存开支,由于抽象的编程方法须要编写额外的代码,虽然这些代码根本执行不到,可是却也要映射到内存当中,不只占用了更多的内存,在执行效率方面也会有所下降。固然这里我并非提倡你们彻底不使用抽象编程,而是谨慎使用抽象编程,不要认为这是一种很酷的编程方式而去肆意使用它,只在你认为有必要的状况下才去使用。

尽可能避免使用依赖注入框架

如今有不少人都喜欢在Android工程当中使用依赖注入框架,好比说像Guice或者RoboGuice等,由于它们能够简化一些复杂的编码操做,好比能够将下面的一段代码:

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
  1. class AndroidWay extends Activity {   
  2.     TextView name;   
  3.     ImageView thumbnail;   
  4.     LocationManager loc;   
  5.     Drawable icon;   
  6.     String myName;   
  7.   
  8.     public void onCreate(Bundle savedInstanceState) {   
  9.         super.onCreate(savedInstanceState);   
  10.         setContentView(R.layout.main);  
  11.         name      = (TextView) findViewById(R.id.name);   
  12.         thumbnail = (ImageView) findViewById(R.id.thumbnail);   
  13.         loc       = (LocationManager) getSystemService(Activity.LOCATION_SERVICE);   
  14.         icon      = getResources().getDrawable(R.drawable.icon);   
  15.         myName    = getString(R.string.app_name);   
  16.         name.setText( "Hello, " + myName );   
  17.     }   
  18. }   
简化成这样的一种写法:
[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
  1. @ContentView(R.layout.main)  
  2. class RoboWay extends RoboActivity {   
  3.     @InjectView(R.id.name)             TextView name;   
  4.     @InjectView(R.id.thumbnail)        ImageView thumbnail;   
  5.     @InjectResource(R.drawable.icon)   Drawable icon;   
  6.     @InjectResource(R.string.app_name) String myName;   
  7.     @Inject                            LocationManager loc;   
  8.   
  9.     public void onCreate(Bundle savedInstanceState) {   
  10.         super.onCreate(savedInstanceState);   
  11.         name.setText( "Hello, " + myName );   
  12.     }   
  13. }  
看上去确实十分诱人,咱们甚至能够将findViewById()这一类的繁琐操做所有省去了。可是这些框架为了要搜寻代码中的注解,一般都须要经历较长的初始化过程,而且还可能将一些你用不到的对象也一并加载到内存当中。这些用不到的对象会一直占用着内存空间,可能要过好久以后才会获得释放,相较之下,也许多敲几行看似繁琐的代码才是更好的选择。

使用ProGuard简化代码

ProGuard相信你们都不会陌生,不少人都会使用这个工具来混淆代码,可是除了混淆以外,它还具备压缩和优化代码的功能。ProGuard会对咱们的代码进行检索,删除一些无用的代码,而且会对类、字段、方法等进行重命名,重命名以后的类、字段和方法名都会比原来简短不少,这样的话也就对内存的占用变得更少了。

使用多个进程

这个技巧其实并非很是建议使用,但它确实是一种能够帮助咱们节省和管理内存的高级技巧。若是你要使用它的话必定要谨慎使用,由于绝大多数的应用程序都不该该在多个进程当中运行的,一旦使用不当,它甚至会增长额外的内存而不是帮咱们节省内存。这个技巧比较适用于那些须要在后台去完成一项独立的任务,和前台的功能是能够彻底区分开的场景。

这里举一个比较适合去使用多进程技巧的场景,好比说咱们正在作一个音乐播放器软件,其中播放音乐的功能应该是一个独立的功能,它不须要和UI方面有任何关系,即便软件已经关闭了也应该能够正常播放音乐。若是此时咱们只使用一个进程,那么即便用户关闭了软件,已经彻底由Service来控制音乐播放了,系统仍然会将许多UI方面的内存进行保留。在这种场景下就很是适合使用两个进程,一个用于UI展现,另外一个则用于在后台持续地播放音乐。

想要实现多进程的功能也很是简单,只须要在AndroidManifest文件的应用程序组件中声明一个android:process属性就能够了,好比说咱们但愿播放音乐的Service能够运行在一个单独的进程当中,就能够这样写:

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
  1. <service android:name=".PlaybackService"  
  2.          android:process=":background" />  

这里指定的进程名是background,你也能够将它改为任意你喜欢的名字。须要注意的是,进程名的前面都应该加上一个冒号,表示该进程是一个当前应用程序的私有进程。

遵循以上的全部编程建议,咱们就可让应用程序内存的使用变得更加合理化。但这只是第一步而已,为了要让程序拥有最佳性能,咱们要学习的东西还有不少,下篇文章当中将会介绍如何分析内存的使用状况,感兴趣的朋友请继续阅读 Android最佳性能实践(二)——分析内存的使用状况 。

相关文章
相关标签/搜索