如何获取位图信息,然后从internet-inputStream解码位图?

时间:2013-07-21 16:29:33

标签: android bitmap inputstream reset

背景

假设我有一个源自某个图像文件的互联网的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

用于从网址中获取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 ...


编辑:

似乎我的代码很好,有点......

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

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

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

4 个答案:

答案 0 :(得分:0)

我认为问题是调用mark(1024)会覆盖带有大值的mark()。如文档中所述:

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

如果读取的值大于此值,则可能导致reset()失败。

答案 1 :(得分:0)

这是针对同一问题的解决方案,但是从磁盘读取时。我一开始并没有意识到你的问题是专门来自网络流。

mark& amp;的问题一般情况下重置是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;
        }
    }
}

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

答案 2 :(得分:-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次。如果流确实支持标记/重置,它必须在它的实现中做同样的事情。

答案 3 :(得分:-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;
}