当设置选项时,BitmapFactory.decodeStream返回null

96

我使用BitmapFactory.decodeStream(inputStream)时遇到了问题。使用不带选项的方式会返回图像,但是在使用选项(例如.decodeStream(inputStream, null, options))时,它却从未返回位图。

我想要做的是在实际加载之前对位图进行降采样以节省内存。我已经阅读了一些好的指南,但是没有一个使用.decodeStream

完全正常

URL url = new URL(sUrl);
HttpURLConnection connection  = (HttpURLConnection) url.openConnection();

InputStream is = connection.getInputStream();
Bitmap img = BitmapFactory.decodeStream(is, null, options);

无法工作

InputStream is = connection.getInputStream();
Bitmap img = BitmapFactory.decodeStream(is, null, options);

InputStream is = connection.getInputStream();

Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;

BitmapFactory.decodeStream(is, null, options);

Boolean scaleByHeight = Math.abs(options.outHeight - TARGET_HEIGHT) >= Math.abs(options.outWidth - TARGET_WIDTH);

if (options.outHeight * options.outWidth * 2 >= 200*100*2){
    // Load, scaling to smallest power of 2 that'll get it <= desired dimensions
    double sampleSize = scaleByHeight
    ? options.outHeight / TARGET_HEIGHT
    : options.outWidth / TARGET_WIDTH;
    options.inSampleSize =
        (int)Math.pow(2d, Math.floor(
        Math.log(sampleSize)/Math.log(2d)));
}

// Do the actual decoding
options.inJustDecodeBounds = false;
Bitmap img = BitmapFactory.decodeStream(is, null, options);

1
你的 System.out.println("Samplesize: " ...) 语句输出是什么?这是否表明 options.inSampleSize 是可接受的值? - Steve Haley
是的,它每次都返回一个可接受的值。 - Robert Foss
由于该语句是调试用的,因此已将其删除。 - Robert Foss
1
感谢您发布解决方案,但还有一件事情需要做。这个问题仍然出现在“未解决的问题”列表中,因为您没有将某个答复标记为“已接受”。您可以通过点击答案旁边的勾选图标来完成此操作。如果您认为Samuh的答案帮助您找到了解决方案,您可以接受他的答案,或者您可以发布自己的答案并接受它。 (通常您会将解决方案放在您的答案中,但由于您已经通过编辑问题包含了该解决方案,因此您可以只引用问题即可。) - Steve Haley
感谢帮助新用户融入社区 :) - Robert Foss
4个回答

115

问题在于,一旦你使用HttpUrlConnection的InputStream获取图像元数据后,就无法倒回并再次使用相同的InputStream。

因此,您必须为实际的图像采样创建一个新的InputStream。

  Options options = new BitmapFactory.Options();
  options.inJustDecodeBounds = true;

  BitmapFactory.decodeStream(is, null, options);

  Boolean scaleByHeight = Math.abs(options.outHeight - TARGET_HEIGHT) >= Math.abs(options.outWidth - TARGET_WIDTH);

  if(options.outHeight * options.outWidth * 2 >= 200*200*2){
         // Load, scaling to smallest power of 2 that'll get it <= desired dimensions
        double sampleSize = scaleByHeight
              ? options.outHeight / TARGET_HEIGHT
              : options.outWidth / TARGET_WIDTH;
        options.inSampleSize = 
              (int)Math.pow(2d, Math.floor(
              Math.log(sampleSize)/Math.log(2d)));
     }

        // Do the actual decoding
        options.inJustDecodeBounds = false;

        is.close();
        is = getHTTPConnectionInputStream(sUrl);
        Bitmap img = BitmapFactory.decodeStream(is, null, options);
        is.close();

17
这是否意味着需要下载两次图像?一次获取大小,一次获取像素数据? - user123321
1
@Robert,你可能应该解释一下这种特定的行为,以便其他用户能够清楚地了解。 - Muhammad Babar
1
我一直在想为什么它不能使用相同的输入流,谢谢你的简要解释。 - kabuto178
1
你不必重新创造它,只需重置它即可达到目的。 - Shashank Tomar
7
我必须说,安卓的Bitmap类很糟糕。使用起来非常混乱和令人沮丧。 - Neon Warge
显示剩余2条评论

31

尝试使用BufferedInputStream包装InputStream。

