【问题标题】:Loading large images without OutOfMemoryError加载没有 OutOfMemoryError 的大图像
【发布时间】:2012-08-09 12:44:52
【问题描述】:

我有一个 5000 x 4000 像素的图像,我想将其绘制到画布上。

首先我尝试从资源中加载它。 我把它放在/res/drawable

我使用了以下方法:

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

它就像一个魅力。

在这种情况下,InputStreamAssetManager.AssetInputStream

所以现在我想从 sdcard 加载它。

这是我尝试做的:

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

在这种情况下,InputStreamFileInputStream,而我在创建 Drawable 时得到了 OutOfMemoryError

所以我想知道:

有没有办法加载图像而不会出现该错误?或者有没有办法将FileInputStream 转换为AssetInputStream

注意:

我不想调整图像大小,因为我正在实现缩放/平移功能。 请不要告诉我阅读Loading Large Bitmaps Efficiently

您可以查看完整课程here。使用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)

编辑:

我在 HTC Desire A8181 上测试我的代码。 在被告知第一个代码 sn-p 无法在其他一些设备上运行后,我在三星 Galaxy S2 和模拟器上进行了测试。

结果: 从资源加载时,模拟器给出OutOfMemoryError,Galaxy S2没有抛出异常但返回的Drawable为null。

所以我想目前唯一的解决方案是对图像进行下采样。

【问题讨论】:

  • 我尝试在 m 模拟器上运行您的代码,即使在“From Res”方法上也出现 OutOfMemory 错误 :) 您使用的哪些设备和/或模拟器设置能够无错误地运行它?

标签: java android android-canvas android-image


【解决方案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);

为我工作。

【讨论】:

  • 正如我在问题中所说,从资源加载图像没有问题。另外我不想调整图片大小。
  • 对不起,我错过了,然后尝试 decodeStream (InputStream is, Rect outPadding, BitmapFactory.Options opts) 方法
  • 我再说一遍我不想调整图片大小
  • 需要来缩放和平铺你不能在内存中保存5000x4000px的位图。要缩放和平移,您需要确定原始图像的哪些部分是可见的,并加载按屏幕密度缩放的相应图块
  • 顺便说一句:如果启用硬件渲染,从资源加载图像的代码将为您提供 Galaxy Nexus 上的 OutOfMemory
【解决方案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。我认为将图像放在 Canvas 上的代码并没有太大的不同。

// 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 限制或其他限制),因此您始终必须将图像分割成更小的部分。

【讨论】:

  • 感谢您的回答。在尝试拆分位图之前b = BitmapFactory.decodeStream(fis, null, options); 已经给出了 OutOfMemoryError。此外,您将位图拆分为 50 个较小的位图,但您仍将所有这些位图同时加载到内存中,结果相同。
  • 如果你设置了android:largeHeap="true" 然后做上面的代码呢?就像我在笔记中所说的那样,您必须将图像分成几部分,因为最大分辨率为 2048*2048。当然,在我的解决方案中,您还必须将图像分成垂直部分(宽度大于 2048)。
【解决方案3】:

我不确定这样做是否正确,但这仍然可以解决您的问题。

尝试在 web 视图中打开您的图像。这将为您提供内置的放大/缩小和平移功能,您不必担心打开任何输入流或其他任何东西。

要从资产在 web 视图中打开图像:

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

您可以WebSettings 允许缩放控制。

希望对你有帮助!!

【讨论】:

  • 感谢您的回答。我正在开发带有手势检测的自定义视图,而 WebView 还不够。
【解决方案4】:

如果从资源加载时它可以工作,而现在手动加载时它不能工作,那么我怀疑你的资源管理有问题,可能是内存泄漏。

OutOfMemoryError 总是在应用程序开始时出现还是在以后出现?是在配置更改吗?您是否使用静态字段(如果使用不当,这通常会导致 Android 应用程序中的内存泄漏,example with explanation 这适合您的情况)?
尝试使用一些分析工具(google 可以帮助找到有用的东西)。

【讨论】:

  • 当我从 Uri 加载时,OutOfMemoryError 出现在这一行 Drawable d = Drawable.createFromStream(input, "image"); 上。我没有使用任何静态字段。您可以查看完整课程here。我打电话给setImageUri()
  • 此链接用于工作代码(从资源加载的图像)。显然您没有推送有问题的更改(新分支?)。
  • 请再次检查。我添加了一个菜单。 “从资源”和“从文件”。使用“从文件”发生错误。您必须将 huge_image.pngres/drawable 复制到您的 sdcard 并替换代码中的路径。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-02
相关资源
最近更新 更多