0

I am making a puzzle game and every time the user completes the puzzle, a recreate button appears which is simply calling the recreate() method to restart the puzzle activity.

I override onSaveInstanceState because i want to save the image selected for the puzzle and the 4 pieces in case of screen orientation change.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    outState.putParcelable("originalBM", originalBm);
    outState.putParcelable("bm1", bm1);
    outState.putParcelable("bm2", bm2);
    outState.putParcelable("bm3", bm3);
    outState.putParcelable("bm4", bm4);
}

So, when the user clicks the recreate button, the recreate() method is being called which also calls onSaveInstanceState by default because this is how android works and the user will have to play the puzzle with the same image again and again.

I don't want to implement the same code that i have on my onCreate method to select a new random image because this is causing memory leaks and my app crashes after 10-12 recreates.

I simply want it to restart the activity clean and fresh!

Instead of using recreate() inside my recreatePuzzle method, I also also tried this

Intent intent = getIntent();
finish();
startActivity(intent);

But this again is causing my app to crash after 10-12 recreates. It is also causing memory leaks.

So, i believe the best way to do this would be by skipping the Override of saveInstanceState when my recreatePuzzle is being called (if this is possible) or by passing a null Bundle when onSaveInstanceState is being called.

Is there any way to implement any of these solutions above?

Any help would be highly appreciated.

Thank you all in advance.

EDIT:

Full code of my class

package kidsbook.jok.kidsbook;


