大量加载大drawable下图片,导致OOM,使用二次裁剪

最近由于要读取drawable下的图片,这些图片有将近50张,平均每张100K左右吧。最后导致了滑动不流畅,以至于OOM。

所以就想办法解决,最后经过请假别人,有的告诉我ImageLoader框架,这个还需要研究,后来又有人告诉我用二次裁剪,说白了就是压缩图片。其实说实话,这些名词以前都没有听说过,由于是新手,接触的东西不多,遇到的问题自然也不多,既然越到了,就想办法解决,而且这种问题再以后应该是很通用的。经过网上参考别人的,最后结合我的测试项目,来讲解下。


Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。

利用BitmapFactory可以从一个指定文件中,利用decodeFile()解出Bitmap;也可以定义的图片资源中,利用decodeResource()解出Bitmap。

在使用方法decodeFile()/decodeResource()时,都可以指定一个BitmapFacotry.Options

利用Options的下列属性,可以指定decode的选项:

  • inPreferredConfig 指定decode到内存中,手机中所采用的编码,可选值定义在Bitmap.Config中。缺省值是ARGB_8888。
  • inJustDecodeBounds 如果设置为true,并不会把图像的数据完全解码,亦即decodeXyz()返回值为null,但是Options的outAbc中解出了图像的基本信息。
  • inSampleSize 设置decode时的缩放比例。

