Eight Possibilities of Android Memory Leakage (Part I)

Keywords: Android Java Mobile

Java is a kind of garbage collection language. Its advantage is that developers do not need to manage memory allocation deliberately. It reduces the possibility of application crashing due to local fault, and prevents unreleased memory from crashing heap, so it is safer to write code.

Unfortunately, there are still many logical leaks in Java that can easily lead to memory leaks. If you're not careful, your Android application can easily waste unreleased memory, resulting in an out-of-memory (OOM) error.

Traditional memory leak is caused by forgetting to release allocated memory. (Translator's Note: Cursor forgot to close, etc.)
The reason for logical memory leak is that when the application no longer needs the object, all references to the object have not yet been released.

If the object is strongly referenced, the garbage collector cannot reclaim the object in memory.

In Android development, the most vulnerable memory leak problem is Context . such as Activity Context contains a large number of memory references, such as View Hierarchies and other resources. Once Context is leaked, it also means leaking all the objects it points to. Android machines have limited memory, and too many memory leaks can easily lead to OOM.

Detecting logical memory leaks requires subjective judgment, especially when the object's life cycle is not clear. Fortunately, Activity has a clear definition life cycle It's easy to find the cause of the leak. Activity.onDestroy() As the end of Activity's life, it should be destroyed programmatically, or the Android system needs to reclaim that memory.
If this method is executed and there is still a strong reference holding the Activity in the stack, the garbage collector can't mark it as recovered memory, and our original purpose is to recover it!
The result is that Activity survives beyond its lifecycle.

Activity is a heavyweight object and should be handled by the Android system. However, logical memory leaks always happen inadvertently. Translator's Note: I tried an activity that caused a 20M memory leak. In Android, there are two pitfalls that can lead to potential memory leaks:

  • The static variable of the process-global. This is a monster that ignores the state of the application and holds strong references to Activity.
  • Threads that live outside the activity lifecycle. There is no empty strong reference to Activity.

Check if you have encountered the following situations.

Static Activities

The static Activity variable is defined in the class, and the current running Activity instance is assigned to the static variable.
If this static variable is not emptied at the end of the Activity life cycle, memory leaks occur. Because the static variable runs through the life cycle of the application, the leaked Activation will always exist in the application process and will not be recycled by the garbage collector.

     static Activity activity;

    void setStaticActivity() {
      activity = this;
    }

    View saButton = findViewById(R.id.sa_button);
    saButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        setStaticActivity();
        nextActivity();
      }
    });

Memory Leak 1 - Static Activity

Static Views

Similar scenarios occur in singleton mode. If Activity is often used, it is useful to save an instance in memory. As mentioned earlier, mandatory extension of Activity's life cycle is quite dangerous and unnecessary, and it can't be done anyway.

Special case: If a View initialization consumes a lot of resources and remains unchanged throughout the life cycle of an Activity, it can be turned into static and loaded into a View tree. Hierachy),Like this When Activity is destroyed, resources should be released. (Translator's Note: The sample code does not release memory, so put this static. null is enough for view, but this static view method is not recommended.)

    static view;

    void setStaticView() {
      view = findViewById(R.id.sv_button);
    }

    View svButton = findViewById(R.id.sv_button);
    svButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        setStaticView();
        nextActivity();
      }
    });

Memory Leak 2 - Static View

Inner Classes

Continue, assuming that there is one in Activity Internal class This improves readability and encapsulation. If we create an internal class and hold a reference to a static variable, congratulations, memory leaks are not far from you.

       private static Object inner;

       void createInnerClass() {
        class InnerClass {
        }
        inner = new InnerClass();
    }

    View icButton = findViewById(R.id.ic_button);
    icButton.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
            createInnerClass();
            nextActivity();
        }
    });

Memory Leak 3 - Inner Class

One of the advantages of internal classes is that they can access external classes. Unfortunately, the cause of memory leaks is that internal classes hold strong references to external class instances.

Anonymous Classes

Similarly, anonymous classes maintain references to external classes. So memory leaks can easily occur. When you define anonymous AsyncTsk in Activity
. When an asynchronous task performs a time-consuming task in the background, Activity is unfortunately destroyed, and the Active instance held by AsyncTask will not be reclaimed by the garbage collector until the asynchronous task ends.

    void startAsyncTask() {
        new AsyncTask<Void, Void, Void>() {
            @Override protected Void doInBackground(Void... params) {
                while(true);
            }
        }.execute();
    }

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    View aicButton = findViewById(R.id.at_button);
    aicButton.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
            startAsyncTask();
            nextActivity();
        }
    });

Memory Leak 4 - AsyncTask

Handler

In the same way, Define anonymous Runnable and execute with anonymous class Handler . Runnable internal classes hold implicit references to external classes and are passed to Handler's message queue MessageQueue. Activity instances are not destroyed until Message Queue messages are processed, resulting in memory leaks.

    void createHandler() {
        new Handler() {
            @Override public void handleMessage(Message message) {
                super.handleMessage(message);
            }
        }.postDelayed(new Runnable() {
            @Override public void run() {
                while(true);
            }
        }, Long.MAX_VALUE >> 1);
    }


    View hButton = findViewById(R.id.h_button);
    hButton.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
            createHandler();
            nextActivity();
        }
    });

Memory Leak 5 - Handler

Threads

We passed it again. Thread and TimerTask To show memory leaks.

    void spawnThread() {
        new Thread() {
            @Override public void run() {
                while(true);
            }
        }.start();
    }

    View tButton = findViewById(R.id.t_button);
    tButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
          spawnThread();
          nextActivity();
      }
    });

Memory Leak 6 - Thread

TimerTask

As long as an instance of an anonymous class, whether it's on a worker thread or not, it holds a reference to Activity, causing a memory leak.

     void scheduleTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                while(true);
            }
        }, Long.MAX_VALUE >> 1);
    }

    View ttButton = findViewById(R.id.tt_button);
    ttButton.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v) {
            scheduleTimer();
            nextActivity();
        }
    });

Memory Leak 7 - TimerTask

Sensor Manager

Finally, through Context.getSystemService(int name) System services are available. These services work in their own processes, helping applications process background tasks and deal with hardware interaction. If you need to use these services, you can register Monitor This causes the service to hold a reference to Context, and memory leaks if these listeners are not logged out when Activity is destroyed.

        void registerListener() {
               SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
               Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
               sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
        }

        View smButton = findViewById(R.id.sm_button);
        smButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                registerListener();
                nextActivity();
            }
        });

Memory Leak 8 - Sensor Manager

summary

Having seen so many examples of memory leaks, it's easy to eat up the memory of mobile phones, making garbage collection more frequent, and even worse, OOM. The operation of garbage collection is expensive and can lead to visible Carlton. So, when instantiating, pay attention to the chain of references that you hold, and often check for memory leaks.

(Translator's Note: The original text does not mention a more general solution. You can refer to this article specifically, static internal classes to relieve your troubles.)

Posted by Dazzozo on Thu, 27 Jun 2019 17:25:50 -0700