问题说明:网络下大图片,遇到的outofmemery问题。
我做的需求是类似一个图片浏览器,负责接收一个地址,并把图片显示出来。我的思路是 从网络下载图片保存在内存,转成 Bitmap 从而在 ImageView 中显示。
好了 问题来了,在我下载图片时出现了 outofmemery 问题代码在于
InputStream imageDateStream = conn.getInputStream(); //从网络上接受一个输入流
BufferedInputStream in = new BufferedInputStream(imageDateStream);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
System.out.println("--------------begin----------");
while ( (size = inputStream.read(buf)) != -1 ){
outputStream.write(buf, 0, size); -----------------------问题就处在这里,爆出 outofmemery
}
System.out.println("--------------------end----------------");
我就没有把下载的图片数据保存成文件,只是保存在 byte byteArray[] 内,
2、在获得byteArray[] 数据后 再显示图片的时候 再次遇到 outofmemery错误,解决方法是 减少像素的采集。代码如下:
byte byteArray[] = outputStream.toByteArray();
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opts);
int image_size = computeSampleSize(opts, -1, ServerActivity.TOTAL_WIDTH * ServerActivity.TOTAL_HEIGHT );
System.out.println("图片缩小的比例====" + image_size);
opts.inSampleSize = image_size;
opts.inJustDecodeBounds = false;
try {
imageBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opts);
} catch (OutOfMemoryError err) {
err.printStackTrace();
}
//动态计算取样的值
public static int computeSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
int initialSize = computeInitialSampleSize(options, minSideLength,maxNumOfPixels);
int roundedSize;
if (initialSize <= 8 ) {
roundedSize = 1;
while (roundedSize < initialSize) {
roundedSize <<= 1;
}
} else {
roundedSize = (initialSize + 7) / 8 * 8;
}
return roundedSize;
}
private static int computeInitialSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == -1) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == -1) ? 128 :
(int) Math.min(Math.floor(w / minSideLength),
Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
// return the larger one when there is no overlapping zone.
return lowerBound;
}
if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
return 1;
} else if (minSideLength == -1) {
return lowerBound;
} else {
return upperBound;
}
}
//-----------------------------------------------------------------因为是把图片保存到内存,数据又大,使用完之后的清理是必须的。
个人觉得放在 finally
try{
//下载图片
} catch (OutOfMemoryError err) {
err.printStackTrace();
displaytoast("图片下载失败!");
System.out.println("出现 outofmemery异常,结束图片浏览器!");
clearBitmap();
finish();
}catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("执行清理工作。。。。。");
if(outputStream != null) {
outputStream.flush();
outputStream.close();
}
if(inputStream != null) {
inputStream.close();
}
imageDateStream.close();
conn.disconnect();
System.out.println("断开连接。。。。。");
}
//---------------------------------------------------------------------------图片使用完毕之后---------------------------------
if(imageBitmap != null && !imageBitmap.isRecycled() ){
imageBitmap.recycle(); //回收图片所占的内存
System.gc(); //提醒系统及时回收
}
imageBitmap = null;
不过很遗憾,我还是没能解决图片下载到内存时,遇到的outofmemery 问题,暂时用
try {
//下载
}catch (OutOfMemoryError err) {
err.printStackTrace();
displaytoast("图片下载失败!");
System.out.println("出现 outofmemery异常,结束图片浏览器!");
clearBitmap();
finish();
}
这样做 只是保证软件不会意外退出,在发生outmemery 的时候
之间使用过计算可用内存的方法,限制超出可使用内存的图片下载,可惜没成功,原因是 内存剩余的空间还多得很,还是曝出了内存异常。
long totalMemory = Runtime.getRuntime().maxMemory();
long usedMemery = Runtime.getRuntime().totalMemory();
奇怪的是 多次浏览同几张图片,偶尔爆出outofmemery 并不是每次都会,郁闷....................,有知道解决方法的望留言告知。
--------------------------------------------------------------------------------问题新进展-----------------------------------------------------------------------------------
outofmemery报错是在 inputstream 转成 outputstream 时出错,很容易联想到 能否直接使用 inputstream,
具体代码是:Bitmap result = BitmapFactory.decodeStream(inputstream);
遗憾的是 这样写 图片一般要下载好几次才能成功一次,网上继续搜索发现以下描述:
这里Android开发网提醒大家,BitmapFactory类的decodeStream方法在网络超时或较慢的时候无法获取完整的数据,这里我们通过继承FilterInputStream类的skip方法来强制实现flush流中的数据,主要原理就是检查是否到文件末端,告诉http类是否继续。
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int byte = read();
if (byte < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
关于 输入流直接转成 图片 不完整的解决方案如下:
imageBitmap = BitmapFactory.decodeStream(new PatchInputStream(inputStream), null, opts);
其中 PathImputStream 是个内部类 代码如下:
//让inputstream 数据保持完整性
public class PatchInputStream extends FilterInputStream {
protected PatchInputStream(InputStream in) {
super(in);
// TODO Auto-generated constructor stub
}
public long skip(long n) throws IOException {
long m = 0l;
while (m < n) {
long _m = in.skip(n - m);
if (_m == 0l) {
break;
}
m += _m;
}
return m;
}
}
最后采用的方案:
int inSampleSize = getPictureUsedecodeStream();
downloadPictureUsedecodeStream( inSampleSize );
****************************************************************
/**
* 直接使用inputstream下载图片,提取像素的比例是固定值
*
*/
private int getPictureUsedecodeStream() {
int inSampleSize = 0;
try {
URL imageUrl = new URL( pictureURL );
HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection();
// 注意要设置超时,设置时间不要超过10秒,避免被android系统回收
conn.setConnectTimeout(10*1000*60);
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestProperty("Connection", "Keep-Alive");
conn.connect();
if (conn.getResponseCode() != 200) {
System.out.println("getPictureUsedecodeStream-----请求url失败: ");
throw new RuntimeException("getPictureUsedecodeStream--请求url失败");
}else {
int netIconSize = conn.getContentLength();
InputStream imageDateStream = conn.getInputStream();
long totalMemory = Runtime.getRuntime().maxMemory();
long usedMemery = Runtime.getRuntime().totalMemory();
if( netIconSize <= 0 || (totalMemory - usedMemery) < netIconSize) {
imageDateStream.close();
conn.disconnect();
}else { //执行图片下载
BufferedInputStream inputStream = null;
ByteArrayOutputStream outputStream = null;
try {
//获取网络输入流
inputStream = new BufferedInputStream(imageDateStream);
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new PatchInputStream(inputStream), null, opts);
inSampleSize = computeSampleSize(opts, -1, ServerActivity.TOTAL_WIDTH * ServerActivity.TOTAL_HEIGHT );
} catch (OutOfMemoryError err) {
}finally {
System.out.println("getPictureUsedecodeStream----执行清理工作。。。。。");
if(outputStream != null) {
outputStream.flush();
outputStream.close();
}
if(inputStream != null) {
inputStream.close();
}
imageDateStream.close();
conn.disconnect();
System.out.println("getPictureUsedecodeStream-------断开连接。。。。。");
}
}
}
} catch (Exception e) {
}
System.out.println("图片inSampleSize===" + inSampleSize);
return inSampleSize;
}
***********************************************************************************************
/**
* 直接使用inputstream下载图片,提取像素的比例是固定值
*
*/
private void downloadPictureUsedecodeStream(int inSampleSize) {
try {
URL imageUrl = new URL( pictureURL );
HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection();
// 注意要设置超时,设置时间不要超过10秒,避免被android系统回收
conn.setConnectTimeout(10*1000*60);
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestProperty("Connection", "Keep-Alive");
conn.connect();
if (conn.getResponseCode() != 200) {
System.out.println("getPictureUsedecodeStream-----请求url失败: ");
throw new RuntimeException("downloadPictureUsedecodeStream--请求url失败");
}else {
int netIconSize = conn.getContentLength();
InputStream imageDateStream = conn.getInputStream();
long totalMemory = Runtime.getRuntime().maxMemory();
long usedMemery = Runtime.getRuntime().totalMemory();
if( netIconSize <= 0 || (totalMemory - usedMemery) < netIconSize) {
imageDateStream.close();
clearBitmap();
}else { //执行图片下载
BufferedInputStream inputStream = null;
ByteArrayOutputStream outputStream = null;
try {
//获取网络输入流
inputStream = new BufferedInputStream(imageDateStream);
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = false;
opts.inSampleSize = inSampleSize;
imageBitmap = BitmapFactory.decodeStream(new PatchInputStream(inputStream), null, opts);
if(null == imageBitmap ) {
System.out.println("-----------下载的图片对象---imageBitmap为null");
}
} catch (OutOfMemoryError err) {
err.printStackTrace();
displaytoast("图片下载失败OutOfMemoryError err!");
System.out.println("downloadPictureUsedecodeStream----出现 outofmemery异常,结束图片浏览器!");
clearBitmap();
finish();
}catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("downloadPictureUsedecodeStream----执行清理工作。。。。。");
if(outputStream != null) {
outputStream.flush();
outputStream.close();
}
if(inputStream != null) {
inputStream.close();
}
imageDateStream.close();
conn.disconnect();
System.out.println("downloadPictureUsedecodeStream-------断开连接。。。。。");
}
}
}
} catch (Exception e) {
System.out.println("从网络上获取图片失败!");
e.printStackTrace();
clearBitmap();
}
}
这样实现了 根据设备的屏幕来显示图片,并避免outofmemery错误,但是缺点是下载了两边图片【计算获取图片的 insamplesize 下载了一遍图片】,另一个缺点是不能下载bmp 格式的图片,bmp 格式只有使用 outputstream 循环读取图片的方法来下载图片了,代码如下:
把图片下载成 bitmap 图片的显示工作就简单了。
private void downloadPictureUseOutputStream() throws OutOfMemoryError {
try {
URL imageUrl = new URL( pictureURL );
HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection();
// 注意要设置超时,设置时间不要超过10秒,避免被android系统回收
conn.setConnectTimeout(10*1000*60);
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestProperty("Connection", "Keep-Alive");
conn.connect();
if (conn.getResponseCode() != 200) {
System.out.println("请求url失败: ");
throw new RuntimeException("请求url失败");
}else {
int netIconSize = conn.getContentLength();
System.out.println("图片大小:" + netIconSize);
InputStream imageDateStream = conn.getInputStream();
long totalMemory = Runtime.getRuntime().maxMemory();
long usedMemery = Runtime.getRuntime().totalMemory();
if( netIconSize <= 0 || (totalMemory - usedMemery) < netIconSize) {
imageDateStream.close();
clearBitmap();
}else { //执行图片下载
System.out.println("downloadPictureUseOutputStream--执行图片下载-------------");
BufferedInputStream inputStream = null;
ByteArrayOutputStream outputStream = null;
try {
int BUFFER_SIZE = 4096;
byte[] buf = new byte[BUFFER_SIZE];
int size = -1;
//获取网络输入流
inputStream = new BufferedInputStream(imageDateStream);
//保存文件
outputStream = new ByteArrayOutputStream();
System.out.println("--------------begin----------");
while ( (size = inputStream.read(buf)) != -1 ){
outputStream.write(buf, 0, size);
}
System.out.println("--------------------end-------");
byte byteArray[] = outputStream.toByteArray();
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opts);
opts.inSampleSize = computeSampleSize(opts, -1,
ServerActivity.TOTAL_WIDTH * ServerActivity.TOTAL_HEIGHT );;
opts.inJustDecodeBounds = false;
try {
imageBitmap = BitmapFactory.decodeByteArray(byteArray, 0,
byteArray.length, opts);
} catch (OutOfMemoryError err) {
err.printStackTrace();
}
} catch (OutOfMemoryError err) {
err.printStackTrace();
displaytoast("downloadPictureUseOutputStream---图片下载失败OutOfMemoryError err!");
System.out.println("downloadPictureUseOutputStream---出现 outofmemery异常,结束图片浏览器!");
clearBitmap();
throw new OutOfMemoryError();
}finally {
System.out.println("downloadPictureUseOutputStream---执行清理工作。。。。。");
if(outputStream != null) {
outputStream.flush();
outputStream.close();
}
if(inputStream != null) {
inputStream.close();
}
imageDateStream.close();
conn.disconnect();
System.out.println("downloadPictureUseOutputStream---断开连接。。。。。");
}
System.out.println("downloadPictureUseOutputStream---downloadPicture--图片下载成功--!");
}
}
} catch (Exception e) {
System.out.println("downloadPictureUseOutputStream---从网络上获取图片失败!");
e.printStackTrace();
clearBitmap();
}
}
*********************************************************************
该方法偶尔会爆出outofmemery ,加上try catch 捕获内存溢出,这个error 也是没得办法的办法了。