在项目中要使用几十张图片,以及背景,产生了out of memory异常.
Bitmap bitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), gi.getBitMapResoureceId(), mOptions));
因为大量使用图片而且切换屏幕时或回到前台时重复生成Bitmap ,导致内存溢出。
专门针对out of memory进行了调查。
问题点:
a.使用大量图片,耗用内存和cup
b.横竖屏切换或者activity回到前台时重复的调用了一些代码,例如读取数据库,重复生成一些对象
c.由于组件的背景图片多,在频繁横竖屏切换时重新加载xml会一时间占用大量内存
方法:
1.提取重复性的代码,就是能一次在onCreate()中做好的东西,不要再onResume()和onConfigurationChanged()中重复出现,这个要考虑业务和逻辑的东西。
但是组件和Layout还是要在onConfigurationChanged()重新加载的(我们使用了android:configChanges="touchscreen|keyboardHidden|orientation",所以横竖屏切换时只调用onConfigurationChanged()方法)。
2.将app的背景图片和button的背景及src从xml中提取出来,在代码中设置为成员变量,一次性读入不再重复读取
例如:
xml中:
android:background="@drawable/bg"
android:src="@drawable/water"
Java代码中:
private Drawable mBgDrawable = getResources().getDrawable(R.drawable.bg);
private Drawable mWaterDrawable = getResources().getDrawable(R.drawable.waterbg);
RelativeLayout mMainLayout = (RelativeLayout) findViewById(R.id.main);
ImageButton mBtnWater = (ImageButton ) findViewById(R.id.waterbyn);
mMainLayout.setBackgroundDrawable(mBgDrawable );
mBtnWater .setImageDrawable(mWaterDrawable );
ImageButton 设置时也可以是setBackgroundDrawable那这样就和xml中的background一个效果了。
后来又把这些成员变量做出static的,是为了提高效率。
但后来发现了问题。就是网上很多的关于Drawable会使用setCallback保留对组件(ImageButton )的引用,而组件又会保留对activity的引用,这样activity上的计数器不会0,虚拟机也不能回收已经不用的activity对象,再次引发内存溢出。
setBackgroundDrawable:
public void setBackgroundDrawable(Drawable d) { boolean requestLayout = false; mBackgroundResource = 0; /* * Regardless of whether we're setting a new background or not, we want * to clear the previous drawable. */ if (mBGDrawable != null) { mBGDrawable.setCallback(null); unscheduleDrawable(mBGDrawable); } if (d != null) { Rect padding = sThreadLocal.get(); if (padding == null) { padding = new Rect(); sThreadLocal.set(padding); } if (d.getPadding(padding)) { setPadding(padding.left, padding.top, padding.right, padding.bottom); } // Compare the minimum sizes of the old Drawable and the new. If there isn't an old or // if it has a different minimum size, we should layout again if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() || mBGDrawable.getMinimumWidth() != d.getMinimumWidth()) { requestLayout = true; } d.setCallback(this); if (d.isStateful()) { d.setState(getDrawableState()); } d.setVisible(getVisibility() == VISIBLE, false); mBGDrawable = d; if ((mPrivateFlags & SKIP_DRAW) != 0) { mPrivateFlags &= ~SKIP_DRAW; mPrivateFlags |= ONLY_DRAWS_BACKGROUND; requestLayout = true; } } else { /* Remove the background */ mBGDrawable = null; if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) { /* * This view ONLY drew the background before and we're removing * the background, so now it won't draw anything * (hence we SKIP_DRAW) */ mPrivateFlags &= ~ONLY_DRAWS_BACKGROUND; mPrivateFlags |= SKIP_DRAW; } /* * When the background is set, we try to apply its padding to this * View. When the background is removed, we don't touch this View's * padding. This is noted in the Javadocs. Hence, we don't need to * requestLayout(), the invalidate() below is sufficient. */ // The old background's minimum size could have affected this // View's layout, so let's requestLayout requestLayout = true; } computeOpaqueFlags(); if (requestLayout) { requestLayout(); } mBackgroundSizeChanged = true; invalidate(); }
这里面的d.setCallback(this)可能会导致保留对组件的引用。当重新调用setBackgroundDrawable时,Drawable调用mBGDrawable.setCallback(null)先将原来的引用设置为null。
回到我的项目,我们在切换横竖屏时并没有重新生成activity,也没有需要销毁的activity,而且最初我们没有设置为static,那么这些成员变量的生命周期和activity是相同的,不会引发上述问题。(理论推测)我们目前的做法是在onConfigurationChanged()中增加了这么句话:
mWaterDrawable .setCallback(null)做个双保险。
那当将Drawable的变量设置为static后,应该在onDestroy中也调用mWaterDrawable .setCallback(null),保证activity可以被正常回收。
继续跟进中……