Cause classification and solutions of memory leakage in Android

Keywords: Java Android

1. What is a memory leak?

android development mainly uses java (kotlin) language, which is the language of automatic memory management. When we use an object, we will automatically allocate memory to the object. When we no longer use the object, we will automatically recycle the memory allocated to the object. However, in some cases, the memory of the object is not recycled after it is used, resulting in that this piece of memory is not used but can no longer be used.

Android's app running mechanism is that each application will be allocated a fixed upper memory limit in the running special virtual machine. When a memory leak occurs, the memory in the virtual machine can no longer be used. When the leaked memory is more and more and no longer enough for app use, it will lead to OOM (ou t of memory) and application crash, The process was killed. Because of this mechanism, after the application is killed, all memory will be released, and the unusable memory can be used again.

2. Why does memory leak occur?

Memory reclamation management strategy is to judge accessibility. When there is a reachable path between memory and GC Roots, it indicates that the current resource is being referenced and the virtual machine cannot recycle it, as shown in the Yellow node in the figure. Conversely, if there is no reachable path between the dot and GC Roots, the virtual opportunity will recycle it during GC, as shown in the blue node in the figure.

This means that when an object with a long life cycle holds an object with a short life cycle, when the life cycle with a short life cycle ends, the GC will not recycle the object. When the object with a long life cycle cannot access the object with a short life cycle or no longer use the object with a short life cycle, A memory leak occurs.

3. Memory leak in Android

3.1. Static variables

Static variables run through the entire application life cycle. When static variables do not clear the corresponding value after the end of the activity life cycle, activity leakage will be caused.

3.1.1. Static variables or collections hold Activity references

When a static activity variable or a collection of stored activities is defined, the running activity instance object is stored in these two static variables. When the corresponding values of these two static variables are not cleared after the end of the activity life cycle, activity leakage will be caused.

companion object{

  var activity:Activity?=null

  val activitySet:Set<Activity> =HashSet<Activity>()

}

3.1.2. Save Activity reference in singleton

In single class mode, objects are also stored through static variables. If a reference to an activity is passed in, as in the previous case, memory cannot be recovered after the end of the activity life cycle, resulting in memory leakage.

The difference from the previous one is that instead of directly storing the Activity reference through the variable, it is stored in the variable object.

class Singleton private constructor(val context: Context) {

  companion object {

    private var instance: Singleton? = null



    @Synchronized

    fun get(context: Context): Singleton {

      if (instance == null) {

        instance = Singleton(context)

      }

      return instance!!

    }

  }

}

3.1.3. Static variable storage View

During View creation, a context object needs to be passed in and stored. Generally, this context is also an Activity object. If the View is stored like the above two cases, the Activity will also be leaked.

public View(Context context) {

  mContext = context;

  ......

}

3.2. Non static internal class

Non static internal classes will hold references to external classes, which will leave hidden dangers. When the life cycle of an external Activity ends, if its internal classes cannot be destroyed, it will lead to the leakage of external activities.

3.2.1. Static storage non static internal class

Statically store the static objects of non static internal classes, so that when the Activity life cycle ends, its internal classes will not be destroyed because of static storage, and the non static internal classes hold the reference of this Activity, resulting in the leakage of the Activity.

class AActivity : AppCompatActivity() {



  companion object {

    var a: A? = null

  }



  inner class A



  override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {

    super.onCreate(savedInstanceState, persistentState)

    a = A()

  }



}

3.2.2. Time consuming operation of internal class

When the internal class performs time-consuming operations, the internal class cannot be released when releasing the Activity. The internal class holds the external Activity object, resulting in memory leakage.

class AActivity : AppCompatActivity() {



  inner class A:Runnable{

    override fun run() {

      while (true){



      }

    }

  }
  override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {

    super.onCreate(savedInstanceState, persistentState)

    Thread(A()).start()

  }
}

3.2.3. Delayed execution of anonymous inner class Handler

Use the Handler to send a delay message. During the delay process, if the Activity is closed, the message will be stored in the message queue for execution. This message will hold a reference to the Handler, and the Handler will hold a reference to the Activity. These references will exist until the execution of the message is completed, resulting in memory leakage.

class AActivity : AppCompatActivity() {
    val handler = object : Handler() {

        override fun handleMessage(msg: Message) {

            super.handleMessage(msg)

            val a = 2

        }

    }

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {

        super.onCreate(savedInstanceState, persistentState)

        handler.sendEmptyMessageDelayed(1,10*60*1000)

    }
}

3.3. Registration not cancelled

