【问题标题】:Object pools to handle ImageViews in Android?在 Android 中处理 ImageViews 的对象池?
【发布时间】:2014-05-05 14:21:36
【问题描述】:

在我的 Android 应用中,我创建了一个显示 9x9 字段的视图(见简化截图):

视图中的每个字段都由一个 ImageView 表示(显示图片而不是矩形)。一些 ImageView 将通过将它们设置为 null 并将可见性设置为 View.GONE 来删除,并且每次将新元素添加到该字段时都会创建新的 ImageView。这意味着我在很短的时间内创建了大量的 ImageView。关键是,游戏在我的 HTC One 上运行良好,但一段时间后会出现滞后(我认为是在垃圾收集运行时),然后它停止滞后并再次运行良好。

这让我想到了使用Pool 来管理我的对象,例如通过回收“已删除的 ImageViews”并通过设置位置和更改图像源来重用它们。我正在为普通 Android 开发,但我考虑使用 LibGDX 框架。

我的问题是:您对如何在纯 Java/Android 中实现 Pool 有什么建议吗?我发现this 的帖子很有趣,我认为我可以使用 Apache Commons ObjectPool。在 Android 或 LibGDX 上有没有更好的方法?

注意:每轮 ImageView 的位置不同。我使用 Tween Engine 移动它们。但是,图像视图的数量是恒定的。意味着在游戏的某个时刻,我确实有 81 个 ImageView (9*9) 以及一些用于动画特殊事件的 ImageView(可能是 +10)。

如果有任何意见/建议,我将不胜感激。

最好的问候,

吉米

【问题讨论】:

  • 如果我以正确的方式理解您的游戏理念,那么带有图像视图的网格是静态的 - 这意味着图像视图的数量始终相同,对吗?为什么不更新 imageView 背景或图像而不是重新创建它们?在我看来,带有池的想法根本不好 - 如果您需要实现像列表或网格视图这样在某个方向滚动的东西,它可能会很有用
  • 对不起,我还没有提到它,但我正在移动 ImageViews(使用 Tween 引擎制作动画),因此它们在每一轮中的位置不同。否则我会完全同意你的观点,只改变图像源是明智的。此外,我确实有更多的 ImageViews 用于动画合并两张卡。希望这对你有意义。
  • 好的,那么我建议使用某种集合,例如 List,每个不再需要的图像视图都应该放在那里,如果需要一些新的 ImageView 那么你应该首先检查您的列表是否不包含一个,并更新其背景和位置。当然,如果您使用它,则应将其从列表中删除。仅当列表为空时才创建新的 ImageView。我认为它是最简单的缓存机制之一,但它在例如 ListView(行视图的重用)中正常工作
  • 将您的解决方案与池相比有很大不同吗?在那种情况下,我总是需要搜索“不可见”的 ImageView。可以工作,我需要尝试一下。你能把你的建议作为答案发表吗?我将在明天之前实施,试一试。

标签: java android libgdx object-pooling


【解决方案1】:

根据吉米的建议,我将我的评论作为答案发表

我建议使用某种集合,例如List< ImageView >

  • 每个不再需要的图像视图都应该去那里

  • 如果需要一些新的 ImageView,那么您应该首先检查您的列表是否不包含一个,并更新其背景和位置。

  • 当然如果你使用它,你应该把它从列表中删除。
  • 仅当列表为空时才创建新的 ImageView。

我认为它是最简单的缓存机制之一,但它在例如 ListView(行视图的重用)中正常工作

【讨论】:

  • 或者我会使用两个列表.. 一个用于可见 ImageView,一个用于不可见 ImageView。正如我告诉你的,我明天会告诉你。迫不及待地想实现它:)
  • 使用两个列表视图效果很好:List<ImageView> cacheViewsList<ImageView> currentViews。当cacheViews 为空时,我创建一个新的ImageView 并将引用存储在currentViews 中,如果它不为空,我通过将cacheViews 移动到另一个列表来重用视图,设置位置和可见性.. 回收很容易,只需将可见性设置为View.GONE 并将其放入cacheViews。感谢您的想法/提醒:-D
  • 很高兴听到这个消息,rupps 的回答也很好,但它是完全不同的方法
  • 没错,您的解决方案更容易在我现有的代码中实现,并且当我使用 Tween 引擎移动 ImageView 时它可以工作。认为使用 rupps 解决方案会相当复杂。但是,当我切换到 LibGDX 框架时,我会尝试类似于 rupps 的解决方案,因为该框架使用(连续)渲染方法而不是 ImageView。 (至少到目前为止我是这么理解的)
【解决方案2】:

我也遇到过类似的问题,但从未对性能感到真正满意。最重要的是,当我将我的运动场扩展到 30*30 时,我得到了 900 个 ImageView。我尝试了很多优化,但性能和内存都很高,而且跨设备无法预测。

所以我所做的就是创建一个自定义视图。只有一个。然后,此视图在画布上绘制正方形。我很惊讶现在我可以拥有数万个正方形 (100x100) 并且性能非常流畅。

