1

Now I have almost finished my app. To tell the truth and I am with the design of the app. I'm getting images to make it look prettier. And I've run into a problem. First I want to explain what makes the app, so they can better understand the problem.

The app is a game of questions, the answers are true or false. Each question has an image. Each question is a fragment that will change with fragmentManager.

The application questions without images works perfectly. But adding images, the application when it reaches the last question stops and closes automatically.

1- Using a virtual machine with Android 2.1, the application does not stop, but if you are getting slower. 2- Using a mobile xperia Z2 with Android 4.4.4 application to get to the question number 10 stops and closes.

I think the problem is the Managing Memory Bitmap.

But how to implement the Managing Memory Bitmap to clean the memory used by the images.

The main code of the first group of questions is:

public class Grp1Fragment extends Fragment {

    private int ContArrayAsk = 0;
    private int ContRight = 0;
    private int ContFailed= 0;
    private int ContArrayResults =0;
    private String msg = "";

    private Button buttonTrue;
    private Button buttonFalse;
    private Button buttonNextAsk;
    private Button buttonShareScore;

    private View view;



    String[] arrayFragmentAResultsGrp1 = new String[]{"false","false","false","true","false","true","true","true","false","true",};

    private Fragment[] fragmentsChangeAsk = new Fragment[]{
                                                    new Grp1FragmentP1(),
                                                    new Grp1FragmentE1(),
                                                    new Grp1FragmentP2(),
                                                    new Grp1FragmentE2(),
                                                    new Grp1FragmentP3(),
                                                    new Grp1FragmentE3(),
                                                    new Grp1FragmentP4(),
                                                    new Grp1FragmentE4(),
                                                    new Grp1FragmentP5(),
                                                    new Grp1FragmentE5(),
                                                    ...

                                };

    public Grp1Fragment() {
        // Required empty public constructor


    }


