Welcome to juejin.cn/user/330776… Leakcanary 🐤 official document: square. Making. IO/leakcanary hprof introduction: hg.openjdk.java.net/jdk6/jdk6/j…

First, memory leaks

1.1 Introduction to Memory Leaks

Memory leak refers to that some objects are no longer needed but cannot be collected by gc. As a result, the memory cannot be released, resulting in a waste of resources. When a large number of memory leaks accumulate, they can also cause OOM indirectly in severe cases. For example, when an Activity is destroyed, the Activity is theoretically no longer needed and the memory space should be freed, but if a static variable holds a reference to the Activity, the GC cannot reclaim the Activity, causing a memory leak. The following code simulates the above scenario: MainActivity starts TestActivity and then backs from TestActivity to MainActivity. If onDestroy() is executed after exiting TestActivity, TestActivity is no longer needed. But because TestActivity is held by Utils’s static object, TestActivity cannot be collected by gc, causing a memory leak. This should be avoided in real development.

package com.bc.example;

public class Utils {
    public static Context cacheContext;
}

pulic class MainActivity extends Activity {
    public void onButtonClick(a) {
        Intent intent = new Intent(this, SecondActivity.class);
        startActivity(intent);
    }
}

pulic class SecondActivity extends Activity {
    @Override
    public void onCreate(a) {
        Utils.cacheContext = this;
        super.onCreate(); }}Copy the code

1.2 Common Causes of Memory Leakage

Memory leaks occur because long-life objects hold short-life objects, so that short-life objects cannot be collected by gc when they are no longer needed. Memory leaks are usually caused by code bugs. Common causes of memory leaks include:

1.2.1 Memory leaks caused by static variables or singletons

Examples are shown in 1.1. Solution: Static variables or singletons do not hold references to objects such as activity or view. If you must hold references, you can change to WeakReference.

1.2.2 Memory leaks caused by internal classes

An inner class holds a reference to an outer class object, for example:

public class MainActivity extends Activity {
    /**
    * 内部类
    */
    public class InnerClass {}}Copy the code

The InnerClass MainActivity$innerclass.class file generated by the above code is as follows:

public class MainActivity$InnerClass {
    public MainActivity$InnerClass(MainActivity this$0) {
        this.this$0 = this$0; }}Copy the code

It can be seen that an inner class holds a reference to an external class. Therefore, when using an inner class, you need to pay attention to whether the object of the inner class is persistently referenced. As a result, the external class cannot be released. Workaround: Consider whether you can implement a static inner class, which is equivalent to a normal class and does not hold references to an external class.

1.2.3 Memory leaks caused by anonymous inner classes

An anonymous inner class also holds a reference to an external class object. Take the following anonymous inner Handler class for example:

public class MainActivity extends Activity {

    private Handler handler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            Log.d("TAG", msg.toString());
            super.handleMessage(msg); }}; }Copy the code

When compiled, the above code generates the anonymous inner class MainActivity$1.class file as follows:

class MainActivityThe $1extends Handler {
    MainActivity$1(MainActivity this$0) {
        this.this$0 = this$0;
    }

    public void handleMessage(Message msg) {
        Log.d("TAG", msg.toString());
        super.handleMessage(msg); }}Copy the code

As you can see, the anonymous inner Handler class holds a reference to the external Activity, so that when the Activity is destroyed, if the handler still has a message in the queue, because message.target points to that handler, The handler holds the activity, so the activity cannot be collected by the GC, causing a memory leak. Solution: the destruction of the activity at the handler. Call removeCallbacksAndMessages (); Consider whether you can use static inner classes, which are equivalent to ordinary classes and do not hold references to external classes; When the business really needs to call the method in the activity, it is called in the way of WeakReference.

1.2.4 Memory leak caused by animation

First, analyze Animator animation start source as follows:

public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {

    private void start(boolean playBackwards) {
        / /... Omit some code
        // Key code
        addAnimationCallback(0);
    }

    private void addAnimationCallback(long delay) {
        // Add yourself to the FrameCallback of the singleton AnimationHandler
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }
    
    @Override
    public void end(a) {
        / /... omit
        endAnimation();
    }
    
    @Override
    public void cancel(a) {
        / /... omit
        endAnimation();
    }

    private void endAnimation(a) {
        / /... omit
        removeAnimationCallback();
    }

    private void removeAnimationCallback(a) {
        // Remove yourself from AnimationHandler's FrameCallback
        getAnimationHandler().removeCallback(this); }}/ * * * AnimationHandler is singleton implementation, used to receive Choreographer. FrameCallback callback complete animation * /
