Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I make a puzzle app in droid without running out of memory?

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

like image 219
user1091368 Avatar asked Feb 29 '16 18:02

user1091368


2 Answers

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

}
like image 166
JeD Avatar answered Oct 07 '22 21:10

JeD


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

like image 37
Maher Abuthraa Avatar answered Oct 07 '22 22:10

Maher Abuthraa