如何在安卓平台上制作一个拼图应用而不会耗尽内存?

9
我注意到谷歌应用商店中的其他益智游戏应用可以拥有多达400个可移动的拼图碎片。
我一直在尝试学习如何至少拍摄一张图片来代表我的拼图,裁剪某些部分并使用拼图设计掩盖剩余的图像空间,以创建我的个人拼图碎片。
我想让我的应用程序最多使用20个碎片,但是根据我的Android Studio内存日志,一旦位图工厂完成创建一个拼图碎片,我就会使用大约18MB的内存。在创建20个碎片以及应用程序的所有其他功能之后,我将使用超过400MB的内存,必须使用“largeHeap=true”来避免内存不足,但我非常接近超过这些更高的限制,使得应用程序非常缓慢并且足够的动画活动将不可避免地导致应用程序崩溃。
我很想知道那些其他应用程序在做什么,而我没有做到。
非常感谢任何建议。
FYI,我正在使用PNG24作为我的图像,并且测试图像的尺寸为556x720。
以下是一个例子,如果我只创建一个可动画的拼图碎片图像。
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.requestWindowFeature(Window.FEATURE_NO_TITLE);//Remove title bar
    this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//Hides notification bar
    this.setContentView(R.layout.activity_main);//set content view AFTER ABOVE sequence (to avoid crash)

    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

    mainDisplay = getWindowManager().getDefaultDisplay();
    mainLayout = (ViewGroup) findViewById(R.id.id_layout);

    DisplayMetrics m = new DisplayMetrics();
    this.getWindowManager().getDefaultDisplay().getMetrics(m);
    int windowHeight = m.heightPixels;
    int windowWidth = m.widthPixels;

    offsetX = (windowWidth / 1440.0f);
    offsetY = (windowHeight / 2560.0f);

    ourContext = this;

    xpos = new float[2];
    ypos = new float[2];

    iv_PuzzleOne();

    bkgdbm = BitmapFactory.decodeResource(getResources(), R.drawable.puzzleimage); 

    paintObject = new Paint();
    paintObject.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); 

    bm_PuzzleOne();

    thisTimerTask = new ThisClass();
    thisTimer = new Timer();
    thisTimer.scheduleAtFixedRate(thisTimerTask, 16, 16);

    touchpad = new ImageButton(this);
    SetPos(0, 0, 1440, 2560);
    touchpad.setLayoutParams(layoutPositioner);
    touchpad.getBackground().setAlpha(1);
    mainLayout.addView(touchpad);

    touchpad.setOnTouchListener(new View.OnTouchListener() {

        //@SuppressLint("NewApi")
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                xpos[0] = event.getX(); //storing my finger's position coordinates when I first touch the screen into the 1st element
                ypos[0] = event.getY();

                if ((event.getX() > imgx1) && (event.getX() < imgx1 + imagewidth1)
                        && (event.getY() > imgy1) && (event.getY() < imgy1 + imageheight1)) {
                    touching1Puzzle = true;
                    img1.bringToFront();
                }

            }

            if (event.getAction() == MotionEvent.ACTION_MOVE) {

                xpos[1] = event.getX(); //add my finger's new current coordinates into the 2nd element
                ypos[1] = event.getY();

                if (touching1Puzzle) {
                    adjustImg();
                }
            }

            if (event.getAction() == MotionEvent.ACTION_UP) {
                if (touching1Puzzle) {
                    touching1Puzzle = false;
                }

            }

            return false;
        }

    });
}


void bm_PuzzleOne()
{
    //start of 1st puzzle

    foregdimg1 = BitmapFactory.decodeResource(getResources(), R.drawable.puzzlepieceprac);//puzzle cut out (42.48MB) +6.15
    mutableforegdimg1 = foregdimg1.copy(Bitmap.Config.ARGB_8888, true); //(48.32MB) +5.84

    compositeImage1 = Bitmap.createBitmap(mutableforegdimg1);//cuts out foreground info into bkgdimage (54.43MB) +6.11

    imgCanvas1 = new Canvas(compositeImage1); //canvas references puzzle cut out image (54.43MB) +0
    imgCanvas1.drawBitmap(croppedBmp, null, new Rect(0, 0, 1500, 2000), paintObject);//places puzzle image on canvas (54.43MB) +0

    img1.setImageBitmap(compositeImage1);

}


