Petar-Krešimir
Petar-Krešimir

Reputation: 464

How to lower my app's memory usage?

I have created an app that fetches data (job ads) from an XML file on my server and displays those job ads in my application. When I checked my app, it was using 30-50mb which I believe is too much for such a simple app. I tried running it on some lower end devices (24mb heap size) and it works okay, a bit laggy but no OOM or any other crashes, so I don't think it's a memory leak.

I'm using global arrays with an initial size of 100 each, and once the XML file is loaded, arrays get resized to the number of nodes in the XML file.

Global variables

private final Integer MAX_SIZE = 100;
    public String[] ID = new String[MAX_SIZE];
    public String[] Category = new String[MAX_SIZE];
    public String[] Title = new String[MAX_SIZE];
    public String[] Content = new String[MAX_SIZE];
    public String[] Link = new String[MAX_SIZE];
    public Integer[] JobID = new Integer[MAX_SIZE];
    public String[] nID = new String[MAX_SIZE];
    public String[] nCategory = new String[MAX_SIZE];
    public String[] nTitle = new String[MAX_SIZE];
    public String[] nContent = new String[MAX_SIZE];
    public String[] nLink = new String[MAX_SIZE];
    public int[] nJobID = new int[MAX_SIZE];
    public int[] nExists = new int[MAX_SIZE];

This is the code I use to fetch the XML and load entries into variables

private class LOADXML extends AsyncTask<String, Void, Void> {

        @Override
        protected void onPreExecute() {
            if (swipeLayout != null) {
                if (!swipeLayout.isRefreshing()) {
                    ((MainActivity) getActivity()).load();
                }
            } else {
                ((MainActivity) getActivity()).load();
            }

            Arrays.fill(((MainActivity) getActivity()).jobCOUNT, 0);
        }

