标签:
问题: 安卓系统经常遇到OOM问题,如何优化和应对?
导致OOM 有以下几种情况:
1 应用中需要加载大对象,例如Bitmap
解决方案:当我们需要显示大的bitmap对象或者较多的bitmap的时候,就需要进行压缩来防止OOM问题。我们可以通过设置BitmapFactory.Optiions的inJustDecodeBounds属性为true,这样的话不会加载图片到内存中,但是会将图片的width和height属性读取出来,我们可以利用这个属性来对bitmap进行压缩。
另外可以通过对象池来减少gc
对于不用的Bitmap对象,我们要及时回收,否则会造成Memory leak ,所以当我们确定Bitmap对象不用的时候要及时调用Bitmap.recycle()方法来使它尽早被GC
尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。
因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。
静态变量导致的Memory leak
不合理使用Context 导致的Memory leak
非静态内部类导致的Memory leak
Drawable对象的回调隐含的Memory leak
3 对于设备本身对应用head内存大小的限制,我们可以自己定义大小Android堆内存
在Android中这个上限值默认是“16m”,而你可以根据实际的硬件配置来调整这个上限值,调整的方法是在系统启动时加载的某个配置文件中设置一个系统属性:
dalvik.vm.heapsize=24m
对于一些Android项目,影响性能瓶颈的主要是Android自己内存管理机制问题,目前手机厂商对RAM都比较吝啬,对于软件的流畅性来说RAM对性能的影响十分敏感,除了 优化Dalvik虚拟机的堆内存分配外,我们还可以强制定义自己软件的对内存大小,我们使用Dalvik提供的 dalvik.system.VMRuntime类来设置最小堆内存为例:
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //设置最小heap内存为6MB大小。当然对于内存吃紧来说还可以通过手动干涉GC去处理
4,当我们在切换视图屏幕的时候(横竖屏),就会重新建立横屏或者竖屏的Activity。我们形象的认为之前建立的Activity会被回收,但是事实如何呢?Java机制不会给你同样的感受,在我们释放Activity之前,因为run函数没有结束,这样MyThread并没有销毁,因此引用它的Activity(Mytest)也有没有被销毁,因此也带来的内存泄露问题。
有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。
线程问题的改进方式主要有:
l 将线程的内部类,改为静态内部类。
l 在程序中尽量采用弱引用保存Context。
5,被JNI中的指针引用着
Framework中的一些类经常会在Java层创建一个对象,同时也在C++层创建一个对象,然后通过JNI让这两个对象相互引用(保存对方的地址),BinderProxy对象就是一个很典型的例子,在这种情况下,Java层的对象同样不会被释放。
当泄漏的内存随着程序的运行越来越多时,最终就会达到heapsize设定的上限值,此时虚拟机就会抛出OutOfMemoryError错误,内存溢出了。
6,Cursor对象未正确关闭
(1) 实际在使用的时候代码的逻辑通常会比上述示例要复杂的多,但总的原则是一定要在使用完毕Cursor以后正确的关闭。
(2) 如果你的Cursor需要在Activity的不同的生命周期方法中打开和关闭,那么一般可以这样做:
在onCreate()中打开,在onDestroy()中关闭;
在onStart() 中打开,在onStop() 中关闭;
在onResume()中打开,在onPause() 中关闭;
即要在成对的生命周期方法中打开/关闭。
(3) 如果程序中使用了CursorAdapter(例如Music),那么可以使用它的changeCursor(Cursor cursor)方法同时完成关闭旧Cursor使用新Cursor的操作。
(4) 至于在cursor.close时需不需要try...catch(cursor非空时),其实在close时做的工作就是释放资源,包括通过Binder跨进程注销ContentObserver时已经捕获了RemoteException异常,所以其实可以不用try...catch。
(5) 关于deactive和close,deactive不等同于close,看他们的API comments就能知道,如果deactive了一个Cursor,说明以后还是会用到它(利用requery方法),这个Cursor会释放一部分资源,但是并没有完全释放;如果确认不再使用这个Cursor了,一定要close。
(6)除了Cursor有时我们也会对Database对象做操作,例如要修正MediaProvider中的一个attachVolume方法,在每次检测到attach的是一个external的volume时就重新建立一个数据库,而不是采用以前的,那么在remove旧的数据库对象的时候不要忘记关闭它。<!-- 第6点关于Database是否考虑去掉 -->
文件描述符泄漏
当然有可能很幸运,每次查询的结果集都很小,做几千次查询都不会内存溢出,但是Android的Linux内核还有另外一个限制,就是文件描述符的上限,这个上限默认是1024。
文件描述符本身是一个整数,用来表示每一个被进程所打开的文件和Socket,第一个打开的文件是0,第二个是1,依此类推。而Linux给每个进程能打开的文件数量设置了一个上限,可以使用命令“ulimit -n”查看。另外,操作系统还有一个系统级的限制。
每次创建一个Cursor对象,都会向内核申请创建一块共享内存,这块内存以文件形式提供给应用进程,应用进程会获得这个文件的描述符,并将其映射到自己的进程空间中。如果有大量的Cursor对象没有正常关闭,可想而知就会有大量的共享内存的文件描述符无法关闭,同时再加上应用进程中的其他文件描述符,就很容易达到1024这个上限,一旦达到,进程就挂掉了。
经常要用到一些XxxListener对象,或者是XxxObserver、XxxReceiver对象,然后用registerXxx方法注册,用unregisterXxx方法注销。本身用法也很简单,但是从一些实际开发中的代码来看,仍然会有一些问题:
(1) registerXxx和unregisterXxx方法的调用通常也和Cursor的打开/关闭类似,在Activity的生命周期中成对的出现即可:
在 onCreate() 中 register,在 onDestroy() 中 unregitster;
在 onStart() 中 register,在 onStop() 中 unregitster;
在 onResume() 中 register,在 onPause() 中 unregitster;
(2) 忘记unregister
以前看到过一段代码,在Activity中定义了一个PhoneStateListener的对象,将其注册到TelephonyManager中:
TelephonyManager.listen(l,PhoneStateListener.LISTEN_SERVICE_STATE);
但是在Activity退出的时候注销掉这个监听,即没有调用以下方法:
TelephonyManager.listen(l,PhoneStateListener.LISTEN_NONE);
因为PhoneStateListener的成员变量callback,被注册到了TelephonyRegistry中,TelephonyRegistry是后台的一个服务会一直运行着。所以如果不注销,则callback对象无法被释放,PhoneStateListener对象也就无法被释放,最终导致Activity对象无法被释放。
标签:
原文地址:http://blog.csdn.net/crazy__chen/article/details/45460279