Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
沉迷打码的小凳子
glide
提交
526ae886
G
glide
项目概览
沉迷打码的小凳子
/
glide
与 Fork 源项目一致
从无法访问的项目Fork
通知
3
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
glide
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
526ae886
编写于
9月 09, 2014
作者:
S
Sam Judd
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Handle and throw exceptions in read in downsampler.
Fixes #126
上级
4dcb3c78
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
337 addition
and
31 deletion
+337
-31
library/src/androidTest/java/com/bumptech/glide/util/ExceptionCatchingInputStreamTest.java
...bumptech/glide/util/ExceptionCatchingInputStreamTest.java
+170
-0
library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java
.../com/bumptech/glide/load/resource/bitmap/Downsampler.java
+37
-26
library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java
...e/load/resource/bitmap/RecyclableBufferedInputStream.java
+0
-5
library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java
...com/bumptech/glide/util/ExceptionCatchingInputStream.java
+130
-0
未找到文件。
library/src/androidTest/java/com/bumptech/glide/util/ExceptionCatchingInputStreamTest.java
0 → 100644
浏览文件 @
526ae886
package
com.bumptech.glide.util
;
import
org.junit.After
;
import
org.junit.Before
;
import
org.junit.Test
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.net.SocketTimeoutException
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertNull
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
static
org
.
mockito
.
Matchers
.
any
;
import
static
org
.
mockito
.
Matchers
.
anyInt
;
import
static
org
.
mockito
.
Matchers
.
anyLong
;
import
static
org
.
mockito
.
Matchers
.
eq
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
verify
;
import
static
org
.
mockito
.
Mockito
.
when
;
public
class
ExceptionCatchingInputStreamTest
{
private
InputStream
wrapped
;
private
ExceptionCatchingInputStream
is
;
@Before
public
void
setUp
()
throws
Exception
{
wrapped
=
mock
(
InputStream
.
class
);
is
=
new
ExceptionCatchingInputStream
();
is
.
setInputStream
(
wrapped
);
}
@After
public
void
tearDown
()
{
ExceptionCatchingInputStream
.
clearQueue
();
}
@Test
public
void
testReturnsWrappedAvailable
()
throws
IOException
{
when
(
wrapped
.
available
()).
thenReturn
(
25
);
assertEquals
(
25
,
is
.
available
());
}
@Test
public
void
testCallsCloseOnWrapped
()
throws
IOException
{
is
.
close
();
verify
(
wrapped
).
close
();
}
@Test
public
void
testCallsMarkOnWrapped
()
{
int
toMark
=
50
;
is
.
mark
(
toMark
);
verify
(
wrapped
).
mark
(
eq
(
toMark
));
}
@Test
public
void
testReturnsWrappedMarkSupported
()
{
when
(
wrapped
.
markSupported
()).
thenReturn
(
true
);
assertTrue
(
is
.
markSupported
());
}
@Test
public
void
testCallsReadByteArrayOnWrapped
()
throws
IOException
{
byte
[]
buffer
=
new
byte
[
100
];
when
(
wrapped
.
read
(
eq
(
buffer
))).
thenReturn
(
buffer
.
length
);
assertEquals
(
buffer
.
length
,
is
.
read
(
buffer
));
}
@Test
public
void
testCallsReadArrayWithOffsetAndCountOnWrapped
()
throws
IOException
{
int
offset
=
5
;
int
count
=
100
;
byte
[]
buffer
=
new
byte
[
105
];
when
(
wrapped
.
read
(
eq
(
buffer
),
eq
(
offset
),
eq
(
count
))).
thenReturn
(
count
);
assertEquals
(
count
,
is
.
read
(
buffer
,
offset
,
count
));
}
@Test
public
void
testCallsReadOnWrapped
()
throws
IOException
{
when
(
wrapped
.
read
()).
thenReturn
(
1
);
assertEquals
(
1
,
is
.
read
());
}
@Test
public
void
testCallsResetOnWrapped
()
throws
IOException
{
is
.
reset
();
verify
(
wrapped
).
reset
();
}
@Test
public
void
testCallsSkipOnWrapped
()
throws
IOException
{
long
toSkip
=
67
;
long
expected
=
55
;
when
(
wrapped
.
skip
(
eq
(
toSkip
))).
thenReturn
(
expected
);
assertEquals
(
expected
,
is
.
skip
(
toSkip
));
}
@Test
public
void
testCatchesExceptionOnRead
()
throws
IOException
{
IOException
expected
=
new
SocketTimeoutException
();
when
(
wrapped
.
read
()).
thenThrow
(
expected
);
int
read
=
is
.
read
();
assertEquals
(-
1
,
read
);
assertEquals
(
expected
,
is
.
getException
());
}
@Test
public
void
testCatchesExceptionOnReadBuffer
()
throws
IOException
{
IOException
exception
=
new
SocketTimeoutException
();
when
(
wrapped
.
read
(
any
(
byte
[].
class
))).
thenThrow
(
exception
);
int
read
=
is
.
read
(
new
byte
[
0
]);
assertEquals
(-
1
,
read
);
assertEquals
(
exception
,
is
.
getException
());
}
@Test
public
void
testCatchesExceptionOnReadBufferWithOffsetAndCount
()
throws
IOException
{
IOException
exception
=
new
SocketTimeoutException
();
when
(
wrapped
.
read
(
any
(
byte
[].
class
),
anyInt
(),
anyInt
())).
thenThrow
(
exception
);
int
read
=
is
.
read
(
new
byte
[
0
],
10
,
100
);
assertEquals
(-
1
,
read
);
assertEquals
(
exception
,
is
.
getException
());
}
@Test
public
void
testCatchesExceptionOnSkip
()
throws
IOException
{
IOException
exception
=
new
SocketTimeoutException
();
when
(
wrapped
.
skip
(
anyLong
())).
thenThrow
(
exception
);
long
skipped
=
is
.
skip
(
100
);
assertEquals
(
0
,
skipped
);
assertEquals
(
exception
,
is
.
getException
());
}
@Test
public
void
testExceptionIsNotSetInitially
()
{
assertNull
(
is
.
getException
());
}
@SuppressWarnings
(
"ResultOfMethodCallIgnored"
)
@Test
public
void
testResetsExceptionToNullOnRelease
()
throws
IOException
{
IOException
exception
=
new
SocketTimeoutException
();
when
(
wrapped
.
read
()).
thenThrow
(
exception
);
is
.
read
();
is
.
release
();
assertNull
(
is
.
getException
());
}
@Test
public
void
testCanReleaseAnObtainFromPool
()
{
is
.
release
();
InputStream
fromPool
=
ExceptionCatchingInputStream
.
obtain
(
wrapped
);
assertEquals
(
is
,
fromPool
);
}
@Test
public
void
testCanObtainNewStreamFromPool
()
throws
IOException
{
InputStream
fromPool
=
ExceptionCatchingInputStream
.
obtain
(
wrapped
);
when
(
wrapped
.
read
()).
thenReturn
(
1
);
int
read
=
fromPool
.
read
();
assertEquals
(
1
,
read
);
}
}
\ No newline at end of file
library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java
浏览文件 @
526ae886
...
...
@@ -9,6 +9,7 @@ import android.util.Log;
import
com.bumptech.glide.load.DecodeFormat
;
import
com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
;
import
com.bumptech.glide.util.ByteArrayPool
;
import
com.bumptech.glide.util.ExceptionCatchingInputStream
;
import
com.bumptech.glide.util.Util
;
import
java.io.IOException
;
...
...
@@ -140,19 +141,21 @@ public abstract class Downsampler implements BitmapDecoder<InputStream> {
final
byte
[]
bytesForOptions
=
byteArrayPool
.
getBytes
();
final
byte
[]
bytesForStream
=
byteArrayPool
.
getBytes
();
final
BitmapFactory
.
Options
options
=
getDefaultOptions
();
final
RecyclableBufferedInputStream
bis
=
new
RecyclableBufferedInputStream
(
is
,
bytesForStream
);
// TODO(#126): when the framework handles exceptions better, consider removing.
final
ExceptionCatchingInputStream
stream
=
ExceptionCatchingInputStream
.
obtain
(
new
RecyclableBufferedInputStream
(
is
,
bytesForStream
));
try
{
bis
.
mark
(
MARK_POSITION
);
stream
.
mark
(
MARK_POSITION
);
int
orientation
=
0
;
try
{
orientation
=
new
ImageHeaderParser
(
bis
).
getOrientation
();
orientation
=
new
ImageHeaderParser
(
stream
).
getOrientation
();
}
catch
(
IOException
e
)
{
if
(
Log
.
isLoggable
(
TAG
,
Log
.
WARN
))
{
Log
.
w
(
TAG
,
"Cannot determine the image orientation from header"
,
e
);
}
}
finally
{
try
{
bis
.
reset
();
stream
.
reset
();
}
catch
(
IOException
e
)
{
if
(
Log
.
isLoggable
(
TAG
,
Log
.
WARN
))
{
Log
.
w
(
TAG
,
"Cannot reset the input stream"
,
e
);
...
...
@@ -162,7 +165,7 @@ public abstract class Downsampler implements BitmapDecoder<InputStream> {
options
.
inTempStorage
=
bytesForOptions
;
final
int
[]
inDimens
=
getDimensions
(
bis
,
options
);
final
int
[]
inDimens
=
getDimensions
(
stream
,
options
);
final
int
inWidth
=
inDimens
[
0
];
final
int
inHeight
=
inDimens
[
1
];
...
...
@@ -177,7 +180,15 @@ public abstract class Downsampler implements BitmapDecoder<InputStream> {
}
final
Bitmap
downsampled
=
downsampleWithSize
(
bis
,
options
,
pool
,
inWidth
,
inHeight
,
sampleSize
,
decodeFormat
);
downsampleWithSize
(
stream
,
options
,
pool
,
inWidth
,
inHeight
,
sampleSize
,
decodeFormat
);
// BitmapDecoder swallows exceptions during decodes and in some cases when inBitmap is non null, may catch
// and log a stack trace but still return a non null bitmap. To avoid displaying partially decoded bitmaps,
// we catch exceptions reading from the stream in our ExceptionCatchingInputStream and throw them here.
final
Exception
streamException
=
stream
.
getException
();
if
(
streamException
!=
null
)
{
throw
new
RuntimeException
(
streamException
);
}
Bitmap
rotated
=
null
;
if
(
downsampled
!=
null
)
{
...
...
@@ -192,33 +203,34 @@ public abstract class Downsampler implements BitmapDecoder<InputStream> {
}
finally
{
byteArrayPool
.
releaseBytes
(
bytesForOptions
);
byteArrayPool
.
releaseBytes
(
bytesForStream
);
stream
.
release
();
releaseOptions
(
options
);
}
}
protected
Bitmap
downsampleWithSize
(
RecyclableBufferedInputStream
b
is
,
BitmapFactory
.
Options
options
,
protected
Bitmap
downsampleWithSize
(
InputStream
is
,
BitmapFactory
.
Options
options
,
BitmapPool
pool
,
int
inWidth
,
int
inHeight
,
int
sampleSize
,
DecodeFormat
decodeFormat
)
{
// Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.
Bitmap
.
Config
config
=
getConfig
(
b
is
,
decodeFormat
);
Bitmap
.
Config
config
=
getConfig
(
is
,
decodeFormat
);
options
.
inSampleSize
=
sampleSize
;
options
.
inPreferredConfig
=
config
;
if
(
options
.
inSampleSize
==
1
||
Build
.
VERSION_CODES
.
KITKAT
<=
Build
.
VERSION
.
SDK_INT
)
{
if
(
shouldUsePool
(
b
is
))
{
if
(
shouldUsePool
(
is
))
{
setInBitmap
(
options
,
pool
.
get
(
inWidth
,
inHeight
,
config
));
}
}
return
decodeStream
(
b
is
,
options
);
return
decodeStream
(
is
,
options
);
}
private
static
boolean
shouldUsePool
(
RecyclableBufferedInputStream
b
is
)
{
private
static
boolean
shouldUsePool
(
InputStream
is
)
{
// On KitKat+, any bitmap can be used to decode any other bitmap.
if
(
Build
.
VERSION_CODES
.
KITKAT
<=
Build
.
VERSION
.
SDK_INT
)
{
return
true
;
}
b
is
.
mark
(
1024
);
is
.
mark
(
1024
);
try
{
final
ImageHeaderParser
.
ImageType
type
=
new
ImageHeaderParser
(
b
is
).
getType
();
final
ImageHeaderParser
.
ImageType
type
=
new
ImageHeaderParser
(
is
).
getType
();
// cannot reuse bitmaps when decoding images that are not PNG or JPG.
// look at : https://groups.google.com/forum/#!msg/android-developers/Mp0MFVFi1Fo/e8ZQ9FGdWdEJ
return
TYPES_THAT_USE_POOL
.
contains
(
type
);
...
...
@@ -228,7 +240,7 @@ public abstract class Downsampler implements BitmapDecoder<InputStream> {
}
}
finally
{
try
{
b
is
.
reset
();
is
.
reset
();
}
catch
(
IOException
e
)
{
if
(
Log
.
isLoggable
(
TAG
,
Log
.
WARN
))
{
Log
.
w
(
TAG
,
"Cannot reset the input stream"
,
e
);
...
...
@@ -238,23 +250,23 @@ public abstract class Downsampler implements BitmapDecoder<InputStream> {
return
false
;
}
private
static
Bitmap
.
Config
getConfig
(
RecyclableBufferedInputStream
b
is
,
DecodeFormat
format
)
{
private
static
Bitmap
.
Config
getConfig
(
InputStream
is
,
DecodeFormat
format
)
{
if
(
format
==
DecodeFormat
.
ALWAYS_ARGB_8888
)
{
return
Bitmap
.
Config
.
ARGB_8888
;
}
boolean
hasAlpha
=
false
;
// We probably only need 25, but this is safer (particularly since the buffer size is > 1024).
b
is
.
mark
(
1024
);
is
.
mark
(
1024
);
try
{
hasAlpha
=
new
ImageHeaderParser
(
b
is
).
hasAlpha
();
hasAlpha
=
new
ImageHeaderParser
(
is
).
hasAlpha
();
}
catch
(
IOException
e
)
{
if
(
Log
.
isLoggable
(
TAG
,
Log
.
WARN
))
{
Log
.
w
(
TAG
,
"Cannot determine whether the image has alpha or not from header for format "
+
format
,
e
);
}
}
finally
{
try
{
b
is
.
reset
();
is
.
reset
();
}
catch
(
IOException
e
)
{
if
(
Log
.
isLoggable
(
TAG
,
Log
.
WARN
))
{
Log
.
w
(
TAG
,
"Cannot reset the input stream"
,
e
);
...
...
@@ -283,36 +295,35 @@ public abstract class Downsampler implements BitmapDecoder<InputStream> {
/**
* A method for getting the dimensions of an image from the given InputStream.
*
* @param
b
is The InputStream representing the image.
* @param is The InputStream representing the image.
* @param options The options to pass to
* {@link BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect,
* android.graphics.BitmapFactory.Options)}.
* @return an array containing the dimensions of the image in the form {width, height}.
*/
public
int
[]
getDimensions
(
RecyclableBufferedInputStream
b
is
,
BitmapFactory
.
Options
options
)
{
public
int
[]
getDimensions
(
InputStream
is
,
BitmapFactory
.
Options
options
)
{
options
.
inJustDecodeBounds
=
true
;
decodeStream
(
b
is
,
options
);
decodeStream
(
is
,
options
);
options
.
inJustDecodeBounds
=
false
;
return
new
int
[]
{
options
.
outWidth
,
options
.
outHeight
};
}
private
static
Bitmap
decodeStream
(
RecyclableBufferedInputStream
b
is
,
BitmapFactory
.
Options
options
)
{
private
static
Bitmap
decodeStream
(
InputStream
is
,
BitmapFactory
.
Options
options
)
{
if
(
options
.
inJustDecodeBounds
)
{
// This is large, but jpeg headers are not size bounded so we need something large enough to minimize
// the possibility of not being able to fit enough of the header in the buffer to get the image size so
// that we don't fail to load images. The BufferedInputStream will create a new buffer of 2x the
// original size each time we use up the buffer space without passing the mark so this is a maximum
// bound on the buffer size, not a default. Most of the time we won't go past our pre-allocated 16kb.
b
is
.
mark
(
MARK_POSITION
);
is
.
mark
(
MARK_POSITION
);
}
final
Bitmap
result
=
BitmapFactory
.
decodeStream
(
b
is
,
null
,
options
);
final
Bitmap
result
=
BitmapFactory
.
decodeStream
(
is
,
null
,
options
);
try
{
if
(
options
.
inJustDecodeBounds
)
{
bis
.
reset
();
bis
.
clearMark
();
is
.
reset
();
}
}
catch
(
IOException
e
)
{
if
(
Log
.
isLoggable
(
TAG
,
Log
.
ERROR
))
{
...
...
library/src/main/java/com/bumptech/glide/load/resource/bitmap/RecyclableBufferedInputStream.java
浏览文件 @
526ae886
...
...
@@ -187,11 +187,6 @@ public class RecyclableBufferedInputStream extends FilterInputStream {
markpos
=
pos
;
}
public
synchronized
void
clearMark
()
{
markpos
=
-
1
;
marklimit
=
0
;
}
/**
* Indicates whether {@code BufferedInputStream} supports the {@link #mark(int)}
* and {@link #reset()} methods.
...
...
library/src/main/java/com/bumptech/glide/util/ExceptionCatchingInputStream.java
0 → 100644
浏览文件 @
526ae886
package
com.bumptech.glide.util
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.util.Queue
;
/**
* An {@link java.io.InputStream} that catches {@link java.io.IOException}s during read and skip calls and stores them
* so they can later be handled or thrown. This class is a workaround for a framework issue where exceptions during
* reads while decoding bitmaps in {@link android.graphics.BitmapFactory} can return partially decoded bitmaps.
*
* See https://github.com/bumptech/glide/issues/126.
*/
public
class
ExceptionCatchingInputStream
extends
InputStream
{
private
static
final
Queue
<
ExceptionCatchingInputStream
>
QUEUE
=
Util
.
createQueue
(
0
);
public
static
ExceptionCatchingInputStream
obtain
(
InputStream
toWrap
)
{
ExceptionCatchingInputStream
result
;
synchronized
(
QUEUE
)
{
result
=
QUEUE
.
poll
();
}
if
(
result
==
null
)
{
result
=
new
ExceptionCatchingInputStream
();
}
result
.
setInputStream
(
toWrap
);
return
result
;
}
// Exposed for testing.
static
void
clearQueue
()
{
}
private
InputStream
wrapped
;
private
IOException
exception
;
ExceptionCatchingInputStream
()
{
// Do nothing.
}
void
setInputStream
(
InputStream
toWrap
)
{
wrapped
=
toWrap
;
}
@Override
public
int
available
()
throws
IOException
{
return
wrapped
.
available
();
}
@Override
public
void
close
()
throws
IOException
{
wrapped
.
close
();
}
@Override
public
void
mark
(
int
readlimit
)
{
wrapped
.
mark
(
readlimit
);
}
@Override
public
boolean
markSupported
()
{
return
wrapped
.
markSupported
();
}
@Override
public
int
read
(
byte
[]
buffer
)
throws
IOException
{
int
read
;
try
{
read
=
wrapped
.
read
(
buffer
);
}
catch
(
IOException
e
)
{
exception
=
e
;
read
=
-
1
;
}
return
read
;
}
@Override
public
int
read
(
byte
[]
buffer
,
int
byteOffset
,
int
byteCount
)
throws
IOException
{
int
read
;
try
{
read
=
wrapped
.
read
(
buffer
,
byteOffset
,
byteCount
);
}
catch
(
IOException
e
)
{
exception
=
e
;
read
=
-
1
;
}
return
read
;
}
@Override
public
synchronized
void
reset
()
throws
IOException
{
wrapped
.
reset
();
}
@Override
public
long
skip
(
long
byteCount
)
throws
IOException
{
long
skipped
;
try
{
skipped
=
wrapped
.
skip
(
byteCount
);
}
catch
(
IOException
e
)
{
exception
=
e
;
skipped
=
0
;
}
return
skipped
;
}
@Override
public
int
read
()
throws
IOException
{
int
result
;
try
{
result
=
wrapped
.
read
();
}
catch
(
IOException
e
)
{
exception
=
e
;
result
=
-
1
;
}
return
result
;
}
public
IOException
getException
()
{
return
exception
;
}
public
void
release
()
{
exception
=
null
;
wrapped
=
null
;
synchronized
(
QUEUE
)
{
QUEUE
.
offer
(
this
);
}
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录