During the development process, there will be registration many times. After registration, the context will be held by the observer for a long time. If there is no de registration, it will cause memory leakage.

3.3.1. The sensor is not logged off

When we need to use system services, such as accessing some sensors, these services will run in their own threads. If we need to receive a notification at any time when an event occurs, we need to pass in the listener, so that the service will hold the reference of the Activity. If the registration is not cancelled when the Activity is destroyed, the Activity will be leaked.

class AActivity : AppCompatActivity(), SensorEventListener {
    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {

        super.onCreate(savedInstanceState, persistentState)
        val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager

        val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL)

        sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST)
    }
    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {

        TODO("Not yet implemented")

    }
    override fun onSensorChanged(event: SensorEvent?) {

        TODO("Not yet implemented")

    }
}

3.3.2. Broadcasting is not cancelled

If a broadcast is registered in the Activity through the registerRceiver() method, the broadcast is not only referenced by the Activity, but also by AMS and other system services, and the broadcast holds the reference of the Activity (context in onReceive()). If the broadcast is not deregistered at the end of the Activity life cycle, the Activity will be leaked.

class AActivity : AppCompatActivity() {
    val receiver=object :BroadcastReceiver(){

        override fun onReceive(context: Context?, intent: Intent?) {

        }

    }

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {

        super.onCreate(savedInstanceState, persistentState)
        registerReceiver(receiver,IntentFilter())

    }

}

3.4. Attribute animation

ValueAnimator implements an interface: AnimationFrameCallback. When calling start() to start animation, this interface will be stored in AnimationHandler, which is a single class mode.

Moreover, the animator usually holds the reference of a view to display the animation, and the view holds the reference of the activity. Therefore, if the animation is looped wirelessly and the attribute animation is not stopped when the activity is destroyed, memory leakage will be caused.

class AActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {

    super.onCreate(savedInstanceState, persistentState)
    val view = TextView(this)

    ValueAnimator()

    .apply {

      duration = 1000

      repeatMode = ValueAnimator.REVERSE

      repeatCount = ValueAnimator.INFINITE

      addUpdateListener {

        view.setBackgroundColor(it.animatedValue as Int)

      }

      start()

    }

  }

}

3.5. Resource object not closed

When creating a stream, various system resources, such as file descriptor, will be used for easy access, which will not be automatically recycled by GC. close release must be called. Although there is a call in finalize(), the execution speed of this method is uncertain, and manual call is better.

class AActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {

    super.onCreate(savedInstanceState, persistentState)
    val file = FileInputStream("a.txt")

  }

}

3.6. webview

The memory leak caused by webview is caused by a callback component callback passed in AwContents. Its binding and unbinding are in onAttachedToWindow and onDetachedFromWindow respectively. In onDetachedFromWindow, it will judge whether destroy() has been called for destruction. If it has been destroyed, the subsequent unbinding process cannot be performed, webview holds the activity object again, causing memory leakage.

3.7. Will bitmap not call recycle() cause memory leakage?

In versions prior to Android 3.0, bitmap was actually composed of two parts when loaded into memory. The first part contains some information about bitmap, which is stored in the memory used by Java; The second part contains pixel information (byte array), and the second part is in the memory used by C. Therefore, you need to call Bitmap.recycle() to release C memory, and then hand it over to GC to reclaim JAVA memory. Otherwise, it will cause memory leakage in the C + + part.

However, after 3.0, bitmap data will not be stored in the memory of C + +, so it is not necessary to use recycle() when freeing memory, but can be left empty and managed by GC mechanism.

In the source code of Bitmap.recycle(), there is a paragraph like this:

This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.

So for now, if bitmap does not call recycle(), it will not cause memory leakage. After there is no reference to the bitmap object, it will be released by GC.

4. How to avoid memory leakage

  • When an Activity is destroyed, all the objects holding it will be emptied.
  • Converts a strong reference to a weak reference.
  • You must use context. You can try to use ApplicationContext instead. Its life cycle is the whole app and will not cause memory leakage.
  • Internal classes use static internal classes. If external references are needed, weak references can be passed in.
  • The logic is complete. Remember to de register when registering, remember to unbind when binding, and detect the shutdown when exiting if there is a delay or timing in the program.
  • The situation in the business will be much more complex and people can't prevent it. There will inevitably be leaks. At this time, you can use some libraries, such as LeakCanary, to detect and locate memory leaks.

last

This article is a brief summary of my learning process. I have no experience in solving the actual leakage. This document is only on paper. You are welcome to correct and supplement the mistakes and deficiencies.

Posted by rsasalm on Sun, 31 Oct 2021 00:14:59 -0700