public class AnimationHandler {
    public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
    private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
            new ArrayList<>();

    public static AnimationHandler getInstance(a) {
        if (sAnimatorHandler.get() == null) {
            sAnimatorHandler.set(new AnimationHandler());
        }
        return sAnimatorHandler.get();
    }

    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if(! mAnimationCallbacks.contains(callback)) { mAnimationCallbacks.add(callback); }if (delay > 0) { mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay)); }}}Copy the code

From the above code analysis, the Animator is held by a singleton class after start() and removed from the callback list after end or Cancel. During development, if the Animator defines an anonymous inner class listener that holds an activity and the animation is valueAnimator.infinite, a memory leak will occur if the animation is not cancelled after start.

public class MainActivity extends Activity {

    private TextView textView;
    private ValueAnimator animator = ValueAnimator.ofFloat(0.1);
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView)findViewById(R.id.text_view);
        animator.setDuration(1000);
        animator.setRepeatMode(ValueAnimator.REVERSE);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        // An anonymous inner class listener holds a reference to an external class
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animator) { Float alpha = (Float) animator.getAnimatedValue(); textView.setAlpha(alpha); }}); }@Override
    protected void onDestroy(a) { animator.cancel(); }}Copy the code

Solution: Call the animation’s Cancel method when the Activity is destroyed.

1.2.5 Memory Leaks Caused by Unclosed Resources

If a resource object (such as InputStream/OutputStream, Cursor, File, etc.) is not closed in time, the GC will not be able to reclaim the memory.

1.2.6 Memory Leakage due to Underegistration of Eventbus

Eventbus is implemented by having a map in a singleton hold registered objects (see Eventbus Theory), so unregistering (eventbus.getDefault ().unregister(this)) will cause a memory leak.

1.2.7 other

In addition, registered BroadcastReceiver, listener, RxJava Subscription, but not unregistered, can also cause memory leaks. There are many bugs that may cause memory leaks during development. For objects such as listener, Receiver, Observer, and callback, developers should carefully check whether reference relationships are necessary or reasonable and whether memory leaks may occur.

1.3 Four Reference types

(1) StrongReference: will not be collected during GC. (2) SoftReference: if the memory is insufficient, an object is reclaimed only if its SoftReference is available. (3) WeakReference: in GC, if an object has only WeakReference, it will be recovered; (4) PhantomReference: it can be reclaimed at any time;

Second, hprof file

2.1 Obtaining hprof Files

hprofThe Heap profile file represents a memory snapshot of the current heap. By analyzing the hprof file, you can see which objects are currently leaking.

There are three ways to get the Hprof file:

(1) AS Profiler tools

Android studioProfiler toolTo capture a snapshot of the current heap memory, click Dump Java Heap on the Memory toolbar. You can also import the hprof file for presentation.

(2) ADB command line access

adb shell am dumpheap <processname> <filename>
Copy the code

(3) Code acquisition

// The following code temporarily suspends all threads and saves a snapshot of the current heap to the specified fileName
Debug.dumpHprofData(fileName)
Copy the code

2.2 Hprof file analysis tool

2.2.1 Profiler tool for AS

Taking the code in 1.1 as an example, start SecondActivity and then return to MainActivity; After clicking on forced garbage collection in the profiler and then clicking on dump, the profiler tool will list the current heap snapshot and list the objects that have memory leaks, as shown below:

After clicking Leak, you see the chain of references to the leaking SecondActivity object as follows:Memory leaks can be fixed based on GC root reference chains.

2.2.2 Shark

Shark is the heap memory analysis tool used in Leakcanary2. Fast running speed, less memory, suitable for analyzing HPROF on the client. If the project needs to analyze HPROF during app running, you can consider using it. Leakcanary1. x Haha is rarely used and is no longer described.

2.2.3 Eclipse MAT tool

Eclipse’s MAT tool is used in a similar way to AS’s Profiler tool, and can also enforce garbage collection, export the current heap snapshot hprof file, and show the reference chain after analyzing the HPROF. I won’t go into details.

C. Leakcanary

