压缩前先搞明白原理:Bitmap占用的内存大小:java
bytes = 原始图片宽*(options.inTargetDensity/options.inDensity)*原始图片长*(options.inTargetDensity/options.inDensity)*每一个像素点位数
复制代码
inTargetDensity指的是当前手机的密度,inDensity是图片的所在drawable目录生成的密度bash
使用sample采样率来对Bitmap进行压缩到指定的宽和高(原理不在赘述) 方法相信你们都熟悉,网上一搜都大致相似,下面是具体的方法框架
// 使用decodeRes方法对资源进行转换,使用InJustDecodeBounds属性置为true,先获图
//片高,native层先不返回Bitmap
//进行计算sampleSize,而后设为false,再次decode返回Bitmap
public Bitmap compressBitmapIntoThumbnailPic(Context context, int res) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap1 = BitmapFactory.decodeResource(context.getResources(), res, options);
Log.d("tag", "first bitmap == " + bitmap1);
int sampleBitmap = calculateInSampleSize(options, 40, 40);
options.inSampleSize = sampleBitmap;
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), res, options);
Log.d("tag", "final == " + bitmap.getByteCount() +
" target real density " + options.inTargetDensity + " folder density " + options.inDensity);
return bitmap;
}
复制代码
// 对要求的宽高和当前图片宽高对比,取一个最小的比例做为sampleSize
private int calculateInSampleSize(BitmapFactory.Options options, int requireW, int requereH) {
int sampleSize = 1;
int outHeight = options.outHeight;
int outWidth = options.outWidth;
int rationHeight = Math.round(outHeight / requereH);
int rationWidth = Math.round(outWidth / requireW);
if (rationHeight > 1 || rationWidth > 1) {
sampleSize = rationHeight > rationWidth ? rationWidth : rationHeight;
}
Log.d("tag", "outHeight = " + outHeight + "outWidth = " + outWidth + " -------- " + sampleSize);
return sampleSize;
}
复制代码
执行上述的代码能正常的将一个大图片进行压缩展现,而后本身看了下源码,发现BitmapFactory最终调用的都是decodeStream(...) 这个方法来处理的,因而想试一下直接处理流来对图片压缩也应该能够,代码见下:ide
/**
* 用流的形式生成Bitmap
**/
public Bitmap compressBitmapIntoThumbnailPic(InputStream is) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// 最终返回的Bitmap
Bitmap finalBitmap = null;
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){
//设置inJustDecodeBounds在native decode时候只返回尺寸等,Bitmap为空
// 或者将inputStream -> BufferedInputStream 保证某些状况reset不支持
BitmapFactory.decodeStream(is, null, options);
int sampleBitmap = calculateInSampleSize(options, 80, 80);
options.inSampleSize = sampleBitmap;
options.inJustDecodeBounds = false;
try {
is.reset();
} catch (IOException e) {
e.printStackTrace();
}
finalBitmap = BitmapFactory.decodeStream(is, null, options);
}else {// 4.4包含之后就没有这个is.mark(1024)的大小限制问题了。不会出现OOM
if (is.markSupported()) {
try {
BitmapFactory.decodeStream(is,null,options);
int sampleBitmap = calculateInSampleSize(options, 40, 40);
options.inSampleSize = sampleBitmap;
options.inJustDecodeBounds = false;
is.reset();
finalBitmap = BitmapFactory.decodeStream(is, null, options);
Log.d("tag", "final == " + finalBitmap.getByteCount() +
" target real density " + options.inTargetDensity + " folder density " + options.inDensity);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return finalBitmap;
}
复制代码
这里对版本作了一个判断,只是为了调式方便。先说下缘由,和上面方法相比而言,多了一个 is.reset , 不加的话直接就返回null 了,即便咱们已近设置了inJustDecodeBounds为false, 这就奇怪了,这时候仍是看下源码来找答案吧,圈红的部分意思是 : 咱们在options里设置要求只返回大小或者流不能被decode的时候就返回null,那就说明是流不能被decode了,而后想到了流被使用事后的位置是会改变的,咱们第一次decode的时候流执行了mark方法,下次再执行这个流的时候就必需要使用reset,返回上次标记的内容,这样才能正常使用流。有点恍然大悟~,被本身感动了。 ui
/**
* Marks the current position in this input stream. A subsequent call to
* the <code>reset</code> method repositions this stream at the last marked
* position so that subsequent reads re-read the same bytes.
*
* <p> The <code>readlimit</code> arguments tells this input stream to
* allow that many bytes to be read before the mark position gets
* invalidated.
*
* <p> The general contract of <code>mark</code> is that, if the method
* <code>markSupported</code> returns <code>true</code>, the stream somehow
* remembers all the bytes read after the call to <code>mark</code> and
* stands ready to supply those same bytes again if and whenever the method
* <code>reset</code> is called. However, the stream is not required to
* remember any data at all if more than <code>readlimit</code> bytes are
* read from the stream before <code>reset</code> is called.
*
* <p> Marking a closed stream should not have any effect on the stream.
*
* <p> The <code>mark</code> method of <code>InputStream</code> does
* nothing.
*
* @param readlimit the maximum limit of bytes that can be read before
* the mark position becomes invalid.
* @see java.io.InputStream#reset()
*/
复制代码
在reset的时候容易发生OOM,缘由是在流在建立时没有调用mark的话,mark记录的大小确定是小于总体的,这时候你去调用reset请求的大于mark记录的大小,就会OOM 。参考此条解决方式this
/**
* Repositions this stream to the position at the time the
* <code>mark</code> method was last called on this input stream.
*
* <p> The general contract of <code>reset</code> is:
*
* <ul>
* <li> If the method <code>markSupported</code> returns
* <code>true</code>, then:
*
* <ul><li> If the method <code>mark</code> has not been called since
* the stream was created, or the number of bytes read from the stream
* since <code>mark</code> was last called is larger than the argument
* to <code>mark</code> at that last call, then an
* <code>IOException</code> might be thrown.
*
* <li> If such an <code>IOException</code> is not thrown, then the
* stream is reset to a state such that all the bytes read since the
* most recent call to <code>mark</code> (or since the start of the
* file, if <code>mark</code> has not been called) will be resupplied
* to subsequent callers of the <code>read</code> method, followed by
* any bytes that otherwise would have been the next input data as of
* the time of the call to <code>reset</code>. </ul>
*
* <li> If the method <code>markSupported</code> returns
* <code>false</code>, then:
*
* <ul><li> The call to <code>reset</code> may throw an
* <code>IOException</code>.
*
* <li> If an <code>IOException</code> is not thrown, then the stream
* is reset to a fixed state that depends on the particular type of the
* input stream and how it was created. The bytes that will be supplied
* to subsequent callers of the <code>read</code> method depend on the
* particular type of the input stream. </ul></ul>
*
* <p>The method <code>reset</code> for class <code>InputStream</code>
* does nothing except throw an <code>IOException</code>.
*
* @exception IOException if this stream has not been marked or if the
* mark has been invalidated.
* @see java.io.InputStream#mark(int)
* @see java.io.IOException
*/
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
复制代码
上面代码中有区分4.4版本的判断,这里解释下缘由:以下图所示,在4.3源码中默认mark的阀值大小为1024K,这样的话对大图片很容易引发OOM ; 在4.4(包含)以后,去掉了这个case,没有具体的1024限制了。这里只是对原理作了简单的研究,实际上咱们仍是使用Glide之类的框架去作加载图片的工做。可是对原理有些了解仍是不错的。 spa
扩展 这里还有俩点疑问,3d
有疑问仍是要去看源码啊,知其然也要知其因此然·,水平有限,错误之处请帮忙斧正code