9

I notice that other puzzle apps from the google play store can go up to as many as 400 separate movable puzzle pieces

I have been attempting to learn how to at least take an image that will represent my puzzle, Crop certain sections and mask the image space that is left with a puzzle piece design in order to create my individual puzzle pieces

I want to max out with 20 pieces for my app but so far according to my android studio memory log once bitmap factory is done creating one puzzle piece I’m using up around 18mb of memory, after 20 pieces are created along with all the other functions of the app I’m using up 400+mb of memory which I must use “largeHeap=true” to keep from running out of memory, but I’m so close to exceeding those higher limits that the app is super sluggish and enough animated activity will inevitably crash the app

I would love to know what those other play store puzzle apps are doing that I’m not

any input is greatly appreciated

FYI I’m using PNG24 for my images and the dimension on my test image is 556x720

Here is an example if I were to just create one animated-able puzzle piece image

@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);
}

}

This is what it looks like when I load 5 images

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

user1091368
  • 359
  • 1
  • 4
  • 17

5 Answers5

3

Alright let's see.

First of all you don't need puzzle pieces to be this big, so you can scale them down:

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

To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize to true in your BitmapFactory.Options object. For example, an image with resolution 2048x1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration of ARGB_8888)

Secondly you should try loading Bitmaps in an AsyncTask to prevent the application from freezing.

Thirdly you can cache your Bitmaps using LruCache to speed things up

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);
}

That should be enough but in case it isn't here are all sources and then some extra: http://developer.android.com/training/displaying-bitmaps/index.html

You can also try using

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);

}

instead of

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);

}
Jan
  • 1,504
  • 10
  • 15
  • when you say I don't need them that big I assume you are referring to the bitmap drawn at 1500x2000 ... The preloaded image is not 1500x2000 I’m forced to expand it to that size only to have the masked puzzle piece look right ... the size of my puzzle image is 425x601 preloaded, I’m forced to expand the size of the puzzle image for it to fit the size of my puzzle piece ... I hope this makes sense and adds more clarity to my current hang up ... Heres an example of what it looks like ...http://postimg.org/image/o62dgyw0t/ – user1091368 Mar 03 '16 at 03:56
  • Made an edit, check it out and tell me if it works. Also, do you use mutableforegdimg after adding it to compositeImage? If not you should try to recycle it – Jan Mar 03 '16 at 09:58
2

I suggest using an image loading library such as Glide or Picasso, which handles all the heavy lifting for you.

https://github.com/bumptech/glide

http://square.github.io/picasso/

Zeyad Gasser
  • 1,516
  • 21
  • 39
2

Simply just use vectors instead of images.. you will save much memory for running app and storage. Its supported very well. and you can use SVG resources

Maher Abuthraa
  • 17,493
  • 11
  • 81
  • 103
  • Thanks Maher, I had never used vectors before and after playing with it in droid I'm shaving off a boat load of memory with that alone, It pains me that I didn't have enough time to test out Zeyad's and Smartiz's 3rd party suggestions before the bounty period was up [I'm juggling 2 jobs and programming in my spare time] but it looked like they were solutions headed in the right direction ... JeD thanks for your in depth answer it just wasn't the solution for my specific issue – user1091368 Mar 10 '16 at 18:21
1

First of all I should thank @JeD for his great answer, BUT I should mention that I think the answer is different.

Lets begin from the basics...


Each android device has an specific "heap size". it begin from 16mg to 64mg and even 128mg in some devices! (im not sure about 128 mg :D)

So whenever you use a drawable in your view, it takes a specific amount of ram.

Question: How is it calculated?!

Its by the drawables dimension, not the size. For example we have two drawables with 500 KBs of size. One of them has the dimensions 500*500, the other has 700*700 The second one takes more heap although both sizes were the same! The formula is this: WIDTH*HEIGHT*4 BYTE (4 comes from ARGB, it takes four bytes for each pixel to store data)

By now, you should understand that you cant use MANY images together! If you want to use LRUCACH as @JeD mentioned, you would lose some of your images as your heap would get full.

Question : So what should we do?!

The answer is using third party engines and canvas! Game engines like COCOS2D,BOX2D,etc... are also good examples or also using NDK which makes your app HEAP FREE!!! :D

ooransoy
  • 785
  • 5
  • 10
  • 25
Omid Heshmatinia
  • 5,089
  • 2
  • 35
  • 50
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();

}

}