public class Puzzle extends AppCompatActivity {

private String[] puzzleIMGS;
private String randomPuzzleIMG;
private int corrects = 0, tries = 0;
private ImageView part1, part2, part3, part4;
private TextView piece1, piece2, piece3, piece4;
private Button againButton;
private Bitmap bm1, bm2, bm3, bm4, originalBm;
private Intent i;
private MediaPlayer mp = new MediaPlayer();
private List<Bitmap> parts = new ArrayList<>();
private boolean recreatePuzzle = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_puzzle);

    mp = MediaPlayer.create(getApplicationContext(), R.raw.pop);

    //Select random image
    puzzleIMGS = getResources().getStringArray(R.array.all_animal_imgs);
    randomPuzzleIMG = puzzleIMGS[new Random().nextInt(puzzleIMGS.length)];

    //Get all elements
    againButton = (Button) findViewById(R.id.againPuzzleButton);
    part1 = (ImageView) findViewById(R.id.part1);
    part2 = (ImageView) findViewById(R.id.part2);
    part3 = (ImageView) findViewById(R.id.part3);
    part4 = (ImageView) findViewById(R.id.part4);
    piece1 = (TextView) findViewById(R.id.piece1);
    piece2 = (TextView) findViewById(R.id.piece2);
    piece3 = (TextView) findViewById(R.id.piece3);
    piece4 = (TextView) findViewById(R.id.piece4);

    part1.setOnTouchListener(new MyTouchListener());
    part2.setOnTouchListener(new MyTouchListener());
    part3.setOnTouchListener(new MyTouchListener());
    part4.setOnTouchListener(new MyTouchListener());
    piece1.setOnDragListener(new MyDragListener());
    piece2.setOnDragListener(new MyDragListener());
    piece3.setOnDragListener(new MyDragListener());
    piece4.setOnDragListener(new MyDragListener());

    if(savedInstanceState!=null) {
        Log.i("debug","inside saved instance");
        //Convert randomly selected resource image to bitmap
        originalBm = savedInstanceState.getParcelable("originalBM");
        bm1 = savedInstanceState.getParcelable("bm1");
        bm2 = savedInstanceState.getParcelable("bm2");
        bm3 = savedInstanceState.getParcelable("bm3");
        bm4 = savedInstanceState.getParcelable("bm4");
    } else {
        Log.i("debug","inside null instance");
        //Convert randomly selected resource image to bitmap
        originalBm = BitmapFactory.decodeResource(getResources(), getImageId(this, randomPuzzleIMG));

        //Split bitmap to 4 parts
        bm1 = Bitmap.createBitmap(originalBm, 0, 0, (originalBm.getWidth() / 2), (originalBm.getHeight() / 2));
        bm2 = Bitmap.createBitmap(originalBm, (originalBm.getWidth() / 2), 0, (originalBm.getWidth() / 2), (originalBm.getHeight() / 2));
        bm3 = Bitmap.createBitmap(originalBm, 0, (originalBm.getHeight() / 2), (originalBm.getWidth() / 2), (originalBm.getHeight() / 2));
        bm4 = Bitmap.createBitmap(originalBm, (originalBm.getWidth() / 2), (originalBm.getHeight() / 2), (originalBm.getWidth() / 2), (originalBm.getHeight() / 2));
    }

    //Make the background transparent
    piece1.setBackgroundDrawable(new BitmapDrawable(getResources(), bm1));
    piece1.setAlpha(0.2f);
    piece2.setBackgroundDrawable(new BitmapDrawable(getResources(), bm2));
    piece2.setAlpha(0.2f);
    piece3.setBackgroundDrawable(new BitmapDrawable(getResources(), bm3));
    piece3.setAlpha(0.2f);
    piece4.setBackgroundDrawable(new BitmapDrawable(getResources(), bm4));
    piece4.setAlpha(0.2f);

    //Place parts in an array
    parts.add(bm1);
    parts.add(bm2);
    parts.add(bm3);
    parts.add(bm4);

    //Shuffle the array
    Collections.shuffle(parts);

    //Assign the correct piece tag to each part
    for(int i=0;i<4;i++){
        if(i==1) {
            part1.setImageBitmap(parts.get(i));
            if (parts.get(i).equals(bm1)){
                part1.setTag("piece1");
            } else if (parts.get(i).equals(bm2)){
                part1.setTag("piece2");
            } else if (parts.get(i).equals(bm3)){
                part1.setTag("piece3");
            } else {
                part1.setTag("piece4");
            }
        } else if(i==2){
            part2.setImageBitmap(parts.get(i));
            if (parts.get(i).equals(bm1)){
                part2.setTag("piece1");
            } else if (parts.get(i).equals(bm2)){
                part2.setTag("piece2");
            } else if (parts.get(i).equals(bm3)){
                part2.setTag("piece3");
            } else {
                part2.setTag("piece4");
            }
        } else if(i==3){
            part3.setImageBitmap(parts.get(i));
            if (parts.get(i).equals(bm1)){
                part3.setTag("piece1");
            } else if (parts.get(i).equals(bm2)){
                part3.setTag("piece2");
            } else if (parts.get(i).equals(bm3)){
                part3.setTag("piece3");
            } else {
                part3.setTag("piece4");
            }
        } else {
            part4.setImageBitmap(parts.get(i));
            if (parts.get(i).equals(bm1)){
                part4.setTag("piece1");
            } else if (parts.get(i).equals(bm2)){
                part4.setTag("piece2");
            } else if (parts.get(i).equals(bm3)){
                part4.setTag("piece3");
            } else {
                part4.setTag("piece4");
            }
        }
    }
}

private static int getImageId(Context context, String imageName) {
    return context.getResources().getIdentifier("drawable/" + imageName, null, context.getPackageName());
}

private final class MyTouchListener implements View.OnTouchListener {
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
            ClipData data = ClipData.newPlainText("", "");
            View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
            view.startDrag(data, shadowBuilder, view, 0);
            return true;
        } else {
            return false;
        }
    }
}

class MyDragListener implements View.OnDragListener {

