Search code examples
androidbitmapfactoryandroid-assetsandroid-7.0-nougat

BitmapFactory.decodeStream from Assets returns null on Android 7


How to decode bitmaps from Asset directory in Android 7?

My App is running well on Android versions up to Marshmallow. With Android 7 it fails to load images from the Asset directory.

My Code:

private Bitmap getImage(String imagename) {
    // Log.dd(logger, "AsyncImageLoader: " + ORDNER_IMAGES + imagename);

    AssetManager asset = context.getAssets();
    InputStream is = null;
    try {
        is = asset.open(ORDNER_IMAGES + imagename);
    } catch (IOException e) {
        // Log.de(logger, "image konnte nicht gelesen werden: " + ORDNER_IMAGES + imagename);
        return null;
    }


    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(is, null, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, PW, PH);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;

    // Lesen des Bitmaps in der optimierten Groesse
    return BitmapFactory.decodeStream(is, null, options);

}

As a result (only Android 7) BitmapFactory.decodeStream is null. It works correctly an older Android APIs.

In debug mode I see the following Message:

09-04 10:10:50.384 6274-6610/myapp D/skia: --- SkAndroidCodec::NewFromStream returned null

Can someone tell me the reason and how to correct the coding?

Edit: Meanwhile i found, that removing of the first BitmapFactory.decodeStream with inJustDecodeBounds=true leads to a successful BitmapFactory.decodeStream afterwards with inJustDecodeBounds=false. Don't know the reason and don't know how to substitute the measurement of bitmap size.


Solution

  • I think we are in the same boat. My team stuck in this problem for a while like you.

    It seems be a problem in BitmapFactory.cpp (https://android.googlesource.com/platform/frameworks/base.git/+/master/core/jni/android/graphics/BitmapFactory.cpp) Some code was added in Android 7.0 and made the problem occurred.

    // Create the codec.
    NinePatchPeeker peeker;
    std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(streamDeleter.release(), &peeker));
    if (!codec.get()) {
        return nullObjectReturn("SkAndroidCodec::NewFromStream returned null");
    }
    

    And I found out the BitmapFactory.decodeStream method didn't create the bitmap after we set inJustDecodeBounds=false but when I try to create bitmap without bound decoding. It's works! The problem is about BitmapOptions in that InputStream doesn't updated when we called BitmapFactory.decodeStream again.

    So I reset that InputStream before decode again

    private Bitmap getBitmapFromAssets(Context context, String fileName, int width, int height) {
        AssetManager asset = context.getAssets();
        InputStream is;
        try {
            is = asset.open(fileName);
        } catch (IOException e) {
            return null;
        }
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, options);
        try {
            is.reset();
        } catch (IOException e) {
            return null;
        }
        options.inSampleSize = calculateInSampleSize(options, width, height);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeStream(is, null, options);
    }
    
    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
    
        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
    

    It's looks like we have to reset InputStream every time before reuse it.