In summary, the primitive way to fix a memory leak is to export the current heap snapshot hprof after the Activity exits, then use an analysis tool to open the hprof file, analyze whether there is a memory leak, and break the reference chain to fix the memory leak. In practice, this would be cumbersome and would require the developer to export hprof and analyze it every time the Activity is destroyed. We would like a tool that would automatically export the Hprof file and analyze it in the event of a memory leak, showing the chain of references to a memory leak, which leakCanary provides. Leakcanary is an Android memory leak detection library that helps developers detect memory leaks in Activity, FragmentAndViewModel, RootView, and Service objects.

3.1 Basic Usage

Leakcanary is easy to use, just add the following dependencies to the build.gradle app

dependencies { 
    // debugImplementation because LeakCanary should only run in debug builds. 
    // As you can see, the code for LeakCanary is pushed into the package only when the debug package is compiled
    debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.7' 
}
Copy the code

At this point, LeakCanary is working properly. After the app starts, you can see the output log of LeakCanary indicating that LeakCanary is running:

D LeakCanary: LeakCanary is running and ready to detect leaks
Copy the code

3.2 Usage

After installing the app, in addition to the developer’s app icon, there will be a leak icon on the desktop that opens the LeakCanary activity. In the event of a memory leak, LeakCanary automatically dumps the current heap snapshot hprof at the appropriate time (the number of leaking objects reaches the default threshold of 5 or the Application is not visible, as described in section 4) and then analyzes for memory leaks. And shown in the activity for Leakcanary. Developers can also proactively trigger memory leak detection by clicking on ‘Dump Heap Now’ themselves.

Using the 1.1 example again, after returning from SecondActivity, click on ‘Dump Heap Now’ to see leakCanary gives the chain of references to the memory leak as follows:It can be seen that SecondActivity cannot be reclaimed because Utils refers to the SecondActivity object. You can consider deleting the reference or using WeakReference to repair the memory leak.

Iv. Leakcanary Principle

Built in build.gradleDebugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.7'After that, you can see the following AAR introduced:Leakcanary Automatically detects memory leaks in four steps:

(1) Automatically monitor the destruction of Activity, FragmentAndViewModel, RootView and Service to determine whether there is a memory leak;

(2) In case of memory leak, capture memory snapshot hprof file;

(3) Used in HeapAnalyzerServiceSharkAnalyze hprof files;

(4) Classify and display the analyzed memory leak results.

4.1 Automatic monitoring of activities, etc

4.1.1 Initialization of LeackCanary

In LeakCanary1.x, the developer needs to call the leakCanary initialization method in the code to implement automatic monitoring:

pulib class MyApplication extends Application {
    @Override
    public void onCreate(a) {
        // Leakcanary1.x needs to be called by the developer to implement automatic monitoring
        LeakCanary.install(this);
        super.onCreate(); }}Copy the code

In Leakcanary2.x, we only need to add dependencies to build.gradle, because leakcanary2.x uses the principle that ContentProverder is created after the Application is created (see section 5 for source code analysis). The custom ContentProverder calls a similar manualInstall method to automatically initialize and monitor objects after the app starts. Leakcanary2.x The ContentProverder that implements automatic monitoring in Leakcanary2.x is the AppWatcherInstaller declared in leakcancanary-object-Watcher-Android-2.7.aar mainfest:


      
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.leakcanary.objectwatcher" >
    <uses-sdk android:minSdkVersion="14" />
    <application>
        <provider
            android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
            android:authorities="${applicationId}.leakcanary-installer"
            android:enabled="@bool/leak_canary_watcher_auto_install"
            android:exported="false" />
    </application>
</manifest>
Copy the code

Leakcanary2.x AppWatcherInstaller:

internal sealed class AppWatcherInstaller : ContentProvider() {

  internal class MainProcess : AppWatcherInstaller(a)override fun onCreate(a): Boolean {
    valapplication = context!! .applicationContextas Application
    // ContentProvider creates the Application and executes onCreate()
    // Call the singleton AppWatcher manualInstall method in onCreate()
    AppWatcher.manualInstall(application)
    return true}}/** * singleton AppWatcher */
object AppWatcher {

  / * * *@paramWatchersToInstall Defaults to appDefaultWatchers(application) */
  @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
    ) {
    watchersToInstall.forEach {
      it.install()
    }
  }

  / * * * the default Watcher, there are four classes: ActivityWatcher, FragmentAndViewModelWatcher, RootViewWatcher, ServiceWatcher * /
  fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
    ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
      )
  }
}
Copy the code