InputStream is = new BufferedInputStream(conn.getInputStream());
is.mark(is.available());
// Do the bound decoding
// inJustDecodeBounds =true
is.reset();  
// Do the actual decoding

2
它对你总是有效吗?由于某些原因,我在使用这种方法时在一些非常特定的情况下得到了null。我在这里写了一篇文章:https://dev59.com/rHTYa4cB1Zd3GeqPx7fW - android developer
1
它确实有效,所以我点了个赞。但是 is.available() 的文档中有警告,它只应用于检查流是否为空,而不应用于计算大小,因为这是不可靠的。 - Abhishek Chauhan
1
被踩了,但是问题中的inputstream连接是一个HTTP连接,reset()不起作用... - Johnny Wu

3

您可以将InputStream转换为字节数组,并使用decodeByteArray()方法。例如:

public static Bitmap decodeSampledBitmapFromStream(InputStream inputStream, int reqWidth, int reqHeight) {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try {
        int n;
        byte[] buffer = new byte[1024];
        while ((n = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, n);
        }
        return decodeSampledBitmapFromByteArray(outputStream.toByteArray(), reqWidth, reqHeight);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}

public static Bitmap decodeSampledBitmapFromByteArray(byte[] data, int reqWidth, int reqHeight) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeByteArray(data, 0, data.length, options);
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}

private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int
        reqHeight) {
    int width = options.outWidth;
    int height = options.outHeight;
    int inSampleSize = 1;
    if (width > reqWidth || height > reqHeight) {
        int halfWidth = width / 2;
        int halfHeight = height / 2;
        while (halfWidth / inSampleSize >= reqWidth && halfHeight / inSampleSize >= reqHeight) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

3
我认为问题出在“calculate-scale-factor”逻辑上,因为除此之外的代码在我看来都是正确的(当然假设inputstream不为空)。
如果您能将所有大小计算逻辑从该例程中提取出来并独立测试该方法,那将更好。可以称其为calculateScaleFactor()或其他名称。
类似于下面的内容:
// Get the stream 
InputStream is = mUrl.openStream();

// get the Image bounds
BitmapFactory.Options options=new BitmapFactory.Options(); 
options.inJustDecodeBounds = true;

bitmap = BitmapFactory.decodeStream(is,null,options);

//get actual width x height of the image and calculate the scale factor
options.inSampleSize = getScaleFactor(options.outWidth,options.outHeight,
                view.getWidth(),view.getHeight());

options.inJustDecodeBounds = false;
bitmap=BitmapFactory.decodeStream(mUrl.openStream(),null,options);

并且独立测试getScaleFactor(...)。

如果还没有这样做,将整个代码用try..catch{}块包围也会有所帮助。


非常感谢您的回答!我尝试设置一个最终的int值,例如“options.inSampleSize = 2”。 但结果仍然出现相同的问题。Logcat读取“SkImageDecoder :: Factory返回null”,对于我尝试解码的每个图像。如果在try / catch块中运行代码将无法帮助我,因为它不会抛出任何异常,对吧? 但是,如果无法创建img,则BitmapFactory.decodeStream确实会返回null,当我尝试使用sampleSize时它无法创建img。 - Robert Foss
使用BitmapFactory.decodeResource(this.getResources(), R.drawable.icon, options) == null)对重新采样的图像进行处理很好。第一个BitmapFactory.decodeStream与options.inJustDecodeBounds = true一起使用可以正常工作,并返回选项。但是,随后的BitmapFactory.decodeStream与options.inJustDecodeBounds = false一起使用每次都会失败。 - Robert Foss
我恐怕做不到这个......我很想知道可能出了什么问题,因为我正在使用类似的代码,而且它对我来说完全正常。 - Samuh
4
好的,我来翻译。问题已解决,出现在HTTP连接中。当你从HttpUrlConnection提供的输入流中读取一次后,就不能再次从中读取,必须重新连接以进行第二个decodeStream()操作。我的翻译清晰明了,并尽量保持原意,没有额外的内容。 - Robert Foss
顺便说一下,我刚刚编辑了这个问题,以反映我使用的是HttpUrlConnection。我应该在问题中发布那个。对此我很抱歉。非常感谢你的帮助!来自瑞典的问候。 - Robert Foss
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接