Android: 缩放图片文件引起的OOM异常

简介:

http://dev.10086.cn/cmdn/wiki/index.php?doc-view-5037.html


传输文件,或者设置头像,我们一般都会检查原始图片的大小,作缩放处理。

常用的Java版缩放图片代码:

	public Bitmap getZoomImage(Bitmap src, int desW, int desH)
	{
		Bitmap desImg = null;
		int srcW = src.getWidth(); // 原始图像宽
		int srcH = src.getHeight(); // 原始图像高
		int[] srcBuf = new int[srcW * srcH]; // 原始图片像素信息缓存
		
		src.getPixels(srcBuf, 0, srcW, 0, 0, srcW, srcH);
		
		// 计算插值表
		int[] tabY = new int[desH];
		int[] tabX = new int[desW];
		
		int sb = 0;
		int db = 0;
		int tems = 0;
		int temd = 0;
		int distance = srcH > desH ? srcH : desH;
		for (int i = 0; i <= distance; i++)
		{/* 垂直方向 */
			tabY[db] = sb;
			tems += srcH;
			temd += desH;
			if (tems > distance)
			{
				tems -= distance;
				sb++;
			}
			if (temd > distance)
			{
				temd -= distance;
				db++;
			}
		}
		
		sb = 0;
		db = 0;
		tems = 0;
		temd = 0;
		distance = srcW > desW ? srcW : desW;
		
		for (int i = 0; i <= distance; i++)
		{/* 水平方向 */
			tabX[db] = (short) sb;
			tems += srcW;
			temd += desW;
			if (tems > distance)
			{
				tems -= distance;
				sb++;
			}
			if (temd > distance)
			{
				temd -= distance;
				db++;
			}
		}
		
		// 生成放大缩小后图形像素
		int[] desBuf = new int[desW * desH];
		int dx = 0;
		int dy = 0;
		int sy = 0;
		int oldy = -1;
		
		for (int i = 0; i < desH; i++)
		{
			if (oldy == tabY[i])
			{
				System.arraycopy(desBuf, dy - desW, desBuf, dy, desW);
			}
			else
			{
				dx = 0;
				for (int j = 0; j < desW; j++)
				{
					desBuf[dy + dx] = srcBuf[sy + tabX[j]];
					dx++;
				}
				sy += (tabY[i] - oldy) * srcW;
			}
			oldy = tabY[i];
			dy += desW;
		}
		// 生成图片
		desImg = Bitmap.createBitmap(desBuf, desW, desH, Bitmap.Config.ARGB_8888);
		
		return desImg;
	}

常用的Android版缩放图片代码:

ContentResolver cr = this.getContentResolver();
try
{
	InputStream in = cr.openInputStream(uri);
	Bitmap bitmap = BitmapFactory.decodeStream(in);
	try
	{
		in.close();
	}
	catch (IOException e)
	{
		e.printStackTrace();
	}
	if(null  == bitmap)
	{
		Toast.makeText(this, "Head is not set successful,Decode bitmap failure", 2000);
	}
	//原始图片的尺寸
	int bmpWidth  = bitmap.getWidth();
	int bmpHeight = bitmap.getHeight();
	
	//缩放图片的尺寸
	float scaleWidth  = (float) 40 / bmpWidth;
	float scaleHeight = (float) 40 / bmpHeight;
	Matrix matrix = new Matrix();
	matrix.postScale(scaleWidth, scaleHeight);
	
	//产生缩放后的Bitmap对象
	Bitmap resizeBitmap = Bitmap.createBitmap(
		bitmap, 0, 0, bmpWidth, bmpHeight, matrix, false);
	bitmap.recycle();
	//Bitmap to byte[]
	byte[] photoData = Bitmap2Bytes(resizeBitmap);
	
	//save file
	String fileName = "/sdcard/test.jpg";
	FileUtil.writeToFile(fileName, photoData);
	
	//save photo check sum to db
	DataCenter.GetInstance().ModifyIMMUser();
	//refresh ImageView
}
catch (FileNotFoundException exp)
{
	exp.printStackTrace();
}

如果图片非常大,在执行BitmapFactory.decodeStream的时候就会抛出OOM异常。

 

我们来看看系统应用MMS是如何处理的,SMS添加了多媒体附件后就作MMS处理了,当附加文件原图超过300K,也会做个缩放处理,具体参考:com.android.mms.ui/.UriImage:

package com.android.mms.ui;
public class UriImage
{
    private int mWidth;
    private int mHeight;
    ... ...
    //
    private void decodeBoundsInfo()
    {
        InputStream input = null;
        try
        {
            input = mContext.getContentResolver().openInputStream(mUri);
            BitmapFactory.Options opt = new BitmapFactory.Options();
            opt.inJustDecodeBounds = true;//只描边,不读取数据
            BitmapFactory.decodeStream(input, null, opt);
            mWidth = opt.outWidth;
            mHeight = opt.outHeight;
        }
        catch (FileNotFoundException e)
        {
            // Ignore
            Log.e(TAG, "IOException caught while opening stream", e);
        }
        finally
        {
            if (null != input) {
                try {
                    input.close();
                } catch (IOException e) {
                    // Ignore
                    Log.e(TAG, "IOException caught while closing stream", e);
                }
            }
        }
    }
    private byte[] getResizedImageData(int widthLimit, int heightLimit)
    {
        int outWidth = mWidth;
        int outHeight = mHeight;
        int s = 1;
        while ((outWidth / s > widthLimit) || (outHeight / s > heightLimit))
        {
            s *= 2;
        }
        //先设置选项
        BitmapFactory.Options options = new BitmapFactory.Options();
        //returning a smaller image to save memory.
        options.inSampleSize = s;
        InputStream input = null;
        try
        {
            input = mContext.getContentResolver().openInputStream(mUri);
            Bitmap b = BitmapFactory.decodeStream(input, null, options);//注意看options的用法
            if (b == null) {
                return null;
            }
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            b.compress(CompressFormat.JPEG, MessageUtils.IMAGE_COMPRESSION_QUALITY, os);
            return os.toByteArray();
        } catch (FileNotFoundException e) {
            Log.e(TAG, e.getMessage(), e);
            return null;
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    Log.e(TAG, e.getMessage(), e);
                }
            }
        }
    }
    ... ...
}

