Загрузка больших изображений без OutOfMemoryError - программирование

Загрузка больших изображений без OutOfMemoryError

У меня есть изображение размером 5000 х 4000 пикселей, которое я хочу нарисовать на холсте.

Сначала я попытался загрузить его из ресурсов. Я положил его в /res/drawable.

Я использовал следующий метод:

InputStream input = getResources().openRawResource(R.drawable.huge_image);
Drawable d = Drawable.createFromStream(input, "image");
d.setBounds(...);
d.draw(canvas);

Он работал как шарм.

В этом случае InputStream является AssetManager.AssetInputStream.

Итак, теперь я хочу загрузить его с SD-карты.

Вот что я пытался сделать:

File f = new File(path);
Uri uri = Uri.fromFile(f);
InputStream input = mContext.getContentResolver().openInputStream(uri);
Drawable d = Drawable.createFromStream(input, "image");

В этом случае InputStream является FileInputStream, и при создании Drawable я получил OutOfMemoryError.

Итак, мне интересно:

Есть ли способ загрузить изображение без получения этой ошибки? Или есть способ конвертировать FileInputStream в AssetInputStream?

Примечание:

Я не хочу изменять размер изображения, потому что я реализую функцию масштабирования/панорамирования. Пожалуйста, не говорите мне, чтобы я читал Эффективное Загрузка больших растровых изображений.

Здесь вы можете просмотреть полный класс . Ошибка возникает при использовании setImageUri().

Здесь мой журнал ошибок:

08-13 11:57:54.180: E/AndroidRuntime(23763): FATAL EXCEPTION: main
08-13 11:57:54.180: E/AndroidRuntime(23763): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:468)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:332)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.graphics.drawable.Drawable.createFromStream(Drawable.java:657)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at com.benitobertoli.largeimagezoom.ZoomImageView.setDrawablefromUri(ZoomImageView.java:187)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at com.benitobertoli.largeimagezoom.ZoomImageView.setImageUri(ZoomImageView.java:588)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at com.benitobertoli.largeimagezoom.TestActivity.onKeyDown(TestActivity.java:30)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.view.KeyEvent.dispatch(KeyEvent.java:1257)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.app.Activity.dispatchKeyEvent(Activity.java:2075)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1673)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.view.ViewRoot.deliverKeyEventToViewHierarchy(ViewRoot.java:2493)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2463)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.view.ViewRoot.handleMessage(ViewRoot.java:1752)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.os.Handler.dispatchMessage(Handler.java:99)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.os.Looper.loop(Looper.java:144)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at android.app.ActivityThread.main(ActivityThread.java:4937)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at java.lang.reflect.Method.invokeNative(Native Method)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at java.lang.reflect.Method.invoke(Method.java:521)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
08-13 11:57:54.180: E/AndroidRuntime(23763):    at dalvik.system.NativeStart.main(Native Method)

EDIT:

Я тестировал свой код на HTC Desire A8181. После того, как мне сообщили, что первый фрагмент кода не работал на некоторых других устройствах, я тестировал на Samsung Galaxy S2 и на эмуляторе.

Результаты: При загрузке из ресурсов эмулятор дал OutOfMemoryError, Galaxy S2 не выдавал исключение, но возвращаемый Drawable был нулевым.

Итак, я считаю, что пока единственным решением является уменьшение размера изображения.

4b9b3361

Ответ 1

Взгляните на:

http://developer.android.com/reference/android/graphics/BitmapFactory.html

decodeStream (InputStream is, Rect outPadding, BitmapFactory.Options opts)

или

decodeFile (String pathName, BitmapFactory.Options opts)

таким образом вы можете загрузить весь растровый рисунок и показать его на экране.

Вам нужно установить правильные параметры.

http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html

inSampleSize

Я попробовал это с вашим кодом:

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

 options.inSampleSize = 4;

 Bitmap myBitmap = BitmapFactory.decodeFile(mUri.getPath(),options);
 Drawable d = new BitmapDrawable(Resources.getSystem(),myBitmap);
 updateDrawable(d);

