Загрузка больших изображений без 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
был нулевым.
Итак, я считаю, что пока единственным решением является уменьшение размера изображения.
Ответы
Ответ 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 может помочь найти что-то полезное).