最快的位图加载/显示方式;重用位图的最佳方法

3
我需要以不同的帧速率显示图片,最大帧速率为30。这些图片来自SD卡,大小均为480 x 640。 我已经创建了三种可能的解决方案,但每个都存在问题:
以下结果均为30 FPS。
I. 不重复使用位图
  • lots of GC calls: aprox. 30 GC per second
  • CPU load: reach up to 92%

    private Bitmap bitmap;
    
    private void startAnimation1() {
        TimerTask updateImage = new UpdateImage1();
        timer.scheduleAtFixedRate(updateImage, 0, 1000 / FPS);
    }
    
    class UpdateImage1 extends TimerTask {
        @Override
        public void run() {
            try {
                if (i == IMAGES_NR) {
                    i = 0;
                }
                bitmap = BitmapFactory.decodeStream(new FileInputStream(framesFiles[i]), null, null);
                i++;
            } catch (FileNotFoundException e) {
                System.out.println("Exception 1: " + e.getMessage());
            }
    
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    imgView.setImageBitmap(bitmap);
                }
            });
        }
    }
    

II. 通过 BitmapFactory.Options.inBitmap 实现位图重用

  • GC 调用次数更低 - 每秒1或2次
  • CPU 负载:高达84%

运行动画一段时间后,应用程序崩溃:

06-20 15:08:58.158: WARN/System.err(7880): java.lang.ArrayIndexOutOfBoundsException: length=-5131855; regionStart=0; regionLength=1024
06-20 15:08:58.158: WARN/System.err(7880): at java.util.Arrays.checkOffsetAndCount(Arrays.java:1731)
06-20 15:08:58.158: WARN/System.err(7880): at java.io.BufferedInputStream.read(BufferedInputStream.java:273)
06-20 15:08:58.158: WARN/System.err(7880): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
06-20 15:08:58.158: WARN/System.err(7880): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:587)
06-20 15:08:58.158: WARN/System.err(7880): at com.example.SendPreviewOptimization.MyActivity$UpdateImage2.run(MyActivity.java:148)
06-20 15:08:58.158: WARN/System.err(7880): at java.util.Timer$TimerImpl.run(Timer.java:284)
06-20 15:08:58.168: DEBUG/skia(7880): ---- read threw an exception
06-20 15:08:58.168: DEBUG/skia(7880): --- decoder->decode returned false
06-20 15:08:58.168: WARN/System.err(7880): java.lang.IllegalArgumentException: Problem decoding into existing bitmap
06-20 15:08:58.168: WARN/System.err(7880): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:590)
06-20 15:08:58.168: WARN/System.err(7880): at com.example.SendPreviewOptimization.MyActivity$UpdateImage2.run(MyActivity.java:148)
06-20 15:08:58.168: WARN/System.err(7880): at java.util.Timer$TimerImpl.run(Timer.java:284)
06-20 15:08:58.178: ERROR/msm8960.hwcomposer(330): prepareBypass: Unable to setup bypass due to non-pmem memory
06-20 15:08:58.198: ASSERT/libc(7880): Fatal signal 11 (SIGSEGV) at 0xffd1d447 (code=1)
06-20 15:08:58.238: ERROR/msm8960.hwcomposer(330): prepareBypass: Unable to setup bypass due to non-pmem memory
06-20 15:08:58.498: ERROR/MP-Decision(1448): DOWN Ld:25 Ns:1.100000 Ts:190 rq:0.000000 seq:194.000000
06-20 15:08:58.708: INFO/DEBUG(27660): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***



    private static BitmapFactory.Options bitmapOptions;
    private FileInputStream in;

    private void startAnimation2() {
        bitmapOptions = new BitmapFactory.Options();
        // setup bitmap reuse options:
        bitmapOptions.inPurgeable = true;
        bitmapOptions.inInputShareable = true;
        bitmapOptions.inBitmap = reusableBitmap;
        bitmapOptions.inMutable = true;
        bitmapOptions.inSampleSize = 1;

        TimerTask updateImage = new UpdateImage2();
        timer.scheduleAtFixedRate(updateImage, 0, 1000 / FPS);
    }

    class UpdateImage2 extends TimerTask {
        @Override
        public void run() {
            try {
                if (i == IMAGES_NR) {
                    i = 0;
                }

                //** version 1:
                in = new FileInputStream(framesFiles[i]);
                //decode into existing bitmap
                BitmapFactory.decodeStream(in, null, bitmapOptions);
                in.close();

                //** version 2:
                //BitmapFactory.decodeFile(framesFiles[i].getAbsolutePath(), bitmapOptions);

                i++;
            } catch (Exception e) {
                System.out.println("Exception 2: " + e.getMessage());
            }
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    imgView.setImageBitmap(reusableBitmap);
                }
            });
        }
    }

