9

背景

假设我有一个来自某个图像文件的 Internet 的 inputStream。

我希望获取有关图像文件的信息,然后才能对其进行解码。

它可用于多种用途,例如下采样以及在显示图像之前预览信息。

问题

我试图通过用 BufferedInputStream 包装 inputStream 来标记和重置 inputStream ,但它没有用:

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 中获取 inputStream,我使用:

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 ......


编辑:

看来我的代码很好,有点......

在某些网站上(例如这个这个),即使重置后也无法解码图像文件。

如果您解码位图(并使用 inSampleSize),它可以很好地解码(只需要很长时间)。

现在的问题是它为什么会发生,我该如何解决它。

4

4 回答 4

0

我认为问题在于对具有大值的标记()的调用被对标记(1024)的调用覆盖。如文档中所述:

在 KITKAT 之前,如果 is.markSupported() 返回 true,将调用 is.mark(1024)。从 KITKAT 开始,情况不再如此。

如果正在执行大于此值的读取,这可能会导致 reset() 失败。

于 2014-01-18T22:26:07.023 回答
0

这是同一问题的解决方案,但是从磁盘读取时。起初我没有意识到您的问题专门来自网络流。

一般来说,标记和重置的问题是BitmapFactory.decodeStream() 有时会重置你的标记。因此,为了进行实际读取而进行的重置被破坏了。

但是 BufferedInputStream 存在第二个问题:它可能导致整个图像在您实际读取它的位置旁边的内存中缓冲。根据您的用例,这确实会影响您的性能。(大量分配意味着大量 GC

这里有一个非常棒的解决方案: https ://stackoverflow.com/a/18665678/1366

我针对这个特定的用例稍微修改了它以解决标记和重置问题:

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's 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;
        }
    }
}

这不会在读取期间分配任何额外的内存,并且即使标记已被清除也可以重置。

于 2014-06-03T18:52:58.487 回答
-1

是否可以标记/重置流取决于流的实现。这些是可选操作,通常不受支持。您的选择是将流读入缓冲区,然后从该流中读取 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());

这可能听起来很傻,但没有魔法。您要么将数据缓冲在内存中,要么从源中读取 2 倍。如果流确实支持标记/重置,它必须在它的实现中做同样的事情。

于 2013-07-21T16:37:33.060 回答
-1

这是一个对我总是有效的简单方法:)

 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;
}
于 2014-06-03T19:13:23.437 回答