可以看出,MMS应用的方法是:先设置缩放选项,再读取缩放的图片数据到内存,规避了内存引起的OOM。

 

修改后的代码:

 	 				ContentResolver cr = this.getContentResolver();
 	 				try
 	 				{
 	 					InputStream in = cr.openInputStream(uri);
 	 		            BitmapFactory.Options options = new BitmapFactory.Options();
 	 		            options.inJustDecodeBounds = true;
 	 		            BitmapFactory.decodeStream(in, null, options);
 	 					try
 	 					{
							in.close();
						}
 	 					catch (IOException e)
 	 					{
							e.printStackTrace();
						}
 	 		            int mWidth = options.outWidth;
 	 		            int mHeight = options.outHeight;
 	 		            
 	 		            int sWidth  = 40;
 	 		            int sHeight = 40;
 	 		            
 	 			        int s = 1;
 	 			        while ((mWidth / s > sWidth * 2) || (mHeight / s > sHeight * 2))
 	 			        {
 	 			            s *= 2;
 	 			        }
 	 		            options = new BitmapFactory.Options();
 	 			        options.inSampleSize = s;
 	 					in = cr.openInputStream(uri);
 	 					Bitmap bitmap = BitmapFactory.decodeStream(in, null, options);
 	 					try
 	 					{
							in.close();
						}
 	 					catch (IOException e)
 	 					{
							e.printStackTrace();
						}
 	 					if(null  == bitmap)
 	 					{
 	 						Toast.makeText(this, "Head is not set successful,Decode bitmap failure", 2000);
 	 						return ;
 	 					}
 	 					//原始图片的尺寸
 	 					int bmpWidth  = bitmap.getWidth();
 	 					int bmpHeight = bitmap.getHeight();
 	 					
 	 					//缩放图片的尺寸
 	 					float scaleWidth  = (float) sWidth / bmpWidth;
 	 					float scaleHeight = (float) sHeight / bmpHeight;
 	 					Matrix matrix = new Matrix();
 	 					matrix.postScale(scaleWidth, scaleHeight);
 	 					
 	 					//产生缩放后的Bitmap对象
 	 					Bitmap resizeBitmap = Bitmap.createBitmap(
 	 						bitmap, 0, 0, bmpWidth, bmpHeight, matrix, false);
 	 					bitmap.recycle();
// 	 					Bitmap resizeBitmap = bitmap;
 	 					//Bitmap to byte[]
 	 					byte[] photoData = bitmap2Bytes(resizeBitmap);
 	 					
 	 					//save file
 	 					String fileName = "/sdcard/test.jpg";
 	 					FileUtil.writeToFile(fileName, photoData);

	private byte[] bitmap2Bytes(Bitmap bm)
	{
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		bm.compress(Bitmap.CompressFormat.JPEG, 100, baos);
		return baos.toByteArray();
	}


相关文章
|
12天前
|
存储 数据库 Android开发
安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐
为了在UI界面添加用于添加和删除本地音乐文件的按钮,以及相关的播放功能,你需要实现以下几个步骤: 1. **集成用户选择本地音乐**:允许用户从设备中选择音乐文件。 2. **创建UI按钮**:在界面中创建添加和删除按钮。 3. **数据库功能**:使用Room数据库来存储音频文件信息。 4. **更新ViewModel**:处理添加、删除和播放音频文件的逻辑。 5. **UI实现**:在UI层支持添加、删除音乐以及播放功能。
|
10天前
|
开发工具 Android开发
android studio build异常
android studio build异常
18 3
|
12天前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
13天前
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。
|
17天前
|
JSON 编解码 Apache
Android中使用HttpURLConnection实现GET POST JSON数据与下载图片
Android中使用HttpURLConnection实现GET POST JSON数据与下载图片
13 1
|
3天前
|
Android开发
Android Gradle开发—脚本实现自动打包后复制一份APK文件,并修改APK名称,到指定目录作备份
Android Gradle开发—脚本实现自动打包后复制一份APK文件,并修改APK名称,到指定目录作备份
10 0
|
3天前
|
缓存 大数据 Android开发
Android 巧用putBinder方法传递大文件
Android 巧用putBinder方法传递大文件
9 0
|
3天前
|
开发工具 Android开发
Android 代码自定义drawble文件实现View圆角背景
Android 代码自定义drawble文件实现View圆角背景
12 0
|
18天前
|
前端开发 Java API
Android系统中读写和显示图片
Android系统中读写和显示图片
12 0
|
3天前
|
安全 Java Android开发
安卓开发中的新趋势:Kotlin与Jetpack的完美结合
【6月更文挑战第20天】在不断进化的移动应用开发领域,Android平台以其开放性和灵活性赢得了全球开发者的青睐。然而,随着技术的迭代,传统Java语言在Android开发中逐渐显露出局限性。Kotlin,一种现代的静态类型编程语言,以其简洁、安全和高效的特性成为了Android开发中的新宠。同时,Jetpack作为一套支持库、工具和指南,旨在帮助开发者更快地打造优秀的Android应用。本文将探讨Kotlin与Jetpack如何共同推动Android开发进入一个新的时代,以及这对开发者意味着什么。