void iv_PuzzleOne()
{
    img1 = new ImageView(ourContext);
    SetPos(imgx1, imgy1, imagewidth1, imageheight1);
    //bkgdimg.setImageResource(R.drawable.c);
    //img1.setBackgroundColor(0xffF07305); //Orange
    img1.setLayoutParams(layoutPositioner);
    mainLayout.addView(img1);

}

void adjustImg()
{
    if (touching1Puzzle)
    {
        if (xpos[1] > xpos[0]) //if the image had slid to the right
        {
            xPositionDifference = xpos[1] - xpos[0]; // find the difference in coordinate value between where my finger was and where it currently is
            imgx1 += xPositionDifference; //add that difference to the current image position ...

            xpos[0] += xPositionDifference; // ... store that difference for the next shift in finger postion

        } else if (xpos[1] < xpos[0]) //if the image had slid to the left
        {
            xPositionDifference = xpos[0] - xpos[1]; // find the difference in coordinate value between where my finger was and where it currently is
            imgx1 -= xPositionDifference; //subtract that difference to the current image position ...

            xpos[0] -= xPositionDifference; // ... store that difference for the next shift in finger postion
        }

        if (ypos[1] > ypos[0]) //if the image had slid to the right
        {
            yPositionDifference = ypos[1] - ypos[0]; // find the difference in coordinate value between where my finger was and where it currently is
            imgy1 += yPositionDifference; //add that difference to the current image position ...

            ypos[0] += yPositionDifference; // ... store that difference for the next shift in finger postion

        } else if (ypos[1] < ypos[0]) //if the image had slid to the left
        {
            yPositionDifference = ypos[0] - ypos[1]; // find the difference in coordinate value between where my finger was and where it currently is
            imgy1 -= yPositionDifference; //subtract that difference to the current image position ...

            ypos[0] -= yPositionDifference; // ... store that difference for the next shift in finger postion

        }
    }

}

class ThisClass extends TimerTask {

    @Override
    public void run() {
        MainActivity.this.runOnUiThread(new Runnable() {
            @Override
            public void run() {

                if(touching1Puzzle)
                {SetPos(imgx1, imgy1, imagewidth1, imageheight1);
                    img1.setLayoutParams(layoutPositioner);}

            }
        });
    }
}

public void SetPos(float x, float y, float width, float height) {
    layoutPositioner = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutPositioner.topMargin = (int) (offsetY * y);
    layoutPositioner.leftMargin = (int) (offsetX * x);
    layoutPositioner.width = (int) (width * offsetX);
    layoutPositioner.height = (int) (height * offsetY);
}

这是加载5张图片时的样子:

http://s15.postimg.org/ymqspk77v/Screenshot_2016_02_29_07_19_26.png

5个回答

3

好的,让我们看看。

首先,您不需要让拼图块如此之大,因此可以将它们缩小:

来自http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

要告诉解码器对图像进行子采样,加载一个较小的版本到内存中,请在BitmapFactory.Options对象中将inSampleSize设置为true。例如,使用4个inSampleSize解码具有分辨率2048x1536的图像会产生一个大约为512x384的位图。将其加载到内存中仅使用0.75MB,而不是完整图像的12MB(假设位图配置为ARGB_8888)。

其次,您应该尝试在AsyncTask中加载位图以防止应用程序冻结。

第三,您可以使用LruCache缓存位图以加快速度。

http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

这应该足够了,但万一不够的话,以下是所有来源以及一些额外内容:

http://developer.android.com/training/displaying-bitmaps/index.html

您还可以尝试使用:

void bm_PuzzleOne()
{
    //start of 1st puzzle
    BitmapFactory.Options opt = new BitmapFactory.Options();

    //makes a loaded image mutable
    opt.inMutable=true;
    //reduces density to that of screen
    opt.inTargetDensity = this.context.getResources().getDisplayMetrics().densityDpi;
    opt.inScaled=true;

    //makes bitmap half as big
    opt.inSampleSize=2;

    //foregdimg1 = BitmapFactory.decodeResource(getResources(), R.drawable.puzzlepieceprac);//puzzle cut out (42.48MB) +6.15
    mutableforegdimg1 = BitmapFactory.decodeResource(getResources(), R.drawable.puzzlepieceprac,opt)

    compositeImage1 = Bitmap.createBitmap(mutableforegdimg1);//cuts out foreground info into bkgdimage (54.43MB) +6.11

    imgCanvas1 = new Canvas(compositeImage1); //canvas references puzzle cut out image (54.43MB) +0
    imgCanvas1.drawBitmap(croppedBmp, null, new Rect(0, 0, 1500, 2000), paintObject);//places puzzle image on canvas (54.43MB) +0

    img1.setImageBitmap(compositeImage1);

}

