说明这个问题,首先来看一下实际的内存占用情况。
我们创建一个最简单的android应用,一个Activity,内容是一张图片,图片放在drawable-hdpi目录下。布局文件:
<?xmlversion="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/welcome"
android:id="@+id/splash_layout">
</RelativeLayout>
图片文件大小145k。
打开DDMS,查看内存占用的情况:
在米1(小屏手机)上的内存占用情况:
而在大屏手机上显示的内存占用情况:
内存占用了12M,1-byte array(byte,boolean)栏中,内存占了8.5M,而最大一个对象占了4.16M,应该就是图片。而实际上图片大小也就145K。之所以大屏手机会出现这种情况,主要是因为我们把图片放在hdpi目录下,而大屏手机实际上是xhdpi甚至是xxhdpi。根据Android官方文档提供的资料:
By default, Android scales your bitmapdrawables (.png,.jpg, and.giffiles) and Nine-Patch drawables (.9.pngfiles) sothat they render at the appropriate physical size on each device. For example,if your application provides bitmap drawables only for the baseline, mediumscreen density (mdpi), then the systemscales them up when on ahigh-density screen, and scales them down when on a low-densityscreen.
Android会自动拉伸图片以适应当前手机的分辨率。因此导致了OOM内存溢出。
那么如果我们把hdpi的图片复制一份到xhdpi和xxhdpi中会怎样?
查看运行的结果:
在米1(小屏手机)上的内存占用情况:
而在大屏手机上显示的内存占用情况:
内存已经有所减少,特别是1-bytearray(byte[], boolean[])这一行。米1由于对应的是hdpi的图片,所以不受影响。
但是这样做也有问题,首先我们得把图片都拷贝到不同分辨率对应的目录下(最正确的做法是分别做一套对应分辨率的图片);其次图片占用内存的大小虽然已经减小,但还是太大。
再想一想,我们在布局文件里面写android:background和android:src这些属性,实际上解析之后执行的是view.setBackgroundResource和view.setImageResource方法,这两个方法实际上是拿到资源ID再去获取资源的drawable。他们会decode图片后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。
实际上我们可以用decodeStream来替代,因为decodeStream直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间.另外我们可以设置图片的参数,例如设置为Bitmap.Config.RGB_565来减少内存开销。因为在android文档中描述Bitmap.Config.RGB_565每一个像素存在2个字节中,而默认的Bitmap.Config.ARGB_8888每一个像素则需要4个字节,理论上足足节省了一半空间。
了解这些之后,我把布局文件中的android:background去掉,在java文件中来设置背景。
如下:
BitmapFactory.Options opt = newBitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
//获取资源图片
InputStream is = context.getResources().openRawResource(resId);
Bitmap bitmap = BitmapFactory.decodeStream(is,null, opt);
is.close();
returnnew BitmapDrawable(context.getResources(),bitmap);
运行之后在来看效果:
在米1(小屏手机)上的内存占用情况:
而在大屏手机上显示的内存占用情况:
内存瞬间降下来,1-bytearray(byte[], boolean[])这一行最大值为151K。