azdragon2 azdragon2 - 1 month ago 16
Android Question

Android - Device Level Memory Leak

Spent 3 days on this issue and I'm stumped, could reallly use your help on this.

My app uses a single activity that contains a fragment manager. In its current state, I've designed it to only have a single fragment attached at any one time. I call:

getSupportFragmentManager().beginTransaction().replace(R.id.main_fragment, mCurrent).commit();


to remove the existing fragment and replace it with the new one. The fragments are not being stored anywhere else. (Just mCurrent and the transaction manager which get replaced when the next fragment is opened)

The issue that occurs is not an OutOfMemory exception, but instead the entire app restarts because the device itself runs out of memory. The logs says nothing except stuff like DeadObjectException and:

9-17 11:34:14.742: W/InputDispatcher(341): channel ~ Consumer closed input channel or an error occurred. events=0x9

09-17 11:34:14.742: E/InputDispatcher(341): channel ~ Channel is unrecoverably broken and will be disposed!


These are coming from the device logs and not my app logs. And they don't provide enough information for me to trace what is causing them. The memory from the app is never released and the tablet becomes unusable from that point.

I've used all the tools I know available to analyze what the memory leak would be but nothing stands out. I used MAT (MemoryAnalyzerTool) and the Eclipse DDMS tools to figure out what may be going wrong. MAT reports between 8MB-16MB of total memory in use after I visit many different fragments. I look at the Leaks Report and nothing seems out of the ordinary. About 3MB for Imageviews, 3 MB for Bitmaps, and 8MB for other. In DDMS, the heap says I'm using ~50% of the app's allotted size. When I look at the Settings at the running processes, each Fragment I visit makes the memory amount from my app fluctuate between 30-140MB however the device itself never decreases in memory, hence why the device runs out of memory. Even when the app is completely closed, exited, destroyed, the memory is never released. All 759MB are used even though the sum of all the current running apps is about 200-300MB.

My assumption is that it is either holding on to the fragment itself in memory, or something contained within these fragments even though my app isn't. I call GC after each fragment swap.

I'm using the Samsung Galaxy Tab 2 10.1", but the same issue occurs on Motorola Xoom.

Questions:


  1. Has anyone experienced this kind of behavior?

  2. Can anyone provide me any direction as to other tools to help me research a possible memory leak?

  3. Is there a system process that I can look at to show me in detail how the device memory is being allocated?



Thanks for your time.

Answer

I found the primary cause of my memory leak. It was around executing a shell command by creating a Process object and opening up input and output streams. I used this to detect rooted devices. I'm not enough of an expert on the Process class to explain why this is causing leaks (I'm going to read up on it now), but if you're using code similar to this I'd be alert to this issue.

public static enum SHELL_COMMAND
{
    check_su_binary(new String[] { "/system/xbin/which", "su" }), ;
    String[] command;

    SHELL_COMMAND(String[] command)
    {
        this.command = command;
    }
}

/**
 * @param shellCommand
 * @return
 */
public List<String> executeCommand(SHELL_COMMAND shellCommand)
{
    //this code was causing my memory leak
    try
    {
        String line = null;
        List<String> fullResponse = new ArrayList<String>();
        Process localProcess = Runtime.getRuntime().exec(shellCommand.command);

        OutputStreamWriter writer = new OutputStreamWriter(localProcess.getOutputStream());
        BufferedWriter bufferedWriter = new BufferedWriter(writer);

        InputStreamReader reader = new InputStreamReader(localProcess.getInputStream());
        BufferedReader in = new BufferedReader(reader);

        while ((line = in.readLine()) != null)
        {
            fullResponse.add(line);
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

    try
    {
        bufferedWriter.close();
        bufferedWriter = null;
        writer.close();
        writer = null;

        in.close();
        in = null;
        reader.close();
        reader = null;
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }

    return fullResponse;
}