III. 选项三:使用缓冲区(使Bytebuffer更有效的一件事是使用直接内存。)

  • this option I could not make it work :(

    private ByteBuffer buffer;
    private byte[] b;
    private IntBuffer mPixels;
    
    private void startAnimation3() {
        buffer = ByteBuffer.allocate(480 * 640 * 6);
        b = new byte[480 * 640 * 6];
        TimerTask updateImage = new UpdateImage3();
        timer.scheduleAtFixedRate(updateImage, 0, 1000 / FPS);
    }
    
    class UpdateImage3 extends TimerTask {
        public void run() {
            try {
                if (i == IMAGES_NR) {
                    i = 0;
                }
                FileInputStream frameInputStream = new FileInputStream(framesFiles[i]);
                frameInputStream.read(b);
                buffer.wrap(b);
                buffer.position(0);
                reusableBitmap.copyPixelsFromBuffer(buffer);
                frameInputStream.close();
                i++;
            } catch (Exception e) {
                System.out.println("Exception 3: " + e.getMessage());
            }
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    imgView.setImageBitmap(reusableBitmap);
                }
            });
        }
    }
    
    private ByteBuffer copyToBuffer(Bitmap bitmap) {
        int size = bitmap.getHeight() * bitmap.getRowBytes();
        ByteBuffer buffer = ByteBuffer.allocateDirect(size);
        bitmap.copyPixelsToBuffer(buffer);
        return buffer;
    }
    
在上述每个解决方案中,我在logcat中收到了大量的以下内容:
ERROR/msm8960.hwcomposer(330): prepareBypass: Unable to setup bypass due to non-pmem memory

我不知道“which I do not know what exactly means”的确切含义。

我以前没有使用过位图重用,也不知道哪种方法最好。

我在这里添加了我的项目:https://www.dropbox.com/sh/3xov369u1bmjpd1/qBQax4t48D,还有两个帧/图片。

对Neron T的回答:

我尝试过那个库:

    //Option IV:
    private AQuery aquery;

    private void startAnimation4() {
        aquery = new AQuery(this);
        aquery.id(R.id.imgView);

        TimerTask updateImage = new UpdateImage4();
        timer.scheduleAtFixedRate(updateImage, 0, 1000 / FPS);
    }

    class UpdateImage4 extends TimerTask {
        public void run() {
            try {
                if (i == 29) {
                    i = 0;
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //load image from file, down sample to target width of 300 pixels
                        aquery.image(framesFiles[i],300);
                    }
                });
                i++;
            } catch (Exception e) {
                System.out.println("Exception 4: " + e.getMessage());
            }
        }
    }

它不能像我预期的那样工作 - 每张图片都有闪烁效果。我认为首先它清除了图片,然后才添加新的图片 :(

1个回答

0

注意,你不能在非UIThread(主线程)内部的线程中修改视图,尝试使用AsyncTasks。一个简单的方法来使用图片而不需要过多思考是使用像Android Query这样的框架,可以在这里查看

如果你想手动完成,请查看这个链接这个链接


首先感谢。我正在使用runOnUiThread方法。我的应用程序上下文比上面的示例更复杂。我将尝试从您提供的链接中使用ImageLoading jar文件(更确切地说是异步加载文件中的图像选项),但是对于我在问题中提出的版本的答案也将不胜感激。 - Paul
抱歉,我没有注意到你在UIThread上运行它,所以我一直在想为什么它没有出现那种错误。希望这个库对你有用。 - Neron T
我已经更新了答案,并附上使用该库的结果。 - Paul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接