        @Override
        protected Void doInBackground(String... urls) {
            if (getActivity() == null) {
                cancel(true);
            }
            if (!((MainActivity) getActivity()).fromNotif && ((MainActivity) getActivity()).refresh) {
                try {
                    XMLParser parser = new XMLParser();
                    String xml = parser.getXmlFromUrl(URL); // getting XML
                    Document doc = parser.getDomElement(xml); // getting DOM element

                    Context cx = getActivity();

                    WriteXMLToFile(xml, cx);


                    NodeList nl = doc.getElementsByTagName(KEY_JOB);
                    listSize = 0;

                    SetArrayLength(nl.getLength());

                    // looping through all item nodes <item>
                    for (int i = 0; i < nl.getLength(); i++) {
                        if (getActivity() == null) {
                            cancel(true);
                        }
                        Element e = (Element) nl.item(i);

                        listSize += 1;
                        setGlobalVars(listSize - 1, parser.getValue(e, KEY_ID), parser.getValue(e, KEY_CATEGORY), parser.getValue(e, KEY_TITLE), parser.getValue(e, KEY_CONTENT), parser.getValue(e, KEY_LINK), parser.getValue(e, KEY_JOBID));
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return null;
        }


        @Override
        protected void onPostExecute(Void result) {
            Activity activity = getActivity();
            if (activity != null && !isCancelled()) {
                ((MainActivity) getActivity()).unload();
                ((MainActivity) getActivity()).refresh = false;

                updateCountVar();
                updateList();
                if (swipeLayout.isRefreshing()) {
                    LinearLayout container = (LinearLayout) vw.findViewById(frag_container);
                    container.removeAllViews();

                    if (swipeLayout != null) {
                        swipeLayout.setRefreshing(false);
                        isRefreshing = false;
                    }

                    showJobBox(li, vw);
                } else {
                    showJobBox(li, vw);
                }
            }
        }
    }

And this is the part of code that is in charge of populating the fragment with data

listSize = ((MainActivity) getActivity()).nID.length;

            for (Integer i = 0; i < listSize; i++) {
                if (filterEntry(i) && (Integer.valueOf(((MainActivity) getActivity()).nJobID[i]) != null)) {
                    // Layout params
                    @SuppressLint("InflateParams") View item_layout = inflater.inflate(R.layout.job_box, null, false);
                    LinearLayout container = (LinearLayout) view.findViewById(frag_container);
                    TextView category = (TextView) item_layout.findViewById(R.id.jobBox_category);
                    TextView title = (TextView) item_layout.findViewById(R.id.jobBox_title);
                    TextView description = (TextView) item_layout.findViewById(R.id.jobBox_description);
                    TextView jobBtn = (TextView) item_layout.findViewById(R.id.jobBox_button);
                    TextView jobIDbox = (TextView) item_layout.findViewById(R.id.jobIDbox);
                    ImageButton imgBtn = (ImageButton) item_layout.findViewById(R.id.imageButton);
                    TextView newLabel = (TextView) item_layout.findViewById(R.id.jobBox_new);

                    Integer nExistsInteger = ((MainActivity) getActivity()).nExists[i];

                    if (nExistsInteger == 0) {
                        newLabel.setVisibility(View.VISIBLE);
                    }
                    // ##############################################
                    int id = i + 10000;

                    item_layout.setId(id);

                    category.setText(((MainActivity) getActivity()).nCategory[i]);
                    title.setText(((MainActivity) getActivity()).nTitle[i]);
                    description.setText(((MainActivity) getActivity()).nContent[i]);
                    Integer ido = ((MainActivity) getActivity()).nJobID[i];
                    jobIDbox.setText("Šifra oglasa: " + String.valueOf(ido));

                    final Integer a = i;
                    jobBtn.setOnClickListener(new View.OnClickListener() {

                        @Override
                        public void onClick(View view) {
                            String url = ((MainActivity) getActivity()).Link[a];
                            Intent i = new Intent(Intent.ACTION_VIEW);
                            i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
                            i.setData(Uri.parse(url));
                            startActivity(i);
                        }
                    });

                    imgBtn.setOnClickListener(new View.OnClickListener() {

                        @Override
                        public void onClick(View view) {
                            Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
                            sharingIntent.setType("text/plain");
                            sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Studentski posao");
                            sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, ((MainActivity) getActivity()).nTitle[a]
                                    + " - " + ((MainActivity) getActivity()).nLink[a]
                                    + " (Via http://bit.ly/StudentServis)");
                            startActivity(Intent.createChooser(sharingIntent, "Podijeli koristeći"));
                        }
                    });

                    RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
                    if (i > 0) {
                        params1.addRule(RelativeLayout.ALIGN_BOTTOM, id - 1);
                        item_layout.setLayoutParams(params1);
                    } else {
                        item_layout.setPadding(10, 10, 10, 10);
                    }

                    container.addView(item_layout);
                    if (i == listSize - 1) {
                        @SuppressLint("InflateParams") View item_layout_dummy = inflater.inflate(R.layout.dummy, null);
                        container.addView(item_layout_dummy);
                    }
                    ((MainActivity) getActivity()).ID[i] = ((MainActivity) getActivity()).nID[i];
                    ((MainActivity) getActivity()).Category[i] = ((MainActivity) getActivity()).nCategory[i];
                    ((MainActivity) getActivity()).Title[i] = ((MainActivity) getActivity()).nTitle[i];
                    ((MainActivity) getActivity()).Content[i] = ((MainActivity) getActivity()).nContent[i];
                    ((MainActivity) getActivity()).Link[i] = ((MainActivity) getActivity()).nLink[i];
                    ((MainActivity) getActivity()).JobID[i] = ((MainActivity) getActivity()).nJobID[i];
                }
            }

I made sure that when the fragment is switched (to a different category), if data has already been loaded, LOADXML doInBackground is skipped.

My question is, is there a more efficient and memory friendlier way to achieve this? This is my first app that has dynamic content.

Upvotes: 1

Views: 87

Answers (1)

Eugen Pechanec
Eugen Pechanec

Reputation: 38243

Phase 1) Use objects.

Ditch arrays. The way you use them is ineffective, the system can do much work for you. You will at least half the memory used.

In your activity instead of numerous arrays (duplicated also) have just one list:

private ArrayList<Job> jobs = new ArrayList<Job>();

Along with it create a Holder pattern class for your jobs.

public static class Job {
    public int jobId;
    public String id, category, title, content, link;
    public boolean exists;
}

The above saves you from ineffectively managing your data using multiple arrays.

Now rewrite your XML loading task to load all jobs into the list in the background. Only when that is done start updating views.

private class LoadXml extends AsyncTask> {

@Override
protected void onPreExecute() {
    //
}

@Override
protected List<Job> doInBackground(String... urls) {
    try {
        final List<Job> jobs = new ArrayList<Job>();

        // this method is written in pseudo code as I don't know anything about parsing XML

        // assume one url containing all jobs
        String xml = getXmlFromUrl(urls[0]);

        // loop through each Job element
        for(Element e : doc) {
            // create a new Job instance
            Job job = new Job();

            // load the Job data
            job.id = e.getAttribute("id");
            // etc.

            // add the Job to the list
            jobs.add(job);
        }

        // return Job list
        return jobs;
    } catch (Exception ex) {
        ex.printStackTrace();

        // if anything fails, return null to indicate
        return null;
    }
}

@Override
protected void onPostExecute(List<Job> result) {
    // send the list to activity and let it handle it
    ((MainActivity)getActivity()).updateJobList(result);

    // MainActivity.updateJobList() will contain pretty much what was here.
}

}

Phase 2) Limit data usage

You do not need to download all your jobs at once. Download ten, when the user reaches end of the list allow him to download another ten. But that topic is beyond this post.

Upvotes: 2

Related Questions