替代

void bm_PuzzleOne()
{
    //start of 1st puzzle

    foregdimg1 = BitmapFactory.decodeResource(getResources(), R.drawable.puzzlepieceprac);//puzzle cut out (42.48MB) +6.15
    mutableforegdimg1 = foregdimg1.copy(Bitmap.Config.ARGB_8888, true); //(48.32MB) +5.84

    compositeImage1 = Bitmap.createBitmap(mutableforegdimg1);//cuts out foreground info into bkgdimage (54.43MB) +6.11

    imgCanvas1 = new Canvas(compositeImage1); //canvas references puzzle cut out image (54.43MB) +0
    imgCanvas1.drawBitmap(croppedBmp, null, new Rect(0, 0, 1500, 2000), paintObject);//places puzzle image on canvas (54.43MB) +0

    img1.setImageBitmap(compositeImage1);

}

当你说它们不需要那么大时,我假设你是指在1500x2000绘制的位图...预加载的图像不是1500x2000,我被迫将其扩展到该大小,只是为了使掩蔽的拼图块看起来正确...我的拼图图像的大小是425x601预加载,我被迫扩展拼图图像的大小,以适应我的拼图块的大小...我希望这样能更清楚地解释我的问题...这是一个示例,看起来像这样...http://postimg.org/image/o62dgyw0t/ - user1091368
我已经进行了一次编辑,请检查一下并告诉我它是否有效。 另外,在将mutableforegdimg添加到compositeImage之后,你是否使用了它? 如果没有的话,建议你尝试回收它。 - JeD

2

2

使用向量图形代替图片,可以节省应用程序的内存和存储空间。它得到了很好的支持,并且您可以使用SVG资源。


谢谢Maher,我以前从未使用过向量,但在Droid上尝试后,单单这个优化就让我减少了很多内存占用。很遗憾,在赏金期限到期之前我没有足够的时间测试Zeyad和Smartiz的第三方建议(我同时应付着两份工作,并抽出业余时间来编程),但看起来它们是朝着正确的方向提供解决方案的……JeD感谢你深入的回答,只是那不是我特定问题的解决方案。 - user1091368

1

首先,我应该感谢@JeD的出色回答,但是我认为答案不同。

让我们从基础知识开始...


每个Android设备都有特定的“堆大小”。它从16mg到64mg,甚至在某些设备上达到128mg!(我不确定128mg :D)
因此,每当您在视图中使用drawable时,它会占用特定数量的RAM。
问题:如何计算?!
这是通过drawable的尺寸而不是大小来计算的。例如,我们有两个大小为500 KB的drawable。其中一个尺寸为500 * 500,另一个为700 * 700。尽管两个大小相同,但第二个需要更多的堆空间!公式如下:WIDTH * HEIGHT * 4 BYTE(4来自ARGB,每个像素需要四个字节来存储数据)
到目前为止,您应该明白不能一起使用许多图像!如果要使用LRUCACH,就像@JeD提到的那样,您将失去一些图像,因为您的堆将变满。
问题:那我们该怎么办?!
答案是使用第三方引擎和画布! 游戏引擎如COCOS2D、BOX2D等也是很好的例子,或者使用NDK使您的应用程序无堆栈内存限制! :D

0
Button b[]=new Button[9];
ArrayList list=new ArrayList();
int random;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
    setContentView(R.layout.activity_three);

    final MediaPlayer mediaPlayer = MediaPlayer.create(this,R.raw.b2);

   for(int i=0;i<b.length;i++)
   {

       int id=getResources().getIdentifier("b"+i,"id",getPackageName());
       b[i]=findViewById(id);
       b[i].setOnClickListener(this);

   }

   for(int i=0;i<b.length;i++)
   {
       while(true) {

           random = new Random().nextInt(9);
           Log.d("First=", "" + random);

           if (!list.contains(random)) {

               list.add(random);
               Log.d("List=", "" + list.get(i).toString());

               break;

           }
       }
   }

   for(int i=0;i<b.length;i++)
   {

       if(!list.get(i).toString().equals("0"))
       {

           b[i].setText(list.get(i).toString());

       }
       else
       {

           b[i].setText("");

       }

   }

}


public void back(View view) {


    final MediaPlayer mediaPlayer = MediaPlayer.create(this,R.raw.b2);
    mediaPlayer.start();
    Intent intent = new Intent(three.this,newgame.class);
    startActivity(intent);
    finish();



}