    @Override
    public void onActivityCreated(Bundle savedInstanceState){
        super.onActivityCreated(savedInstanceState);

        android.support.v4.app.FragmentManager managerAsk = getActivity().getSupportFragmentManager();
        managerAsk.beginTransaction()
                .add(R.id.fragmentaskGRP1, fragmentsChangeAsk[0])
                .add(R.id.fragmentaskGRP1, fragmentsChangeAsk[1])
                ...
                .hide(fragmentsChangeAsk[1])
                .hide(fragmentsChangeAsk[2])
        ...
                .commit();
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.fragment_grp1, container, false);
        return view;
    }


    @Override
    public void onViewCreated(View view, Bundle savedInstanceState){
        super.onViewCreated(view, savedInstanceState);


        buttonTrue = (Button) view.findViewById(R.id.buttontruegpr1);
        if(buttonTrue != null){
            buttonTrue.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    if (arrayFragmentAResultsGrp1[ContArrayResults].equals("true")) {
                        Toast.makeText(getActivity(), "Respuesta correcta", Toast.LENGTH_SHORT).show();
                        ContRight++;
                        ContArrayAsk++;
                        ContArrayAsk++;
                        ContArrayResults++;
                        setContent(ContArrayAsk);
                        validarpantallafinal();
                    }else{
                        Toast.makeText(getActivity(), "Respuesta incorrecta", Toast.LENGTH_SHORT).show();
                        ContFailed++;
                        ContArrayAsk++;
                        ContArrayResults++;
                        setContent(ContArrayAsk);
                        buttontrueorfalsedisabled();
                        validarpantallafinal();
                    }
                }
            });
        }


        buttonFalse = (Button) view.findViewById(R.id.buttonfalsegpr1);
        if(buttonFalse != null) {
            buttonFalse.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    if (arrayFragmentAResultsGrp1[ContArrayResults].equals("false")) {
                        Toast.makeText(getActivity(), "Respuesta correcta", Toast.LENGTH_SHORT).show();
                        ContRight++;
                        ContArrayAsk++;
                        ContArrayAsk++;
                        ContArrayResults++;
                        setContent(ContArrayAsk);
                        validarpantallafinal();
                    } else {
                        Toast.makeText(getActivity(), "Respuesta incorrecta", Toast.LENGTH_SHORT).show();
                        ContFailed++;
                        ContArrayAsk++;
                        ContArrayResults++;
                        setContent(ContArrayAsk);
                        buttontrueorfalsedisabled();
                        validarpantallafinal();
                    }
                }
            });
        }


        buttonNextAsk = (Button) view.findViewById(R.id.buttonnextaskgrp1);
        buttonNextAsk.setEnabled(false);
        buttonNextAsk.setVisibility(View.INVISIBLE);
        if(buttonFalse != null) {
            buttonNextAsk.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    //Toast.makeText(getActivity(), "Ha pulsado el botón siguiente pregunta", Toast.LENGTH_LONG).show();
                    buttontrueorfalseenabled();
                    ContArrayAsk++;
                    setContent(ContArrayAsk);
                    validarpantallafinal();
                }
            });
        }


        buttonShareScore = (Button) view.findViewById(R.id.buttonsharescoregrp1);
        buttonShareScore.setEnabled(false);
        buttonShareScore.setVisibility(View.INVISIBLE);
        if(buttonShareScore != null) {
            buttonShareScore.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    msg = msg.concat(getString(R.string.message_share_score_grp1) +
                                                "\nPreguntas acertadas: "+ Integer.toString(ContRight) +
                                                "\nPreguntas falladas: " + Integer.toString(ContFailed) +
                                                "\n" + getString(R.string.message_share));
                    Intent intent = new Intent();
                    intent.setAction(Intent.ACTION_SEND);
                    intent.putExtra(Intent.EXTRA_TEXT, msg);
                    intent.setType("text/plain");
                    startActivity(Intent.createChooser(intent,
                            getString(R.string.action_share)));
                    //Toast.makeText(getActivity(), "Ha pulsado el botón compartir resultados\nAciertos:" + Integer.toString(ContRight) + "\nFallos:" + Integer.toString(ContFailed), Toast.LENGTH_LONG).show();
                }
            });
        }

    }

    public void buttontrueorfalseenabled(){
        buttonTrue.setEnabled(true);
        buttonTrue.setVisibility(View.VISIBLE);
        buttonFalse.setEnabled(true);
        buttonFalse.setVisibility(View.VISIBLE);
        buttonNextAsk.setEnabled(false);
        buttonNextAsk.setVisibility(View.INVISIBLE);
    }

    public void buttontrueorfalsedisabled(){
        buttonTrue.setEnabled(false);
        buttonTrue.setVisibility(View.INVISIBLE);
        buttonFalse.setEnabled(false);
        buttonFalse.setVisibility(View.INVISIBLE);
        buttonNextAsk.setEnabled(true);
        buttonNextAsk.setVisibility(View.VISIBLE);
    }

    public void validarpantallafinal(){
        if (ContArrayAsk == fragmentsChangeAsk.length-1){
            buttonTrue.setEnabled(false);
            buttonTrue.setVisibility(View.INVISIBLE);
            buttonFalse.setEnabled(false);
            buttonFalse.setVisibility(View.INVISIBLE);
            buttonNextAsk.setEnabled(false);
            buttonNextAsk.setVisibility(View.INVISIBLE);
            buttonShareScore.setEnabled(true);
            buttonShareScore.setVisibility(View.VISIBLE);
            TextView score = (TextView) view.findViewById(R.id.textScoregrp1);
            score.setText("Felicidades, ha terminado las preguntas del apartado de Historia.\n"+
                           "Las preguntas acertadas son: " + Integer.toString(ContRight) +
                           "\nLas preguntas falladas son: " + Integer.toString(ContFailed));

        }
    }

    public void setContent(int index){

        Fragment toHide1 = null;
        Fragment toHide2 = null;
        Fragment toHide3 = null;
        Fragment toHide4 = null;
        Fragment toHide5 = null;
        Fragment toHide6 = null;
        Fragment toHide7 = null;
        Fragment toHide8 = null;
        Fragment toHide9 = null;
        Fragment toHide10 = null;
        Fragment toHide11 = null;
        Fragment toHide12 = null;
        Fragment toHide13 = null;
        Fragment toHide14 = null;
        Fragment toHide15 = null;
        Fragment toHide16 = null;
        Fragment toHide17 = null;
        Fragment toHide18 = null;
        Fragment toHide19 = null;
        Fragment toHide20 = null;
        Fragment toShow = null;

        switch (index){
            case 0:
                toHide1 = fragmentsChangeAsk[1];
                toHide2 = fragmentsChangeAsk[2];
                toHide3 = fragmentsChangeAsk[3];
                toHide4 = fragmentsChangeAsk[4];
                toHide5 = fragmentsChangeAsk[5];
                toHide6 = fragmentsChangeAsk[6];
                toHide7 = fragmentsChangeAsk[7];
                toHide8 = fragmentsChangeAsk[8];
                toHide9 = fragmentsChangeAsk[9];
                toHide10 = fragmentsChangeAsk[10];
                toHide11 = fragmentsChangeAsk[11];
                toHide12 = fragmentsChangeAsk[12];
                toHide13 = fragmentsChangeAsk[13];
                toHide14 = fragmentsChangeAsk[14];
                toHide15 = fragmentsChangeAsk[15];
                toHide16 = fragmentsChangeAsk[16];
                toHide17 = fragmentsChangeAsk[17];
                toHide18 = fragmentsChangeAsk[18];
                toHide19 = fragmentsChangeAsk[19];
                toHide20 = fragmentsChangeAsk[20];
                toShow = fragmentsChangeAsk[0];
                break;
            case 1:
                toHide1 = fragmentsChangeAsk[0];
                toHide2 = fragmentsChangeAsk[2];
                ...
        case 20:
        ...
        }
        android.support.v4.app.FragmentManager managerAsk = getActivity().getSupportFragmentManager();
        managerAsk.beginTransaction()
                .hide(toHide1)
                .hide(toHide2)
                ...
                .show(toShow)
                .commit();

    }


}