我在这里发布了我的观点框架,删除了所有废话以获取灵感,我强烈建议您遵循这种方法。

    /**
     * ChequeredView is a view that displays a 2D square matrix, where each square can be individually selected.
     * For high performance, It only uses one view regardless of the matrix size (everything is drawn in a canvas)
     * @author rodo 13 march 2014 <rlp@nebular.tv>
     */

    public class ChequeredView extends View {

        private final String TAG="ChequeredView";

        private static final int 
            DEFAULT_MATRIX_SIZE_COLS=20, 
            DEFAULT_MATRIX_SIZE_COLS=20, 
            DEFAULT_SQUARE_SIZE=100;

        private int mCols=DEFAULT_MATRIX_SIZE_COLS, 
                    mRows=DEFAULT_MATRIX_SIZE_ROWS;

        /* Save touch press */
        private int mTouchX=0, mTouchY=0;

        ///////////////// VIEW CODE

        public ChequeredView(Context context, AttributeSet attrs) { super(context, attrs); }
        public ChequeredView(Context context) { super(context); }

        /**
         * Report a size of your view that is: SquareSize * NUM_COLS x SquareSize * NUM_ROWS. You will paint it later.
         */

        @Override
        protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);

            // calculate optimum square size
            mStyleSquareSize=MeasureSpec.getSize(widthMeasureSpec) / mSquaresPerCanvas;

            // report total size
            setMeasuredDimension(DEFAULT_MATRIX_SIZE_COLS * mStyleSquareSize, DEFAULT_MATRIX_SIZE_ROWS * mStyleSquareSize);
        }

        @Override
        public void onDraw(Canvas canvas)  {
            render(canvas);
        }


        @Override 
        public boolean onTouchEvent(android.view.MotionEvent event) {

            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // I execute the action in ACTION_UP so I can put this inside a scrollview and touch doesn't interferre the scroll.
                mTouchX=(int) event.getX();
                mTouchY=(int) event.getY();
                return true;

            case MotionEvent.ACTION_UP:

                // only process touch if finger has not moved very much (if it has, it's a fling on parent)

                if ( isApprox(event.getX(), mTouchX, 5) && (isApprox(event.getY(), mTouchY, 5)) ) 
                        processTouch((int)event.getX(), (int)event.getY());

                break;

            }
            return false;
        };

        /**
         * Check if a value is close to another one
         * @param value Value to check
         * @param ref Reference value
         * @param threshold Threshold
         * @return true if |val-ref|<threshold
         */

        private boolean isApprox(float value, int ref, int threshold) {
            float result=Math.abs(value-ref);
            return (result<threshold);
        }

        ///////////////// VIEW METHODS


        public void setMatrixSize(int numx, int numy) {
            mRows=numx;
            mCols=numy;
            invalidate();
        }

        ///////////////// VIEW INTERNALS


        /**
         * Renders the whole squaremap
         * @param canvas
         */

        private void render(Canvas canvas) {
            if (canvas==null) return;
            for (int x=0; x<mCols; x++) {
                for (int y=0; y<mRows; y++) {
                    render_square(canvas, x, y);
                }
            }
        }

        /**
         * Renders one of the squares
         * @param canvas Canvas where to draw
         * @param nCol The column
         * @param nRow The row
         */

        private void render_square(Canvas canvas, int nCol, int nRow) {


            String text=null, transition=null;
            int delay=0;
            Paint paint=null;

            int cx=nCol*mStyleSquareSize, cy=nRow*mStyleSquareSize;

            canvas.save();
            canvas.translate(cx, cy);
            canvas.drawRect(mStyleSquareMargin, mStyleSquareMargin, mStyleSquareSize-2*mStyleSquareMargin, mStyleSquareSize-2*mStyleSquareMargin, paint);

            // this draws an square (I use vectorial squares with text rather than images, but just change drawRect to drawBitmap)
            // just change it for drawBitmap() to draw one bitmap

            canvas.restore();
        }

        /**
         * Process a touch on the map area
         * @param x raw x coordinate
         * @param y raw y coordinate
         */ 

        private void processTouch(int x, int y) {
            int nx=x/mStyleSquareSize, ny=y/mStyleSquareSize;
            mSelectedX=nx;
            mSelectedY=ny;
            if (mSquareListener!=null) {
                mSquareListener.onSquareSelected(nx, ny, data);
            } 
            invalidate();
        }
    }

【讨论】:

  • 你有什么建议我可以在这个骨架上使用 ImageView 吗?我不绘制矩形 - 每个 ImageView 都代表另一个图像源(但有些是相同的)。但是,我可以想象,比一遍又一遍地画会快得多。
  • 忘记ImageView,只要在我指出的地方使用drawBitmap。 drawBitmap 可以在画布上绘制任何位图。 ImageView 内部正是这样做的!看看函数render_square!
  • 是的...关键是要摆脱 ImageView 而不是创建 400 个视图,而是只创建一个。性能提升与这些节省有关,相信我。
  • 现在考虑一下,我希望它可以与 Tween 引擎一起使用。 Tween Engine 正在移动所有元素(任何类型的布局或布局中的元素)。我已经使用这个引擎实现了所有动画。现在,我不知道这是否适用于画布。希望如此..
  • 哈哈,我的意思是理解你的问题,我的项目已经受够了!
猜你喜欢
  • 2012-12-14
  • 2011-06-23
  • 1970-01-01
  • 1970-01-01
  • 2021-12-27
  • 1970-01-01
  • 2015-12-11
  • 2017-10-26
  • 2010-10-27
相关资源
最近更新 更多