Leakcanary2.x has called the manualInstall method of AppWatcher after application creation. Method the default implementation in the four watcher (ActivityWatcher FragmentAndViewModelWatcher RootViewWatcher, ServiceWatcher) to monitor the Activity, FragmentAndVi respectively References to ewModel, RootView, and Service to detect memory leaks in these four objects. In addition, the PlumberInstaller is initialized as soon as the Application is started by means of a ContentProvider to increase memory leak detection for rare objects, which we won’t go into here.

4.1.2 ObjectWatcher automatic monitoring

The four watcher implementations are responsible for passing references to activities, FragmentAndViewModel, RootView, and Service destruction. The objectWatcher analyzes whether or not the current object has been reclaimed at intervals in the form of weak references. If it has been reclaimed, there is no memory leak; otherwise, there may be a memory leak. Here is the source code analysis:

object AppWatcher {

  /** * objectWatcher is used to detect whether the object currently being monitored may have memory leaks */
  val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
      // retainedDelayMillis defaults to 5s
      mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true})fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      // The four Watcher types all pass objectWatcher as an argument
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }
}
Copy the code

ActivityWatcher, for example, initialization by registered ActivityLifecycleCallback constantly receive a callback when the Activity destroyed, after receiving the callback pass reference to objectWatcher, The objectWatcher holds a weak reference to the Activity and analyzes whether it is recycled after a certain amount of time (5s by default).

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

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        Expectexpectlyreachable can be analyzed by the ObjectWatcher to determine whether the Activity is likely to leak memory after it destroys
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback")}}override fun install(a) {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall(a) {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}
Copy the code

Similarly, FragmentAndViewModelWatcher, RootViewWatcher, ServiceWatcher is mainly responsible for the pass reference to when the object is destroyed objectWatcher, by objectWatcher determine whether be recycled, The main difference is that the time of destruction is determined differently. Activty, Fragment, and RootView are called back through the registration system, and Service is implemented through proxy.newProxyInstance (). Next, I’ll focus on how the ObjectWatcher can tell if an object is reclaimed when it receives a expectWeaklyReachable() callback.

class ObjectWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {
  /** * Holds the currently observed object */ as a weak reference
  private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
  /** * The ReferenceQueue to which all weak references are registered determines whether the object to which the weak reference refers is reclaimed */
  private val queue = ReferenceQueue<Any>()
  /** * Listener when a memory leak is detected */
  private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()

