如何获取位图信息,然后从 internet-inputStream 解码位图?
Posted
技术标签:
【中文标题】如何获取位图信息,然后从 internet-inputStream 解码位图?【英文标题】:how to get bitmap information and then decode bitmap from internet-inputStream? 【发布时间】:2013-07-20 09:54:22 【问题描述】:背景
假设我有一个来自互联网的某个图像文件的 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...
编辑:
看来我的代码还不错,有点……
在某些网站(如this one和this one)上,即使重置后也无法解码图像文件。
如果你解码位图(并使用 inSampleSize),它可以很好地解码(只是需要很长时间)。
现在的问题是它为什么会发生,我该如何解决它。
【问题讨论】:
嘿@android 开发者我面临同样的错误......你是如何解决的?请分享您的代码...在此先感谢 @DhirenParmar 不。我没有修复它。 我在这里提出了一个新的建议:code.google.com/p/android/issues/detail?id=231550 【参考方案1】:我认为问题在于对具有大值的 mark() 的调用被对 mark(1024) 的调用覆盖。如文档中所述:
在 KITKAT 之前,如果 is.markSupported() 返回 true,将调用 is.mark(1024)。从 KITKAT 开始,情况不再如此。
如果正在执行大于此值的读取,这可能会导致 reset() 失败。
【讨论】:
没有在 kitkat 上测试它,但我现在不明白文档所说的内容。如果 is.mark(1024) 没有被调用,那是什么?无论如何,我报告的问题仍然不能像我写的那样处理。现在的问题是:你如何正确地做到这一点? 这是decodeStream的文档。这意味着它在内部调用 is.mark(1024) ,覆盖您的调用。如何正确执行:在不调用 is.mark(1024) 的情况下重新实现 decodeStream。否则,您需要生成两次输入流。 我该怎么做?从源代码简单复制是不够的,因为其中很多是内部和私有的,我假设在这种情况下我还需要本机代码(C/C++)...... 我不确定。我注意到代码中有过多的封装,这使得重用它变得困难或不可能(这在 android 库和一般的 java 项目中都是一个常见的主题)。如果我通过这条路径,我将完全复制并粘贴整个 BitmapFactory.java 文件,删除有问题的部分。不过我没有尝试过这种方式,我去生成两次输入流。 我认为最好将文件下载到缓存然后从中读取,而不是连接到服务器两次。关于封装,你是对的。我不知道他们为什么让它如此“安全”。它只是位图......【参考方案2】:(这是针对同一问题的解决方案,但是从磁盘读取时。起初我没有意识到您的问题是专门来自网络流的。)
一般来说,标记和重置的问题是BitmapFactory.decodeStream()
有时会重置你的标记。因此,为了进行实际读取而进行的重置被破坏了。
但是 BufferedInputStream 存在第二个问题:它会导致整个图像在您实际读取它的位置旁边的内存中缓冲。根据您的用例,这确实会影响您的性能。 (大量分配意味着大量 GC)
这里有一个非常棒的解决方案: https://***.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;
这不会在读取期间分配任何额外的内存,并且即使标记已被清除也可以重置。
【讨论】:
我不熟悉“FilterInputStream”,但是由于它需要“FileInputStream”而不是简单的InputStream,这意味着我需要给它一个设备上存在的文件,不是吗?如果没有,您是否检查过它是否有效? 正确,在我的用例中,我获取网络流并将其保存到磁盘缓存中。然后我的实际图像加载器总是使用 FileInputStream 从缓存中读取。 FilterInputStream 只是一个包装器,它允许您在读取 InputStream 时对其进行转换。 FilterInputStream 可以接受任何 InputStream,但是由于使用 FileChannel 来执行标记和重置功能,因此 MarkableFileInputStream 以它为 FileInputStream 为前提。 如果您对缓存图像感兴趣,我建议您使用 DiskLruCache。 github.com/JakeWharton/DiskLruCache 它确实可以帮助您减少冗余的网络流量和加载时间,让您第一次获得数据。 问题是如何避免使用磁盘并使用 inputStream 完成所有操作,所以这不是答案(尽管它可能是正确的)。最后,我决定使用磁盘,因为它更容易,而且可能会更好。使用你所做的和简单地使用 BufferedInputStream 有什么区别? 我把它放在了答案中,但是 BufferedInputStream 在最坏的情况下会导致数据的整个第二个副本在您读取它时驻留在内存中。这就是 BufferedInputStream 通过将数据扔到中间 Buffer 中来提供它所提供的功能的方式。【参考方案3】:是否可以标记/重置流取决于流的实现。这些是可选操作,通常不受支持。您的选择是将流读入缓冲区,然后从该流中读取 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 倍。如果流确实支持标记/重置,它必须在它的实现中做同样的事情。
【讨论】:
不能 BufferedInputStream 只保存我将很快返回的字节吗?我不希望读取整个文件或创建新连接,因为它错过了整点 - 我已经有了 inputStream 并且它的数据也可用...... 它取决于用于构造BufferedInputStream
的流的实现。支持reset()
吗?
应该,不是吗?无论如何,假设我使用您的方法,是不是意味着我下载了文件的整个字节?
不,不应该。您必须了解的是,流是流,而不是缓冲区。它不会在读取数据后固有地缓冲或缓存数据。这就是流的全部意义所在。是的,这意味着您必须下载文件的整个字节并将其存储在内存中,或者您必须从源代码中读取 2 次。这些是您的选择。我很好奇你认为数据来自哪里。它要么在内存中,要么再次从源读取。这里没有魔法。
是否有一个 inputStream 类缓存字节,以便我可以读取一些字节,然后返回到某个位置?如果需要,我什至可以告诉它支持返回多少字节......【参考方案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;
【讨论】:
获取位图信息(宽度,高度,...)的部分在哪里?下采样部分在哪里?以上是关于如何获取位图信息,然后从 internet-inputStream 解码位图?的主要内容,如果未能解决你的问题,请参考以下文章
SeekBar:从 Drawable 获取空位图,直到将其设置为 ImageView 或如何从 9patch 获取结果位图