BitmapFactory.decodeStream из Assets возвращает null на Android 7

Как декодировать растровые изображения из каталога активов в Android 7?

Мое приложение хорошо работает на версиях Android до Marshmallow. В Android 7 не удается загрузить изображения из каталога активов.

Мой код:

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);

}

В результате (только Android 7) BitmapFactory.decodeStream равно нулю. Он корректно работает со старыми API Android.

В режиме отладки я вижу следующее сообщение:

09-04 10:10:50.384 6274-6610/myapp D/skia: --- SkAndroidCodec::NewFromStream вернул null

Может ли кто-нибудь сказать мне причину и как исправить кодировку?

Изменить: тем временем я обнаружил, что удаление первого BitmapFactory.decodeStream с inJustDecodeBounds=true впоследствии приводит к успешному BitmapFactory.decodeStream с inJustDecodeBounds=false. Не знаю в чем причина и не знаю чем заменить измерение размера растрового изображения.


person Savari    schedule 04.09.2016    source источник
comment
Я предполагаю, что ваша проблема связана с вашим активом. Я только что протестировал один из образцов моей книги, который загружает изображения из активы на Nexus 9 под управлением Android 7.0. Кажется, это работает нормально.   -  person CommonsWare    schedule 04.09.2016


Ответы (3)


Я думаю, что мы в одной лодке. Моя команда застряла в этой проблеме на некоторое время, как и вы.

Похоже, проблема в BitmapFactory.cpp (https://android.googlesource.com/platform/frameworks/base.git/+/master/core/jni/android/graphics/BitmapFactory.cpp) Некоторый код был добавлен в Android 7.0 и сделал проблему возникла.

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

И я обнаружил, что метод BitmapFactory.decodeStream не создает растровое изображение после того, как мы установили inJustDecodeBounds=false, но когда я пытаюсь создать растровое изображение без связанного декодирования. Это работает! Проблема связана с BitmapOptions в том, что InputStream не обновляется, когда мы снова вызываем BitmapFactory.decodeStream.

Поэтому я сбрасываю этот InputStream перед повторным декодированием

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

Похоже, нам нужно сбрасывать InputStream каждый раз перед его повторным использованием.

person Akexorcist    schedule 19.01.2017
comment
Это помогло. Просто сброс InputStream. Спасибо за помощь. - person Savari; 21.01.2017
comment
Я попробовал этот трюк, но я продолжаю получать отметку/сброс, не поддерживаемую IOException, при вызове rest. Но я использую FileInputStream, потому что пытаюсь загрузить растровое изображение с SD-карты. - person Kirill Volkov; 13.03.2017
comment
Есть ли другой способ, кроме FileInputStream, открыть файл из хранилища? - person Kirill Volkov; 20.03.2017
comment
Спасибо, это помогло мне. Android 7, файл использовался из ресурсов (изображение png). Добавление inputStream.reset() безопасно для использования в предыдущих версиях, поэтому добавьте его, если вы повторно используете один и тот же поток ввода. - person Kirill Karmazin; 23.04.2017
comment
Андроид 7.1.2 и он не работает. Чтобы это работало, мне нужно изменить reset() на close(), а затем создать новый поток перед следующим decodeStream(). - person Jakub S.; 21.06.2017
comment
Была такая же проблема с Android 5.1, благодаря вам она решена +1. - person Farshad Tahmasbi; 05.07.2017

Я подозреваю, что конфигурация безопасности Android по умолчанию не позволяет вам загружать файл с использованием протокола «http://» (без шифрования).

Попробуйте найти изображение или файл с протоколом «https://». Посмотрите, не приведет ли это к загрузке вашего файла. В противном случае установите конфигурацию безопасности сети, которая разрешает «http».

person dirtbag    schedule 05.01.2019

На случай, если это кому-то поможет, я столкнулся с аналогичной проблемой, обновляя старый код, который ранее работал для изменения размера изображений. Моя проблема была выше по стеку, где я читал данные из файла изображения. Я использовал IOUtils.toByteArray(Reader), который устарел. Я переключился на преобразование в массив байтов напрямую из URI, и теперь он работает хорошо. См. первые две строки resizeImage() ниже для примера этого нового метода (остальная часть кода позволяет мне изменять размер изображения).

public static Bitmap resizeImage(Uri imageUri, int targetWidth, int targetHeight) {
    // Convert the image to a byte array
    java.net.URI tempUri = new URI(uri.toString());
    byte[] imageData = IOUtils.toByteArray(tempUri);

    // First decode with inJustDecodeBounds=true to check dimensions
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeByteArray(imageData, 0, imageData.length, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    Bitmap reducedBitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.length, options);
    Bitmap resizedBitmap = Bitmap.createScaledBitmap(reducedBitmap, targetWidth, targetHeight, false);

    return resizedBitmap;
}

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    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;

        // Calculate the largest inSampleSize value that is a 
        // power of 2 and keeps both height and width larger
        // than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}
person Ben Jakuben    schedule 28.10.2016