background

Android memory optimization is an important part of the stable operation of the APP, the development process if the code is written too casual, it is easy to cause memory leaks, after multiple accumulation, will produce OOM, and then cause the APP crash. This article introduces the knowledge of memory leaks and the implementation principle of the detection tool LeakCanary, and summarizes the techniques for reducing the running memory of an application.

What is a memory leak

Memory leak refers to the fact that objects that are no longer used in the process continuously occupy memory or the memory occupied by objects that are no longer used is not released in a timely manner. As a result, the actual available memory becomes smaller, resulting in a waste of memory space.

How do I detect memory leaks

The following two tools are used mainly for memory leaks in Android.

Profiler

Profiler is a built-in memory detection tool in Android Studio, which can intuitively see the changes of app memory during the running process. But to go further, you need to do something else that’s not intuitive.

LeakCanary

LeakCanary is currently the most used memory detection tool, it is integrated within the APP, just add a dependency to the build.gradle file. In contrast to the Profiler, it doesn’t need to keep tabs on the memory changes that are displayed in the Android Studio page, and it will only pop up when a memory leak occurs, alerting the developer to a memory leak and giving the chain of references that caused the leak. Simpler and more flexible to use than profilers.

Currently, our APP uses LeakCanary to detect memory leaks. Here is the main implementation principle of LeakCanary.

LeakCanary

LeakCanary initialization

The difference between 2.x and 1.x is that there is no need to initialize manually. Just add the following dependencies to build.gradle in the main module to complete initialization.

debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.6'
Copy the code

So how is the new version initialized? The AppWatcherInstaller class is easy to find in the source code because the ContentProvider executes before the Application when the app is started, so there is no need to manually execute the initialization code in the Application class.

internal sealed class AppWatcherInstaller : ContentProvider() {
    override fun onCreate(a): Boolean {
    valapplication = context!! .applicationContextas Application
    AppWatcher.manualInstall(application)
    return true}}Copy the code

So when is the onCreate method of the AppWatcherInstaller called? This is when the ContentProvider is initialized. It’s actually in the ActivityThread’s handleBindApplication method.

private void handleBindApplication(AppBindData data) {...// don't bring up providers in restricted mode; they may depend on the
            // app's custom Application class
            if (!data.restrictedBackupMode) {
                if(! ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers); }}// Application initializationmInstrumentation.callApplicationOnCreate(app); . }Copy the code

As we can see from the code above, the onCreate method of the ContentProvider is initialized before the Application, and the initialization of LeakCanary takes place when the ContentProvider is initialized, which means that it helps us automate the initialization process.

It is important to note that when initializing in this way, no time-consuming code can be executed, because the ContentProvider initializes in the main thread, otherwise the app will start slowly.

LeakCanary Detection principle

  • LeakCanary performs memory leak detection mainly through the following steps:
    1. Get the objects that might leak
    2. generate.hproffile
    3. Analysis of the.hprofFile, and prompts
  • LeakCanary Detects the target
    1. Activity
    2. Fragments and ViewModel
    3. View
    4. Service

fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }

Copy the code

First let’s look at ObjectWatcher. Its key code is as follows:


@Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    if(! isEnabled()) {return
    }
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    SharkLog.d {
      "Watching " +
          (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
          (if (description.isNotEmpty()) "($description)" else "") +
          " with key $key"
    }
 
    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

Copy the code

Basically, we’re using a weak reference to a watchedObject, and notice that we’re using ReferenceQueue, so the combination of the two makes it possible that if the object that’s associated with a weak reference is reclaimed, then we’re going to queue that weak reference to determine whether or not that object has been reclaimed.

LeakCanary’s main detection objects are the above four. Take Activity as an example for analysis. Other detection types are also similar to the principle, which will not be repeated.

class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback")}}}Copy the code

Registered in ActivityWatcher ActivityLifecycleCallbacks, at the same time when onActivityDestroyed, perform some operations, view the source code:

  @Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
  ) {
    if(! isEnabled()) {return
    }
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    SharkLog.d {
      "Watching " +
        (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
        (if (description.isNotEmpty()) "($description)" else "") +
        " with key $key"
    }

    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }
Copy the code

The main logic of the above code is:

  1. Remove weakly reachable objects
  2. Will the currentwatchedObjectAdded to theKeyedWeakReferenceamong
  3. Save the weakReference into the array
  4. incheckRetainedExecutorPerformed in themoveToRetainedmethods

According to the principle of removeWeaklyReachableObjects method, if the object except by ObjectWatcher added WeakReference, no other objects in the reference it, then the object also can be recycled, WatchedObjects can then remove it.

  private fun removeWeaklyReachableObjects(a) {
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if(ref ! =null) {
        watchedObjects.remove(ref.key)
      }
    } while(ref ! =null)}}Copy the code