@Override
public void onClick(View v) {

    for(int i=0;i<b.length;i++)
    {

        if(v.getId()==b[i].getId())
        {
            final MediaPlayer mediaPlayer = MediaPlayer.create(this,R.raw.b2);
            mediaPlayer.start();

        }

    }


    if(v.getId()==b[0].getId())
    {
        if (b[1].getText().toString().equals("")) {
            b[1].setText("" + b[0].getText().toString());
            b[0].setText("");
        }

        if (b[3].getText().toString().equals("")) {
            b[3].setText("" + b[0].getText().toString());
            b[0].setText("");
        }
    }

    if(v.getId()==b[1].getId())
    {
        if(b[0].getText().toString().equals(""))
        {
            b[0].setText(""+b[1].getText().toString());
            b[1].setText("");
        }

        if(b[2].getText().toString().equals(""))
        {
            b[2].setText(""+b[1].getText().toString());
            b[1].setText("");
        }

        if(b[4].getText().toString().equals(""))
        {
            b[4].setText(""+b[1].getText().toString());
            b[1].setText("");
        }

    }

    if(v.getId()==b[2].getId())
    {

        if (b[1].getText().toString().equals(""))
        {

            b[1].setText("" + b[2].getText().toString());
            b[2].setText("");

        }

        if (b[5].getText().toString().equals(""))
        {

            b[5].setText("" + b[2].getText().toString());
            b[2].setText("");

        }
    }

    if(v.getId()==b[3].getId())
    {
        if(b[0].getText().toString().equals(""))
        {
            b[0].setText(""+b[3].getText().toString());
            b[3].setText("");
        }

        if(b[4].getText().toString().equals(""))
        {
            b[4].setText(""+b[3].getText().toString());
            b[3].setText("");
        }

        if(b[6].getText().toString().equals(""))
        {
            b[6].setText(""+b[3].getText().toString());
            b[3].setText("");
        }

    }

    if(v.getId()==b[4].getId())
    {
        if(b[1].getText().toString().equals(""))
        {
            b[1].setText(""+b[4].getText().toString());
            b[4].setText("");
        }

        if(b[3].getText().toString().equals(""))
        {
            b[3].setText(""+b[4].getText().toString());
            b[4].setText("");
        }

        if(b[5].getText().toString().equals(""))
        {
            b[5].setText(""+b[4].getText().toString());
            b[4].setText("");
        }

        if(b[7].getText().toString().equals(""))
        {
            b[7].setText(""+b[4].getText().toString());
            b[4].setText("");
        }

    }

    if(v.getId()==b[5].getId())
    {
        if(b[2].getText().toString().equals(""))
        {
            b[2].setText(""+b[5].getText().toString());
            b[5].setText("");
        }

        if(b[4].getText().toString().equals(""))
        {
            b[4].setText(""+b[5].getText().toString());
            b[5].setText("");
        }

        if(b[8].getText().toString().equals(""))
        {
            b[8].setText(""+b[5].getText().toString());
            b[5].setText("");
        }

    }

    if(v.getId()==b[6].getId())
    {
        if (b[7].getText().toString().equals(""))
        {

            b[7].setText("" + b[6].getText().toString());
            b[6].setText("");

        }

        if (b[3].getText().toString().equals(""))
        {

            b[3].setText("" + b[6].getText().toString());
            b[6].setText("");

        }

    }

    if(v.getId()==b[7].getId())
    {
        if(b[4].getText().toString().equals(""))
        {
            b[4].setText(""+b[7].getText().toString());
            b[7].setText("");
        }

        if(b[6].getText().toString().equals(""))
        {
            b[6].setText(""+b[7].getText().toString());
            b[7].setText("");
        }

        if(b[8].getText().toString().equals(""))
        {
            b[8].setText(""+b[7].getText().toString());
            b[7].setText("");
        }

    }

    if(v.getId()==b[8].getId())
    {
        if (b[7].getText().toString().equals(""))
        {

            b[7].setText("" + b[8].getText().toString());
            b[8].setText("");

        }

        if (b[5].getText().toString().equals(""))
        {

            b[5].setText("" + b[8].getText().toString());
            b[8].setText("");

        }

    }


}

public void reset(View view) {

    final MediaPlayer mediaPlayer = MediaPlayer.create(this,R.raw.b2);
    mediaPlayer.start();

    Intent intent = new Intent(three.this,three.class);
    startActivity(intent);
    finish();

}

}


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