先设置inJustDecodeBounds= true,调用decodeFile()得到图像的基本信息[Step#2~4];

利用图像的宽度(或者高度,或综合)以及目标的宽度,得到inSampleSize值,再设置inJustDecodeBounds= false,调用decodeFile()得到完整的图像数据。

先获取比例,再读入数据,如果欲读入大比例缩小的图,将显著的节约内容资源。有时候还会读入大量的缩略图,这效果就更明显了。


首先来一个压缩工具类BitmapCompressTools:

import android.R.integer;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;

public class BitmapCompressTools {
	public static Bitmap decodeSampledBitmapFromResource(Resources res,
			int resId, int reqWidth, int reqHeight) {
		// 给定BitmapFactory设置解码的参数
		final BitmapFactory.Options options = new BitmapFactory.Options();

		// 从解码器中获取原始图片的高度,这样就避免了直接申请内存控件
		options.inJustDecodeBounds = true;
		// inJustDecodeBounds
		// 如果设置为true,并不会把图像的数据完全解码,亦即decodeXyz()返回值为null,
		// 但是Options的outAbc中解出了图像的基本信息。
		BitmapFactory.decodeResource(res, resId, options);

		// 属性值inSampleSize表示缩略图大小为原始图片大小的几分之一
		// options.inSampleSize = calculateInSampleSize(options, reqWidth,
		// reqHeight);
		options.inSampleSize = 10;

		// 压缩完后便可以将inJustDecodeBounds设置为false了
		options.inJustDecodeBounds = false;
		return BitmapFactory.decodeResource(res, resId, options);
	}

	/*
	 * 指定图片的缩放比例里
	 */
	public static int calculateInSampleSize(BitmapFactory.Options options,
			int reqWidth, int reqHeight) {

		// 原始图片的宽高
		final int height = options.outHeight;
		final int width = options.outWidth;
		int inSampleSize = 1;

		if (height > reqHeight || width > reqWidth) {
			// // 这里两种压缩方式,可以选择
			// /*
			// * 方式1
			// */
			// final int halfHeight = height / 2;
			// final int halfWidth = width / 2;
			// while (((halfWidth / inSampleSize) > reqWidth)
			// && ((halfHeight / inSampleSize) > reqHeight)) {
			// inSampleSize *= 2;
			// }
			/*
			 * 方式2
			 */
			// 计算压缩的比例
			final int heightRatio = Math.round((float) height
					/ (float) reqHeight);
			final int widthRatio = Math.round((float) width / (float) reqWidth);
			inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
		}

		return inSampleSize;
	}

}

此处加点额外的知识:

在开发图片浏览器等软件是,很多时候要显示图片的缩略图,而一般情况下,我们要将图片按照固定大小取缩略图,一般取缩略图的方法是使用BitmapFactory的decodeFile方法,然后通过传递进去 BitmapFactory.Option类型的参数进行取缩略图,在Option中,属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
  然而,如果我们想取固定大小的缩略图就比较困难了,比如,我们想将不同大小的图片去出来的缩略图高度都为200px,而且要保证图片不失真,那怎么办?我们总不能将原始图片加载到内存中再进行缩放处理吧,要知道在移动开发中,内存是相当宝贵的,而且一张100K的图片,加载完所占用的内存何止 100K?
  经过研究,发现,Options中有个属性inJustDecodeBounds,研究了一下,终于明白是什么意思了,SDK中的E文是这么说的
  If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.
  意思就是说如果该值设为true那么将不返回实际的bitmap不给其分配内存空间而里面只包括一些解码边界信息即图片大小信息,那么相应的方法也就出来了,通过设置inJustDecodeBounds为true,获取到outHeight(图片原始高度)和 outWidth(图片的原始宽度),然后计算一个inSampleSize(缩放值),然后就可以取图片了,这里要注意的是,inSampleSize 可能小于0,必须做判断。
具体代码如下:
FrameLayout fr=(FrameLayout)findViewById(R.id.FrameLayout01);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        // 获取这个图片的宽和高
        Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.jpg", options); //此时返回bm为空
        options.inJustDecodeBounds = false;
         //计算缩放比
        int be = (int)(options.outHeight / (float)200);
        if (be <= 0)
            be = 1;
        options.inSampleSize = be;
        //重新读入图片,注意这次要把options.inJustDecodeBounds 设为 false哦
        bitmap=BitmapFactory.decodeFile("/sdcard/test.jpg",options);
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        System.out.println(w+"   "+h);
        ImageView iv=new ImageView(this);
        iv.setImageBitmap(bitmap);

这样我们就可以读取较大的图片而不会内存溢出了。如果你想把压缩后的图片保存在Sdcard上的话就很简单了:
File file=new File("/sdcard/feng.png");
        try {
            FileOutputStream out=new FileOutputStream(file);
            if(bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)){
                out.flush();
                out.close();
            }
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


接下来继续上代码。

MainAcitvity:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import android.app.Activity;
import android.content.Intent;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity implements OnClickListener {
	// 定义两个按钮
	private Button submit, look;

	static String DbName = "jingdian_db";

	Handler handler = new Handler() {
		// 处理其他线程传来的消息
		@Override
		public void handleMessage(android.os.Message msg) {
			super.handleMessage(msg);
			switch (msg.what) {
			case 1:
				importDB();
				break;

			default:
				break;
			}
		};

	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		// 根据id 获取到相对应的控件
		submit = (Button) findViewById(R.id.button1);
		look = (Button) findViewById(R.id.button2);

		// 按钮设置点击监听
		submit.setOnClickListener(this);
		look.setOnClickListener(this);

	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.button1:
			Message msg = new Message();
			msg.what = 1;
			handler.sendMessage(msg);
			break;
		case R.id.button2:
			Intent intent = new Intent();
			intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
			intent.setClass(this, ResultActivity.class);
			startActivity(intent);
			break;
		default:
			break;
		}

	}

	private void importDB() {
		File file = new File(getFilesDir(), DbName);
		// String DbName = "people_db";
		// 判断是否存在
		if (file.exists() && file.length() > 0) {
			Log.i("import_db", "已存在");
		} else {
			// 使用AssetManager类来访问assets文件夹
			AssetManager asset = getAssets();
			InputStream is = null;
			FileOutputStream fos = null;
			try {
				is = asset.open(DbName);
				fos = new FileOutputStream(file);
				int len = 0;
				byte[] buf = new byte[1024];
				while ((len = is.read(buf)) != -1) {
					fos.write(buf, 0, len);
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if (is != null) {
						is.close();
					}
					if (fos != null) {
						fos.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

}

Resultactivity类:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import android.app.Activity;
import android.content.Intent;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity implements OnClickListener {
	// 定义两个按钮
	private Button submit, look;

	static String DbName = "jingdian_db";

	Handler handler = new Handler() {
		// 处理其他线程传来的消息
		@Override
		public void handleMessage(android.os.Message msg) {
			super.handleMessage(msg);
			switch (msg.what) {
			case 1:
				importDB();
				break;

			default:
				break;
			}
		};

	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		// 根据id 获取到相对应的控件
		submit = (Button) findViewById(R.id.button1);
		look = (Button) findViewById(R.id.button2);

		// 按钮设置点击监听
		submit.setOnClickListener(this);
		look.setOnClickListener(this);

	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.button1:
			Message msg = new Message();
			msg.what = 1;
			handler.sendMessage(msg);
			break;
		case R.id.button2:
			Intent intent = new Intent();
			intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
			intent.setClass(this, ResultActivity.class);
			startActivity(intent);
			break;
		default:
			break;
		}

	}

	private void importDB() {
		File file = new File(getFilesDir(), DbName);
		// String DbName = "people_db";
		// 判断是否存在
		if (file.exists() && file.length() > 0) {
			Log.i("import_db", "已存在");
		} else {
			// 使用AssetManager类来访问assets文件夹
			AssetManager asset = getAssets();
			InputStream is = null;
			FileOutputStream fos = null;
			try {
				is = asset.open(DbName);
				fos = new FileOutputStream(file);
				int len = 0;
				byte[] buf = new byte[1024];
				while ((len = is.read(buf)) != -1) {
					fos.write(buf, 0, len);
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if (is != null) {
						is.close();
					}
					if (fos != null) {
						fos.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

}

还有一个存放大量图片的Image类:

public class Image {
	/*
	 * 变量和常量用static加final修饰,可以大大提高它的访问速度
	 */
	public final static Integer[] images = { R.drawable.jinci_icon1,
			R.drawable.chunyanggong_icon1, R.drawable.tianlongshanshiku_icon1,
			R.drawable.shanxibowuyuan_icon1, R.drawable.fenhegongyuan_icon1,
			R.drawable.shuangtasi_icon1, R.drawable.dafosi_icon1,
			R.drawable.tianningsi_icon1, R.drawable.shengmudian_icon1,
			R.drawable.dongwuyuan_icon1, R.drawable.yingzegongyuan_icon1,
			R.drawable.jueweishan_icon1, R.drawable.chongshansi_icon1,
			R.drawable.jindoudafuci_icon1, R.drawable.ertonggongyuan_icon1,
			R.drawable.kaihuasilianlita_icon1, R.drawable.duofusi_icon1,
			R.drawable.yurangqiao_icon1, R.drawable.foguangchansi_icon1,
			R.drawable.qingzhengusi_icon1, R.drawable.jiulonghuaxuechang_icon1,
			R.drawable.aolinhaishijie_icon1,
			R.drawable.zhongguomeitanbowuguan_icon1,
			R.drawable.mengshandafo_icon1, R.drawable.fenheerku_icon1,
			R.drawable.liuxiang_icon1, R.drawable.diantougubao_icon1,
			R.drawable.erlongshan_icon1, R.drawable.yuwenshanzhuang_icon1,
			R.drawable.gujinsenguojiasenlingongyuan_icon1,
			R.drawable.longshanshiku_icon1, R.drawable.donghucuyuan_icon1,
			R.drawable.jinyanghu_icon1, R.drawable.zhonghuafushanyuan_icon1,
			R.drawable.tianlongsi_icon1, R.drawable.xilinghuaxuechang_icon1,
			R.drawable.yingzedajie_icon1, R.drawable.xuefugongyuan_icon1,
			R.drawable.yinfengongyuan_icon1, R.drawable.taishang_icon1,
			R.drawable.jinyanghu_icon1, R.drawable.zhonghuafushanyuan_icon1,
			R.drawable.tianlongsi_icon1, R.drawable.xilinghuaxuechang_icon1,
			R.drawable.yingzedajie_icon1, R.drawable.xuefugongyuan_icon1,
			R.drawable.xilinghuaxuechang_icon1, R.drawable.yingzedajie_icon1,
			R.drawable.xuefugongyuan_icon1 };
}

还有一个Person类:

package com.test.mode;

import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;

public class Person {
	
	private int imageView;
	private String mingcheng;
	private String jieshao;
	private String leixing;
	private int _id;
	private Bitmap bitmap;

	public Bitmap getBitmap() {
		return bitmap;
	}

	public void setBitmap(Bitmap bitmap) {
		this.bitmap = bitmap;
	}

	public int getImageView() {
		return imageView;
	}

	public void setImageView(int imageView) {
		this.imageView = imageView;
	}

	public String getMingcheng() {
		return mingcheng;
	}

	public void setMingcheng(String mingcheng) {
		this.mingcheng = mingcheng;
	}

	public String getJieshao() {
		return jieshao;
	}

	public void setJieshao(String jieshao) {
		this.jieshao = jieshao;
	}

	public String getLeixing() {
		return leixing;
	}

	public void setLeixing(String leixing) {
		this.leixing = leixing;
	}

	public int get_id() {
		return _id;
	}

	public void set_id(int _id) {
		this._id = _id;
	}

}

项目原码


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值