The checkRetainedExecutor is actually a singleton object that uses a handler to delay method execution by 5s. If it exceeds 5s, LeakCanary’s leak detection mechanism will be triggered. The 5s is just an empirical value, because GC doesn’t happen in real time, so 5s is reserved for GC operations.

Triggered after LeakCanary leak detection, will perform HeapDumpTrigger dumpHeap method, in acquiring. Hprof files, call HeapAnalyzerService. RunAnalysis () the results of the analysis are presented. Analysis of the. Hprof file is not the focus of this article, please refer to the hprof file protocol for details. Its analysis is basically to find the leaking object according to GC Root. The general flow chart is as follows.

Common memory leaks in Android

The singleton

Memory leaks caused by singletons are almost the most common memory leaks in Android development.

public class Singleton {

   private static Singleton singleton;
   private Context context;

   private Singleton(Context context) {
       this.context = context;
   }

   public static Singleton getInstance(Context context) {
       if (singleton == null) {
           singleton = new Singleton(context);
       }
       returnsingleton; }}Copy the code

In the above code, if the activity object is passed in when the getInstance method is executed, then the activity object will not be reclaimed in time, resulting in a memory leak. Consider passing the ApplicationContext, or putting the context in a method variable.

Non-static inner classes (including anonymous inner classes)

A non-static inner class holds a reference to an external class by default, causing a memory leak if its lifetime is longer than that of the external class. In Android development, this is often the case with handlers.

Avoid static variables if possible


    class MainActivity : AppCompatActivity() {

    companion object {
        @JvmStatic
        private var info: StaticInfo? = null
    }
    
    override fun onCreate(savedInstanceState: Bundle?). {
        info = StaticInfo(this)}class StaticInfo(activity: MainActivity) {

}

Copy the code

In the above code, info is a static variable, but it holds a reference to the activity. Because the life cycle of the static variable is longer than the life cycle of the activity, the activity cannot be reclaimed in time, causing a memory leak.

A memory leak caused by an unclosed resource

Objects such as Cursor and inputStream must be closed in a timely manner

    try{}catch (e:Exception) {

    }finally {
        // We can close objects such as Cursor in the finally method
    } 
Copy the code

A memory leak caused by objects in the collection not being cleaned up in time

 val list = ArrayList<Activity>()
Copy the code

For example, if a list contains an activity object, the activity may not be recycled in a timely manner. If the list is static and the activity is not removed, the memory leak will occur.

Memory leak caused by WebView

After the WebView loads the page, its callback will hold a reference to the activity, causing webView memory to be blocked. You can remove the WebView in the activity’s onDestroy() method and call webView.destroy ().

Memory leak caused by underegistered or callbacks

Callbacks are used a lot in Android, but if the context object is passed in when the callback is registered, you need to be careful to cancel the callback in time, otherwise there may be a memory leak. Examples are Eventbus and broadcast.

Memory optimization

  • useintentServiceInstead ofServiceOr stop the service when it is finished
  • When system resources are tight, release as many non-critical resources as possible (such as memory cache of images)
class MyApp : Application() {

    override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
        // You can do some memory freeing here}}Copy the code
  • avoidbitmapThe abuse of

If you don’t have to work with bitmaps, you can use some good third-party libraries for image loading. Remember to reuse and recycle bitmaps.

  • Use a data container optimized for memory

In most scenarios, you can use SparseArray instead of HashMap and so on to reduce memory usage somewhat.

  • Using multiple processes

Push starts a separate process, webView starts a separate process

conclusion

This article first introduces the implementation principle of LeakCanary, android’s memory leak detection tool, then introduces some causes of memory leaks, and finally gives some suggestions for Android memory optimization. Of course, there are thousands of memory problems, this article can not do everything. Hopefully, through the introduction of this article, you will be able to track and solve memory leaks in your app, after all, memory optimization is troublesome, but also important.


Nanjing S300 Cloud Information Technology Co., LTD. (CH300) was founded on March 27, 2014. It is a mobile Internet enterprise rooted in Nanjing, currently located in Nanjing and Beijing. After 7 years of accumulation, the cumulative valuation has reached 5.2 billion times, and has been favored by many high-quality investment institutions at home and abroad, such as Sequoia Capital, SAIC Industrial Fund, etc. S300 Cloud is an excellent domestic independent third-party SaaS service provider of auto transaction and finance, which is based on artificial intelligence and takes the standardization of auto transaction pricing and auto financial risk control as the core product.

Welcome to join s300 Cloud and witness the booming development of the automobile industry together. Look forward to walking hand in hand with you! Website: www.sanbaiyun.com/ Resume: [email protected], please indicate from 😁