работает для меня.

Ответ 2

На мой взгляд, лучший вариант - разделить изображение на более мелкие куски. Это предотвратит применение приложения OutOfMemoryException. Вот мой пример кода

Сначала получите изображение с SD-карты и поместите его в растровое изображение

Bitmap b = null;
        try {
            File sd = new File(Environment.getExternalStorageDirectory() + link_to_image.jpg);
            FileInputStream fis;

            fis = new FileInputStream(sd);

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPurgeable = true;
            options.inScaled = false;

            b = BitmapFactory.decodeStream(fis, null, options);
            fis.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

Разбить растровое изображение (b) на меньшие части. В этом случае SPLIT_HEIGHT - 400.

double rest = (image_height + SPLIT_HEIGHT);
        int i = 0;
        Bitmap[] imgs = new Bitmap[50];

        do {
            rest -= SPLIT_HEIGHT;
            if (rest < 0) {
                // Do nothing
            } else if (rest < SPLIT_HEIGHT) {
                imgs[i] = Bitmap.createBitmap(b, 0, (i * SPLIT_HEIGHT), image_width, (int) rest);
            } else {
                imgs[i] = Bitmap.createBitmap(b, 0, i * SPLIT_HEIGHT, image_width, SPLIT_HEIGHT);
            }

            i++;

        } while (rest > SPLIT_HEIGHT);

Теперь у вас есть коллекция меньших изображений. Если вы хотите сделать это быстро, вы можете переработать это растровое изображение:

// Not needed anymore, free up some memory
        if (b != null)
            b.recycle();

С этого момента вы можете поместить меньшие изображения на холст. В моем следующем коде я помещал изображения в ScrollView. Я считаю, что код для размещения изображений на холсте не очень отличается.

// Add images to scrollview
        for (int j = 0; j < imgs.length; j++) {
            Bitmap tmp = imgs[j];
            if (tmp != null) {
                // Add to scrollview
                ImageView iv = new ImageView(activity);

                String id = j + "" + articlenumber;
                iv.setId(Integer.parseInt(id));
                iv.setTag(i);

                iv.setImageBitmap(tmp);

                RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(globalwidth, tmp.getHeight());
                lp.addRule(RelativeLayout.BELOW, previousLongPageImageId);
                lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);

                container.addView(iv, lp);
                previousLongPageImageId = iv.getId();
            }
        }

Надеюсь, это поможет вам.

ПРИМЕЧАНИЕ. Максимальное разрешение для изображений - 2048 * 2048 (ограничения OpenGL или что-то еще), поэтому вам всегда нужно разделить изображения на более мелкие фрагменты.

Ответ 3

Я не уверен, правильно ли это делается, но все же это может решить вашу проблему.

Попробуйте открыть изображение в веб-просмотре. Это обеспечит вам встроенную функцию увеличения/уменьшения и панорамирования, и вам не придется беспокоиться об открытии любого входного потока или чего-либо еще.

Чтобы открыть изображение в webview из активов:

    WebView  wv = (WebView) findViewById(R.id.webView1);  
    wv.loadUrl("file:///android_asset/abc.png");  

Вы можете WebSettings для управления масштабированием.

Надеюсь, это поможет вам!

Ответ 4

Если он работал, когда вы загружались из ресурсов, и теперь он не работает при загрузке вручную, я подозреваю, что у вас есть некоторые проблемы с управлением ресурсами, возможно, с утечкой памяти.

Выполняется ли OutOfMemoryError всегда в начале приложения или происходит позже? При изменении конфигурации? Используете ли вы статические поля (если они используются неправильно, это часто приводит к утечкам памяти в приложении Android, пример с объяснением, это подходит вашему делу)?
Попробуйте использовать некоторые инструменты для профилирования (google может помочь найти что-то полезное).