上一篇文章总结了一下内存分析的方法,这次聊聊如何出现解决和避免内存相关的问题。
上一篇文中我们提到,出现OOM通常是因为有内存泄漏或是内存使用不当(分配了过多的内存),在Android的早期时代,内存真是非常的珍贵啊,大部分是手机只有32或24M的heapsize,记得曾经有一个项目,有图片处理的逻辑,运行几分种就会OOM,很多人就说是有内存泄露,后来排查了半天,发现是处理的速度跟不上加载的速度,消耗了太多的内存导致,现在主流的手机heapsize基本上都有64M,应该来讲,只要没有内存泄漏,很少会发生OOM的,并且,从Android 3.0开始,引入了largeheap,就是说你在manifest的application TAG中加入 android:largeHeap="true" 之后就可以让你的应用申请更多的内存(每个手机定义的大小不尽相同),标准的heapsize和largeHeap都可以通过方法查询的到:
ActivityManager mgr = (ActivityManager) MainActivity.this .getSystemService(Context.ACTIVITY_SERVICE); Log.d(TAG, "MemoryClass is:"+mgr.getMemoryClass()+",large class is:"+mgr.getLargeMemoryClass());你的应用到底有多少的heapsize可以通过:
避免内存泄漏
首先是一定要避免内存泄漏,如果存在内存泄漏,多少内存也不够用,关于Java为什么会有内存泄漏以及内存泄露的定义IBM网站上有一篇文章说的非常到位: http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/
这里摘录一下该文的重点:
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对 象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占 用内存。
GC是如何工作的呢:
文章也很好的交代了这一点:
为了更好理解GC的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可 以作为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有 效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
这些问题阐述清楚之后我们来看看Android平台常见的导致内存泄漏的原因(基本上都是在以往的项目中碰到的):
1.registerContentObserver未解注册(观察者模式要小心,切记适时解注册);
2.没有关闭(一定要在finally中关闭)cursor,这里要多说一点的是,我们其实可以用AsyncQueryHandler或者 CursorLoader 等数据库异步加载框架避免在activity中对cursor的管理;
3.Adapter bindview实现不当,这个属于比较低级的,但是还是有人会这么干,就是在bindview里不去判断convertview直接创建,关于怎么实现这里不再多讲;
4.单例模式注册listener未解注册;
5.mTelePhonyMgr.listen(mListener,PhoneStateListener.LISTEN_NONE);貌似这么调用之后不能解注册,不知道4.0以后的版本有没有修改。
大部分的问题都跟static有关,一旦声明成static,除非我们主动的设置为null,否则是无法被回收的,像单例,观察者这些设计模式,通常都有生命周期较长的一方,使用的时候一定要小心。
能不创建的对象就不要创建
其实很多时候我们一个不小心的举动就会创建过多的对象,比如下面的这段代码:
public class DataBean { private String mData = ""; private ArrayList<DataB> mDataBs = new ArrayList<DataB>(); }
这就是很不好的编程习惯,如果这些字段为空,就无端的多创建了很多的对象,如果我们在内存中又加载了大量这样的对象,。。。。浪费啊。
再比如说,通常我们有一些比较复杂的界面,布局文件会比较庞大,常常会通过逻辑的需要来控制不同元素的可见性,这时候如果我们采用 viewstub,在需要的时候再加载相应的布局,就可以避免创建无用的对象,也可以加快activity的加载速度。:
另外,有时候我们可以通过复用对象来达到少创建对象的目地,比如当我们有大量的数据需要插入到数据库时,
我们就可以这么做:
private ContentValues mValues = new ContentValues(); public void inserVaules(String value1,int vaule2){ mValues.clear(); mValues.put(key, value1); mValues.put(key, vaule2); this.getContentResolver().insert(url, values); }
合理管理对象生命周期:
这个通常在架构阶段就要考虑清楚,我们有哪些东西是要常驻内存的,有哪些是伴随界面存在的,尤其是那些缓存和业务逻辑层的manager,在做缓存的时候一定要平衡好内存和性能,关于一些缓存资源的释放推荐看一下Android新引入的 onTrimMemory() 方法,目前源码中已经有部分的逻辑实现了该方法
比如系统luncher中:
public void onTrimMemory() { mContent.setVisibility(GONE); // Clear the widget pages of all their subviews - this will trigger the widget previews // to delete their bitmaps mAppsCustomizePane.clearAllWidgetPages(); }
AppsCustomizePagedView.java
public void clearAllWidgetPages() { cancelAllTasks(); int count = getChildCount(); for (int i = 0; i < count; i++) { View v = getPageAt(i); if (v instanceof PagedViewGridLayout) { ((PagedViewGridLayout) v).removeAllViewsOnPage(); mDirtyPageContent.set(i, true); } } } private void cancelAllTasks() { // Clean up all the async tasks Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator(); while (iter.hasNext()) { AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next(); task.cancel(false); iter.remove(); mDirtyPageContent.set(task.page, true); // We've already preallocated the views for the data to load into, so clear them as well View v = getPageAt(task.page); if (v instanceof PagedViewGridLayout) { ((PagedViewGridLayout) v).removeAllViewsOnPage(); } } mDeferredSyncWidgetPageItems.clear(); mDeferredPrepareLoadWidgetPreviewsTasks.clear(); }
科学设计应用背景图片
这个不用做过多的阐述,凡事都是有代价的,太炫太复杂太大太多的背景图片会导致应用耗用过多的内存。
另外,谷歌也给出了一些节省内存的 tips:
比如stringbuffer啊,尽量用int基本类型而不是Integer等。