Example of how to show the question with the image:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:id="@+id/fragment_grp1_e1">


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/GRP1error1"
        android:id="@+id/textViewGrp1e1"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        style="@style/textAsk"/>

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:contentDescription="@string/impask"
        android:src="@mipmap/img1"
        android:layout_below="@+id/textViewGrp1e1"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

In Android Studio you can see the used memory. When I run the app it uses 188Mb. When the app is used soon reaches 190MB.

The images we use weigh no more than 300kb. And the weight of the installed app is 6Mb.

Thank you very much for your help

Kushal
  • 8,100
  • 9
  • 63
  • 82
Natlum
  • 160
  • 12
  • What does the stack trace say ? Does it say Out Of Memory ? – inmyth Apr 22 '15 at 11:45
  • Where do you load images? I see no code here for how an actual image is displayed, unless it's just specified here `android:src="@mipmap/img1"` which doesn't cache them when they are not needed (I am guessing, anyways): http://stackoverflow.com/questions/477572/strange-out-of-memory-issue-while-loading-an-image-to-a-bitmap-object - either way, you should be resizing your bitmaps before displaying them, I recommend using Picasso library for loading images. – EpicPandaForce Apr 22 '15 at 11:47
  • 1
    Its due to ImageView. Every your fragment uses a big ImageView with size of 200x200 dp, every such ImageView takes about 300kb in RAM for mdpi screen. I suggest you not to create all your fragments at start but switching them one by one so the only 1 fragment will be created at a time. – Stan Apr 22 '15 at 11:47

2 Answers2

3

Your problem is that you actually have all 20 fragments in memory at all times, which all have a large bitmap in them.

    android.support.v4.app.FragmentManager managerAsk = getActivity().getSupportFragmentManager();
    managerAsk.beginTransaction()
            .hide(toHide1)
            .hide(toHide2)
            ...
            .show(toShow)
            .commit();

The program should be structured so that there's only one Fragment created at all times, considering you have need only for the one Fragment that is displayed. In fact, I'm not even sure if there should be more than two fragments (grpE and grpP), it depends on whether they truly do change in layout, or only in content.

If that cannot be done, at least use a mechanism like FragmentStatePagerAdapter so that the fragments you don't need are not actually created, occupying memory.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
2

You are using Many Fragments and each have layout with ImageView.

Your approach seems like :

Every question is new Fragment and has new layout. You are showing that Fragment when question displays.

Why your approch is not optimized ?

  • How many time will you add new Fragment when you add new question in questionset. Your current approach is static and you need to add manually a fragment and layout for new question
  • Android DVM spares more heap size when fragment comes in application. You are initializing many fragment in your application. This uses much more Heap size than necessary (approximately 199MB)

Solution to above points :

  • Better use Less fragment and corresponding layout. When you need to display next question, just replace current layout textfield and imagefield with next question information. This way you do not require to add fragments manually when you want to display new question
  • Nullify any ImageViews which are not in use. This way you will prevent memory leaks For more way to prevent memory leak : an excellent video from Google Engineers Google IO Memory optimization will definitely help you
Kushal
  • 8,100
  • 9
  • 63
  • 82
  • Thank you very much Kushal, I changed the fragments by TextView that will change with setText. I have also put Imagevew that will change with setImageResource. The application has decreased memory usage significantly. Now hardly take up any memory. I am very grateful. On this website I am learning a lot. Thanks also to: EpicPandaForce, Stan, inmyth. – Natlum Apr 22 '15 at 15:52
  • Thank you.. i am happy that your problem is solved.. enjoy learning – Kushal Apr 22 '15 at 16:17