Как получить bitmap-информацию, а затем декодировать растровое изображение из internet-inputStream?
фон
Предположим, у меня есть inputStream, который был создан из Интернета определенного файла изображения.
Я хочу получить информацию о файле изображения и только потом его декодировать.
он полезен для нескольких целей, таких как понижающая дискретизация, а также предварительный просмотр информации до отображения изображения.
проблема
Я попытался помечать & reset inputStream, обернув inputStream с помощью BufferedInputStream, но это не сработало:
inputStream=new BufferedInputStream(inputStream);
inputStream.mark(Integer.MAX_VALUE);
final BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeStream(inputStream,null,options);
//this works fine. i get the options filled just right.
inputStream.reset();
final Bitmap bitmap=BitmapFactory.decodeStream(inputStream,null,options);
//this returns null
для получения входного потока из URL-адреса, я использую:
public static InputStream getInputStreamFromInternet(final String urlString)
{
try
{
final URL url=new URL(urlString);
final HttpURLConnection urlConnection=(HttpURLConnection)url.openConnection();
final InputStream in=urlConnection.getInputStream();
return in;
}
catch(final Exception e)
{
e.printStackTrace();
}
return null;
}
вопрос
как я могу заставить обработчик кода маркировать сброс?
он отлично работает с ресурсами (на самом деле мне даже не нужно создавать новый BufferedInputStream для этого, чтобы он работал), но не с inputStream из Интернета...
EDIT:
Кажется, мой код в порядке, вроде...
на некоторых сайтах (например этот и этот), он не может декодировать файл изображения даже после перезапуска.
если вы декодируете растровое изображение (и используете inSampleSize), он может декодировать его в порядке (занимает много времени).
теперь возникает вопрос, почему это происходит, и как я могу его исправить.
Ответы
Ответ 1
Я считаю, что проблема заключается в том, что вызов mark() с большим значением перезаписывается вызовом метки (1024). Как описано в документации:
До появления KITKAT, если is.markSupported() возвращает true, будет вызываться is.mark(1024). Что касается KITKAT, это уже не так.
Это может привести к ошибке reset(), если чтение превышает размер этого значения.
Ответ 2
(Вот решение для той же проблемы, но при чтении с диска. Сначала я не понял, что ваш вопрос был конкретно из сетевого потока.)
Проблема с меткой и reset в общем случае заключается в том, что BitmapFactory.decodeStream()
иногда сбрасывает ваши метки. Таким образом, сброс, чтобы выполнить фактическое чтение, нарушен.
Но есть вторая проблема с BufferedInputStream: это может привести к тому, что все изображение будет буферизировано в памяти вдоль того места, где вы на самом деле его читаете. В зависимости от вашего варианта использования, это может действительно убить вашу производительность. (Множество значений означает много GC)
Здесь есть отличное решение:
fooobar.com/questions/22013/...
Я немного модифицировал этот конкретный прецедент для решения проблемы и проблемы reset:
public class MarkableFileInputStream extends FilterInputStream
{
private static final String TAG = MarkableFileInputStream.class.getSimpleName();
private FileChannel m_fileChannel;
private long m_mark = -1;
public MarkableFileInputStream( FileInputStream fis )
{
super( fis );
m_fileChannel = fis.getChannel();
}
@Override
public boolean markSupported()
{
return true;
}
@Override
public synchronized void mark( int readlimit )
{
try
{
m_mark = m_fileChannel.position();
}
catch( IOException ex )
{
Log.d( TAG, "Mark failed" );
m_mark = -1;
}
}
@Override
public synchronized void reset() throws IOException
{
// Reset to beginning if mark has not been called or was reset
// This is a little bit of custom functionality to solve problems
// specific to Android Bitmap decoding, and is slightly non-standard behavior
if( m_mark == -1 )
{
m_fileChannel.position( 0 );
}
else
{
m_fileChannel.position( m_mark );
m_mark = -1;
}
}
}
Это не будет выделять дополнительную память во время чтения и может быть reset, даже если метки были очищены.
Ответ 3
можно ли пометить / reset поток зависит от реализации потока. это необязательные операции и обычно не поддерживаются. ваши параметры состоят в том, чтобы прочитать поток в буфер, а затем прочитать из этого потока 2x или просто сделать сетевое соединение 2x.
проще всего записать в ByteArrayOutputStream
,
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int count;
byte[] b = new byte[...];
while ((count = input.read(b) != -1) [
baos.write(b, 0, count);
}
теперь либо напрямую используйте результат baos.toByteArray()
, либо создайте ByteArrayInputStream
и используйте его повторно, вызывая reset()
после его потребления каждый раз.
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
что может показаться глупым, но нет волшебства. вы либо буферизируете данные в памяти, либо читаете их 2x из источника. если поток поддерживает знак / reset, он должен будет сделать то же самое в реализации.
Ответ 4
Вот простой способ, который всегда работает для меня:)
private Bitmap downloadBitmap(String url) {
// initilize the default HTTP client object
final DefaultHttpClient client = new DefaultHttpClient();
//forming a HttoGet request
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
//check 200 OK for success
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w("ImageDownloader", "Error " + statusCode +
" while retrieving bitmap from " + url);
return null;
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
try {
// getting contents from the stream
inputStream = entity.getContent();
// decoding stream data back into image Bitmap that android understands
image = BitmapFactory.decodeStream(inputStream);
} finally {
if (inputStream != null) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (Exception e) {
// You Could provide a more explicit error message for IOException
getRequest.abort();
Log.e("ImageDownloader", "Something went wrong while" +
" retrieving bitmap from " + url + e.toString());
}
return image;
}