Как получить 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;
}