  /** * After the Activity is destroyed, its reference is held as a weak reference for later determination of whether it is recycled */
  @Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
  ) {
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // A weak reference holds the Activity
    val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    // Add a weak reference to the Activity to the watch list
    watchedObjects[key] = reference
    CheckRetainedExecutor is created in AppWatcher and is no longer analyzed
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

  /** * In the list of currently observed objects, the objects that have been reclaimed are removed, and the remaining objects are the objects that could leak */
  @Synchronized private fun moveToRetained(key: String) {
    // Remove objects that have already been reclaimed
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if(retainedRef ! =null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      // The Listener method will be called for further analysis (section 4.2 analyzing source code).
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

  private fun removeWeaklyReachableObjects(a) {
    var ref: KeyedWeakReference?
    do {
      // If there is such a WeakReference in the ReferenceQueue, it means that the object that the WeakReference points to has been recovered
      ref = queue.poll() as KeyedWeakReference?
      if(ref ! =null) {
        watchedObjects.remove(ref.key)
      }
    } while(ref ! =null)}}Copy the code

4.1.3 KeyedWeakReference

As mentioned above, when the Activity executes onDestroy(), the Activity object is held by the ObjectWatcher in the form of a KeyedWeakReference WeakReference, which is essentially a WeakReference:

class KeyedWeakReference(referent: Any, val key: String, val description: String, 
    val watchUptimeMillis: Long,referenceQueue: ReferenceQueue<Any>)
    : WeakReference<Any>(referent, referenceQueue) {
}
Copy the code

How to judge whether an object is recovered is realized by the relationship between WeakReference and ReferenceQueue: When creating a WeakReference object, a ReferenceQueue object can be specified. When the object that the WeakReference points to is marked by GC and can be recycled, the WeakReference will be added to the end of the ReferenceQueue. WeakReference has two constructors, the source code is as follows:

public class WeakReference<T> extends Reference<T> {
    /** * creates a weak reference to the passed object */
    public WeakReference(T referent) {
        super(referent);
    }

    /** * Creates a weak reference to the incoming object and registers the weak reference at the ReferenceQueue. * when the object to which the weak reference refers is recycled by GC, the weak reference is placed at the end of the ReferenceQueue
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q); }}Copy the code

4.1.4 summary

Leakcanary2.x uses the principle that the ContentProvider is created after the Application is created, and has been initialized once the ContentProvider is created, making leakcanary easy for developers to use. Leakcanary through ActivityWatcher, FragmentAndViewModelWatcher, RootViewWatcher, ServiceWatcher will give reference to objectWatcher when object is destroyed, The objectWatcher determines whether an object might have a memory leak. After the Activity, FragmentAndViewModel, RootView, and Service have been destroyed for 5 seconds, the objectWatcher will determine whether the object is only collected by GC in the main thread. If not, it will consider a memory leak. HeapDumpTrigger (analyzed in Section 4.2) is then fired to check again on the worker thread for a memory leak, and if so, the HPFO file is exported and the memory leak reference chain is analyzed. ObjectWatcher determines whether the observed object is recycled by: When creating a WeakReference object, a ReferenceQueue object can be specified. When the object that the WeakReference points to is marked by GC and can be recycled, the WeakReference will be added to the end of the ReferenceQueue.

4.2 Confirming Memory Leaks

Depending on the circumstances, the Onobjectwatcher calls the OnObjectRetainedListener () method if it thinks a memory leak may have occurred:

onObjectRetainedListeners.forEach { it.onObjectRetained() }
Copy the code

The timing of the 2 addOnObjectRetainedListener

So, the next continue to analyze AppWatcher. ObjectWatcher. AddOnObjectRetainedListener (this) is in what time to add, add the timing is also in AppWatcher manualInstall method:

object AppWatcher {
  @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  ) {
    // Key analysis
    LeakCanaryDelegate.loadLeakCanary(application)
    watchersToInstall.forEach {
      it.install()
    }
  }
}

internal object LeakCanaryDelegate {

  val loadLeakCanary by lazy {
    try {
      The implementation class is InternalLeakCanary
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE").get(null) as (Application) -> Unit
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
  }
}
Copy the code

After receiving a callback that may have a memory leak, the main logic is done in InternalLeakCanary, the source code is as follows:

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {

  private var _application: Application? = null

  override fun invoke(application: Application) {
    _application = application
    / / here is objectWatcher addOnObjectRetainedListener timing, is adding good during initialization
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
    // Trigger the implementation of GC
    val gcTrigger = GcTrigger.Default
    val configProvider = { LeakCanary.config }
    // The worker thread
    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)
    // heapDump triggers in the worker thread
    heapDumpTrigger = HeapDumpTrigger(application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,configProvider)
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    registerResumedActivityListener(application)
    // Add desktop shortcuts
    addDynamicShortcut(application)

    // We post so that the log happens after application.oncreate ()
    mainHandler.post {
      backgroundHandler.post {
        SharkLog.d {
          when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
            is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
            is Nope -> application.getString(R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
            )
          }
        }
      }
    }
  }
}
Copy the code

4.2.2 onObjectRetained

After receiving a callback that may have a memory leak, the next main logic is completed in InternalLeakCanary, and then analyze what work is done after receiving a callback, source code is as follows:

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
  override fun onObjectRetained(a) = scheduleRetainedObjectCheck()

  fun scheduleRetainedObjectCheck(a) {
    if (this::heapDumpTrigger.isInitialized) {
      / / heapDumpTrigger is in the above InternalLeakCanary created in the invoke method
      heapDumpTrigger.scheduleRetainedObjectCheck()
    }
  }
}
Copy the code

Holdings HeapDumpTrigger

HeapDumpTrigger checks again on the worker thread to see if there is a memory leak by firing a GC and then checking to see if there are still objects that have not been collected by the GC flag. If so, the heap is dumped. Then call HeapAnalyzerService to further analyze the hprof file:

internal class HeapDumpTrigger(
  private val application: Application,
  private val backgroundHandler: Handler,
  private val objectWatcher: ObjectWatcher,
  private val gcTrigger: GcTrigger,
  private val heapDumper: HeapDumper,
  private val configProvider: () -> Config
  ) {

  /** * Dump heap for further analysis ** /
  fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
    ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      return
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    // Scheduling of worker threads
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      // The main method is analyzed below
      checkRetainedObjects()
    }, delayMillis)
  }

  /** * triggers GC to double-check if there is a memory leak, and if so, dump heap ** /
  private fun checkRetainedObjects(a) {
    val config = configProvider()
    // 1. View the objects currently held by objectWatcher
    var retainedReferenceCount = objectWatcher.retainedObjectCount
    // 2. When objectWatcher holds an object, it fires a gc first
    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }
    // 3. After gc, if objectWatcher holds fewer than 5 references, return
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
    // 4. After gc, if the objectWatcher holds more than 5 references, the dump heap does further analysis
    // The interval between each dump heap must be >=60s. If the interval is less than 60s, return and delay the execution until the interval reaches 60s
      val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
      onRetainInstanceListener.onEvent(DumpHappenedRecently)
      showRetainedCountNotification(
        objectCount = retainedReferenceCount,
        contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
        )
      // If the interval is less than 60 seconds, the heap dump will be delayed
      scheduleRetainedObjectCheck(
        delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
        )
      return
    }

    dismissRetainedCountNotification()
    val visibility = if (applicationVisible) "visible" else "not visible"
    // 5. Execute dump heap
    dumpHeap(
      retainedReferenceCount = retainedReferenceCount,
      retry = true,
      reason = "$retainedReferenceCount retained objects, app is $visibility")}/** * First debug. dumpHprofData, then call HeapAnalyzerService to further analyze the hprof file ** /
  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean,
    reason: String
    ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
      / / 1. HeapDumper. DumpHeap () to perform the Debug dumpHprofData (heapDumpFile. AbsolutePath) to dump heap
    when (val heapDumpResult = heapDumper.dumpHeap()) {
      is HeapDump -> {
        lastDisplayedRetainedObjectCount = 0
        lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
        objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
          // 2. Call HeapAnalyzerService to further analyze the hprof file
        HeapAnalyzerService.runAnalysis(
          context = application,
          heapDumpFile = heapDumpResult.file,
          heapDumpDurationMillis = heapDumpResult.durationMillis,
          heapDumpReason = reason
          )
      }
    }
  }
}
Copy the code

4.2.4 GcTrigger

HeapDumpTrigger trigger garbage collection is implemented by GcTrigger, source code analysis is as follows:

interface GcTrigger {
  fun runGc(a)

  /** * The default implementation of GcTrigger */
  object Default : GcTrigger {
    override fun runGc(a) {
      // Calling gc() does not guarantee that GC will happen, so here the thread pauses for 100ms after GC and then triggers GC again
      Runtime.getRuntime().gc()
      enqueueReferences()
      System.runFinalization()
    }

    private fun enqueueReferences(a) {
      // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
      // references to the appropriate queues.
      try {
        Thread.sleep(100)}catch (e: InterruptedException) {
        throw AssertionError()
      }
    }
  }
}
Copy the code

4.2.5 summary

The ObjectWatcher can only detect that any object currently has a possible memory leak. In HeapDumpTrigger, the worker thread checks again for a memory leak by firing a GC and then checking to see if any object has not been collected by gc. If so, a memory leak has occurred. The heap is then dumped and HeapAnalyzerService is called to further analyze the hprof file. Each HeapDumpTrigger dump heap is limited to 60 seconds because the dump heap temporarily suspends all Java threads in the current process.

4.3 Analyzing the Hprof file

4.3.1 HeapAnalyzerService

After HeapDumpTrigger outputs the hprof file, HeapAnalyzerService is called to start the service to analyze the hprof file and obtain the final memory leak analysis result. The source code is as follows:

internal class HeapAnalyzerService : ForegroundService(
  HeapAnalyzerService::class.java.simpleName,
  R.string.leak_canary_notification_analysing,
  R.id.leak_canary_notification_analyzing_heap
), OnAnalysisProgressListener {

  companion object {

    fun runAnalysis(context: Context,heapDumpFile: File,heapDumpDurationMillis: heapDumpReason: String = "Unknown") {
      val intent = Intent(context, HeapAnalyzerService::class.java)
      // Wrap heapDumpFile in the intent and pass it to HeapAnalyzerService for analysis
      intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
      startForegroundService(context, intent)
    }
  }

  override fun onHandleIntentInForeground(intent: Intent?). {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
    // 1. Obtain the hprof file path
    val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
    val config = LeakCanary.config
    val heapAnalysis = if (heapDumpFile.exists()) {
      // 2. Analyze the hprof file
      analyzeHeap(heapDumpFile, config)
    } else {
      missingFileFailure(heapDumpFile)
    }
    val fullHeapAnalysis = when (heapAnalysis) {
      // 3. Analysis results are encapsulated as HeapAnalysisSuccess objects
      is HeapAnalysisSuccess -> heapAnalysis.copy(
        dumpDurationMillis = heapDumpDurationMillis,
        metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)
      )
      is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
    }
    onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
    // 4. Call back the analysis results to Listern and update the UI to show the analysis results to developers
    config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
  }
}
Copy the code

4.3.2 HeapAnalysisSuccess

Shark analysis hprof file code will not be further studied, the following is the analysis results of the package class HeapAnalysisSuccess:

data class HeapAnalysisSuccess(
  override val heapDumpFile: File,
  override val createdAtTimeMillis: Long.override val dumpDurationMillis: Long = DUMP_DURATION_UNKNOWN,
  override val analysisDurationMillis: Long.val metadata: Map<String, String>,
  /** * A list of objects that are currently leaking in the APP. Developers are mainly concerned with */
  val applicationLeaks: List<ApplicationLeak>,
  /** * The list of leaked objects in the third-party library of the current APP may not be fixed by the developer */
  val libraryLeaks: List<LibraryLeak>,
  val unreachableObjects: List<LeakTraceObject>
) : HeapAnalysis() {
  /** * All leaked objects */
  val allLeaks: Sequence<Leak>
    get() = applicationLeaks.asSequence() + libraryLeaks.asSequence()

}
Copy the code

Five, the other

5.1 Analysis of the Creation time of ContentProvider

As described in 4.1, the ContentProvider is created after the Application is created, and the onCreate time of the ContentProvider precedes the onCreate time of the Application. Activitythread. H class: activityThread. H class: activityThread. H class: activityThread. H

public final class ActivityThread extends ClientTransactionHandler {

    class H extends Handler {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                // start Application
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    //
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                / / to exit the Application
                case EXIT_APPLICATION:
                    if(mInitialApplication ! =null) {
                        mInitialApplication.onTerminate();
                    }
                    Looper.myLooper().quit();
                    break;
                / /... omit}}}private void handleBindApplication(AppBindData data) {
        // 1. Set some process information
        VMRuntime.registerSensitiveThread();
        Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
        Process.setArgV0(data.processName);
        android.ddm.DdmHandleAppName.setAppName(data.processName,
            data.appInfo.packageName,
            UserHandle.myUserId());
        VMRuntime.setProcessPackageName(data.appInfo.packageName);
        VMRuntime.setProcessDataDirectory(data.appInfo.dataDir);

        2. Create AppContext and Instrumentation
        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
        final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, appContext.getClassLoader(), false.true.false);
        mInstrumentation = (Instrumentation)cl.loadClass(data.instrumentationName.getClassName()).newInstance();
        final ComponentName component = new ComponentName(ii.packageName, ii.name);
        mInstrumentation.init(this, instrContext, appContext, component,
            data.instrumentationWatcher, data.instrumentationUiAutomationConnection);

        // 3. Create Application
        Application app;
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;
        
        // 4. Create ContentProvider
        if(! data.restrictedBackupMode) {if(! ArrayUtils.isEmpty(data.providers)) {//installContentProviders(app, data.providers); }}// 5. Execute Application onCreate()
        mInstrumentation.onCreate(data.instrumentationArgs);
        mInstrumentation.callApplicationOnCreate(app);
    }

    private void installContentProviders(Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();
        for (ProviderInfo cpi : providers) {
            // Instantiate ContentProviders
            ContentProviderHolder cph = installProvider(context, null, cpi, false.true.true);
            if(cph ! =null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }
        ActivityManager.getService().publishContentProviders(
            getApplicationThread(), results);
    }

    private ContentProviderHolder installProvider(Context context,
        ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        final java.lang.ClassLoader cl = c.getClassLoader();
        lLoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
        if (packageInfo == null) {
            packageInfo = getSystemContext().mPackageInfo;
        }
        1. Instantiate the ContentProvider
        localProvider = packageInfo.getAppFactory().instantiateProvider(cl, info.name);
        // 2. Call attachInfo of the ContentProvider object, which calls onCreate() of the ContentProviderlocalProvider.attachInfo(c, info); }}Copy the code