上一篇文章总结了一下内存分析的方法,这次聊聊如何出现解决和避免内存相关的问题。
上一篇文中我们提到,出现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可以通过:
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
来获取(注意,这里的单位是K)
我测试了几个手机,Galaxy nexus 可以申请到256M的heapsize,256M啊,同学们,这是多么的奢侈啊,但是人家谷歌说的很清楚,用这个标签一定要慎重,除非你是真的需要这么多内存,不能把这玩意作为快速解决OOM的手段,如果你的内存使用过多,还是先慎重的分析一下你的内存到哪里去了,因为这会导致GC的时间较长并且会影响系统的性能,原文如下:
However, the ability to request a large heap is intended only for a small set of apps that can justify the need to consume more RAM (such as a large photo editing app). Never request a large heap simply because you've run out of memory and you need a quick fix—you should use it only when you know exactly where all your memory is being allocated and why it must be retained. Yet, even when you're confident your app can justify the large heap, you should avoid requesting it to whatever extent possible. Using the extra memory will increasingly be to the detriment of the overall user experience because garbage collection will take longer and system performance may be slower when task switching or performing other common operations
无论手机的内存有多大,请记住,这只是手机,一定要珍惜内存。
那怎么样能够减少内存的使用呢,我个人认为,可以从以下几个方面入手:
- 避免内存泄漏
- 能不创建的对象就不要创建
- 合理管理对象生命周期
- 科学设计应用背景图片(尽量使用.9.png图片)
避免内存泄漏
首先是一定要避免内存泄漏,如果存在内存泄漏,多少内存也不够用,关于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的加载速度。:
写道
<ViewStub android:id="@+id/stub"
android:inflatedId="@+id/subTree"
android:layout="@layout/mySubTree"
android:layout_width="120dip"
android:layout_height="40dip" />
另外,有时候我们可以通过复用对象来达到少创建对象的目地,比如当我们有大量的数据需要插入到数据库时,
我们就可以这么做:
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中:
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
mAppsCustomizeTabHost.onTrimMemory();
}
}
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();
}
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等。