    @Override
    public boolean onDrag(View v, DragEvent event) {

        int action = event.getAction();

        switch (action) {
            case DragEvent.ACTION_DRAG_STARTED:
                // do nothing
                break;
            case DragEvent.ACTION_DRAG_ENTERED:
                break;
            case DragEvent.ACTION_DRAG_EXITED:
                break;
            case DragEvent.ACTION_DROP:
                // Dropped, reassign View to ViewGroup
                View view = (View) event.getLocalState();
                ViewGroup owner = (ViewGroup) view.getParent();

                if(view.getTag().equals(v.getTag())){
                    if(view.getTag().equals("piece1")){
                        owner.removeView(view);
                        useMediaPlayer();
                        piece1.setBackgroundDrawable(new BitmapDrawable(getResources(), bm1));
                        piece1.setAlpha(0.9f);
                        corrects++;
                    } else if (view.getTag().equals("piece2")){
                        owner.removeView(view);
                        useMediaPlayer();
                        piece2.setBackgroundDrawable(new BitmapDrawable(getResources(), bm2));
                        piece2.setAlpha(0.9f);
                        corrects++;
                    } else if (view.getTag().equals("piece3")){
                        owner.removeView(view);
                        useMediaPlayer();
                        piece3.setBackgroundDrawable(new BitmapDrawable(getResources(), bm3));
                        piece3.setAlpha(0.9f);
                        corrects++;
                    } else if (view.getTag().equals("piece4")) {
                        owner.removeView(view);
                        useMediaPlayer();
                        piece4.setBackgroundDrawable(new BitmapDrawable(getResources(), bm4));
                        piece4.setAlpha(0.9f);
                        corrects++;
                    }
                }

                tries++;

                if(corrects==4){
                    finish();
                }

                break;
            case DragEvent.ACTION_DRAG_ENDED:
                break;
            default:
                break;
        }
        return true;
    }
}

public void useMediaPlayer(){
    mp.start();
}

public void againPuzzle(View v){
    recreatePuzzle = true;
    recreate();
}

public void finish(){
    againButton.setVisibility(View.VISIBLE);
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    outState.putParcelable("originalBM", originalBm);
    outState.putParcelable("bm1", bm1);
    outState.putParcelable("bm2", bm2);
    outState.putParcelable("bm3", bm3);
    outState.putParcelable("bm4", bm4);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_games, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item){
    switch(item.getItemId()){
        case android.R.id.home:
            i = new Intent(this, MainActivity.class);
            i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(i);
            return true;
        case R.id.menu_about:
            i = new Intent(this, About.class);
            startActivity(i);
            return true;
        case R.id.menu_help:
            i = new Intent(this, Help.class);
            startActivity(i);
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

@Override
public void onBackPressed() {
    i = new Intent(this, MainActivity.class);
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    startActivity(i);
}
}
Smolikas
  • 326
  • 1
  • 6
  • 19

2 Answers2

0

What about starting the same activity then finishing the previous one ? like :

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

Using finish will help you not having any memoryleak as oncreate() do.

Second option : move the code selecting a puzzle in a new method (like selectNewPuzzle()), then call that method in onCreated and when you need select a new puzzle.

Third option : use a global boolean named like "canSaveInstance" wich is true when onCreate end. Encapsulate the lines saving the instancestate in an if statement checking this boolean, and when you need recreating, put this variable false, recreate as you usually do (so no data is saved, new puzzle is started) then reput it true (to handle config changes). You will need to be carefull when recreating activity : if(savedinstancestate!=null) must become "if(savedinstancestate!=null && canSaveInstance) (because if not you may try to load data which were not saved previously).

last option : (but i'm not really aware on how to do exactly) prevent user from launching multiple time the puzzle activity, then "launch" a new one (the older one will be overrided i think). You may need do some more research to do this one. As other options are easier I won't search for documentation helping doing that.

Feuby
  • 708
  • 4
  • 7
  • I already tried those 3 lines of code you suggested for my recreate() method, and each time i recreate, the memory used is being increased by 10MB approx. This causes a need of more than 120MB of memory after 10-12 recreates and the application then crashes. – Smolikas Apr 23 '17 at 22:52
  • I have tried using a global boolean variable as well to exclude saving any data onSaveInstanceState method but again, this will return a not null bundle in onCreate method which will result the entry to the !=null part of my code in onCreate method and there, the required variables will not have any values (as the bundle will be empty) and this will cause my app to crash. – Smolikas Apr 23 '17 at 22:58
  • what about the second option ? That's also easy to set – Feuby Apr 24 '17 at 00:30
0

So i finally came up with a solution. What i was missing was to release the bitmap memory of my activity and that was what was causing memory leak. So, finally, me recreate() method looks like

public void againPuzzle(View v){
    originalBm.recycle();
    bm1.recycle();
    bm2.recycle();
    bm3.recycle();
    bm4.recycle();
    originalBm = null;
    bm1 = null;
    bm2 = null;
    bm3 = null;
    bm4 = null;
    Intent intent = getIntent();
    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
    finish();
    startActivity(intent);
}
Smolikas
  • 326
  • 1
  • 6
  • 19