美文网首页
源码分析 - LeakCanary

源码分析 - LeakCanary

作者: Android小悟空 | 来源:发表于2020-12-24 10:20 被阅读0次

    1、使用

    dependencies {
      debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
      releaseImplementation   'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
      // 可选,如果你使用支持库的fragments的话
      debugImplementation   'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
    }
    
    
    public class MyApplication extends Application {
    
        private RefWatcher refWatcher;
        @Override
        public void onCreate() {
            super.onCreate();
            refWatcher = setRefWatcher();
    
        }
    
        private RefWatcher setRefWatcher(){
            if (LeakCanary.isInAnalyzerProcess(this)) {
                return RefWatcher.DISABLED;
            }
            return LeakCanary.install(this);
        }
    
        public static RefWatcher getRefWathcher(Context context){
            MyApplication myApplication = (MyApplication) context.getApplicationContext();
            return myApplication.refWatcher;
        }
    }
    

    这样使用只会检测Activity和标准Fragment是否发生内存泄漏,如果要检测V4包的Fragment在执行完onDestroy()之后是否发生内存泄露的话,则需要在Fragment的onDestroy()方法中加上如下两行代码去监视当前的Fragment:

    RefWatcher refWatcher = WanAndroidApp.getRefWatcher(_mActivity);
    refWatcher.watch(this);
    

    源码分析

    流程:

    1. 监听哪些类的堆内存泄露情况
    2. 如何找到泄露的对象或类
    3. 找到泄露后如何找到导致泄露的引用链
    4. 找到引用链后将引用链抛出来写入到泄露日志中

    首先我们从LeakCanary.install()开始

    LeakCanary.install():

      public static @NonNull RefWatcher install(@NonNull Application application) {
        return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
            .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
            .buildAndInstall();
      }
    
    • refWatcher(application) : 创建一个AndroidRefWatcherBuilder

    • listenerServiceClass(DisplayLeakService.class) : 传入了一个HeapDump.Listener,HeapDump.Listener中的一个参数是DisplayLeakService,DisplayLeakService的作用是展示泄露分析的结果日志,然后会展示一个用于跳转到显示泄露界面DisplayLeakActivity的通知。

    • excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) :
      过滤那些系统或者制造商导致的泄露,这些泄露是我们开发者无法处理的泄露

    • buildAndInstall() : 创建一个RefWatcher,并且开始观察Activity和Fragment。
      注意,这里如果在一个进程调用多次会抛出UnsupportedOperationException异常

    AndroidExcludedRefs 类
    它是一个enum类,它声明了Android SDK和厂商定制的SDK中存在的内存泄露的case,根据AndroidExcludedRefs这个类的类名就可看出这些case都会被Leakcanary的监测过滤掉。目前这个版本是有46种这样的case被包含在内,后续可能会一直增加。

    这里我们来说一下如何观察Activity和Fragment的:

      public @NonNull RefWatcher buildAndInstall() {
        if (LeakCanaryInternals.installedRefWatcher != null) {
          throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
        }
        RefWatcher refWatcher = build();
        if (refWatcher != DISABLED) {
          LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
          if (watchActivities) {
            ActivityRefWatcher.install(context, refWatcher);
          }
          if (watchFragments) {
            FragmentRefWatcher.Helper.install(context, refWatcher);
          }
        }
        LeakCanaryInternals.installedRefWatcher = refWatcher;
        return refWatcher;
      }
    

    我们看到,分别调用了ActivityRefWatcher.install(context, refWatcher);和FragmentRefWatcher.Helper.install(context, refWatcher);

    我们先来看Activity:

    public final class ActivityRefWatcher {
        //...
        
        public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
            Application application = (Application) context.getApplicationContext();
            ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    
            application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
      }
    
      private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
          new ActivityLifecycleCallbacksAdapter() {
            @Override public void onActivityDestroyed(Activity activity) {
              refWatcher.watch(activity);
            }
          };
    }
    

    这里可以看到,ActivityRefWatcher类中的install方法中创建了一个ActivityRefWatcher,并且注册了它的Lifecycle,在onActivityDestroyed时调用了refWatcher.watch(activity);来进行观察

    Fragment 的观察基本一致:

    public interface FragmentRefWatcher {
      final class Helper {
      
        public static void install(Context context, RefWatcher refWatcher) {
          List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
    
          if (SDK_INT >= O) {
            fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
          }
    
          try {
            Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
            Constructor<?> constructor =
                fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
            FragmentRefWatcher supportFragmentRefWatcher =
                (FragmentRefWatcher) constructor.newInstance(refWatcher);
            fragmentRefWatchers.add(supportFragmentRefWatcher);
          } catch (Exception ignored) {
          }
    
          if (fragmentRefWatchers.size() == 0) {
            return;
          }
    
          Helper helper = new Helper(fragmentRefWatchers);
    
          Application application = (Application) context.getApplicationContext();
          application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
        }
    
        private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
            new ActivityLifecycleCallbacksAdapter() {
              @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                for (FragmentRefWatcher watcher : fragmentRefWatchers) {
                  watcher.watchFragments(activity);
                }
              }
            };
      
        //...
      }
    }
    

    这里面的逻辑很简单,首先在将Android标准的Fragment的RefWatcher类,即AndroidOfFragmentRefWatcher添加到新创建的fragmentRefWatchers中。在使用反射将leakcanary-support-fragment包下面的SupportFragmentRefWatcher添加进来,如果你在app的build.gradle下没有添加下面这行引用的话,则会拿不到此类,即LeakCanary只会检测Activity和标准Fragment这两种情况。

    watcher.watchFragments(activity);最终调用到了:

    @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
        View view = fragment.getView();
        if (view != null) {
            refWatcher.watch(view);
        }
    }
    
    @Override
    public void onFragmentDestroyed(FragmentManagerfm, Fragment fragment) {
        refWatcher.watch(fragment);
    }
    

    可以看到,不管是activity还是fragment,最后都调用到了watch中,我们来看下watch(activity)方法中做了什么:

    public void watch(Object watchedReference, String referenceName) {
        if (this == DISABLED) {
          return;
        }
        checkNotNull(watchedReference, "watchedReference");
        checkNotNull(referenceName, "referenceName");
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);
        final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);
        
        ensureGoneAsync(watchStartNanoTime, reference);
    }
    
    • 为每个reference创建一个唯一的UUID,把这个UUID放入到set集合retainedKeys中。
    • 为watchedReference对象创建一个弱引用KeyedWeakReference
    • 调用ensureGoneAsync(watchStartNanoTime, reference);

    我们先看看KeyedWeakReference中做了什么:

    final class KeyedWeakReference extends WeakReference<Object> {
      public final String key;
      public final String name;
    
      KeyedWeakReference(Object referent, String key, String name,
          ReferenceQueue<Object> referenceQueue) {
        //1、
        super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
        this.key = checkNotNull(key, "key");
        this.name = checkNotNull(name, "name");
      }
    }
    

    在super中,将referent注册到queue中,这里说一下,在我们创建软引用、弱引用时,可以声明一个ReferenceQueue:

    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
    

    在KeyedWeakReference内部,使用了key和name标识了一个被检测的WeakReference对象。在注释1处,将弱引用和引用队列 ReferenceQueue 关联起来,如果弱引用reference持有的对象被GC回收,JVM就会把这个弱引用加入到与之关联的引用队列referenceQueue中。即 KeyedWeakReference 持有的 Activity 对象如果被GC回收,该对象就会加入到引用队列 referenceQueue 中。

    如果我们在创建一个引用对象时,指定了ReferenceQueue,那么当引用对象指向的对象达到合适的状态(根据引用类型不同而不同)时,GC 会把引用对象本身添加到这个队列中,方便我们处理它,因为“引用对象指向的对象 GC 会自动清理,但是引用对象本身也是对象(是对象就占用一定资源),所以需要我们自己清理。”

    参考:Reference 、ReferenceQueue 详解

    再来看看ensureGoneAsync(watchStartNanoTime, reference):

      private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        watchExecutor.execute(new Retryable() {
          @Override public Retryable.Result run() {
            return ensureGone(reference, watchStartNanoTime);
          }
        });
      }
    

    通过异步调用ensureGone(reference, watchStartNanoTime);,watchExecutor我们往回翻找可以发现其实是AndroidWatchExecutor,来看下AndroidWatchExecutor:

    public final class AndroidWatchExecutor implements WatchExecutor {
    
      static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
      private final Handler mainHandler;
      private final Handler backgroundHandler;
      private final long initialDelayMillis;
      private final long maxBackoffFactor;
    
      //1、
      public AndroidWatchExecutor(long initialDelayMillis) {
        mainHandler = new Handler(Looper.getMainLooper());
        HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
        handlerThread.start();
        backgroundHandler = new Handler(handlerThread.getLooper());
        this.initialDelayMillis = initialDelayMillis;
        maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
      }
    
      @Override public void execute(@NonNull Retryable retryable) {
        //2、
        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
          waitForIdle(retryable, 0);
        } else {
          postWaitForIdle(retryable, 0);
        }
      }
    
      private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
        mainHandler.post(new Runnable() {
          @Override public void run() {
            waitForIdle(retryable, failedAttempts);
          }
        });
      }
    
      private void waitForIdle(final Retryable retryable, final int failedAttempts) {
        // This needs to be called from the main thread.
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle() {
            //3、
            postToBackgroundWithDelay(retryable, failedAttempts);
            return false;
          }
        });
      }
    
      private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
        long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
        long delayMillis = initialDelayMillis * exponentialBackoffFactor;
        backgroundHandler.postDelayed(new Runnable() {
          @Override public void run() {
            //4、
            Retryable.Result result = retryable.run();
            if (result == RETRY) {
              //5、
              postWaitForIdle(retryable, failedAttempts + 1);
            }
          }
        }, delayMillis);
      }
    }
    
    • 1、initialDelayMillis默认为5s
    • 2、如果当前是主线程,调用waitForIdle(retryable, 0);,如果不是,通过handler机制切换到主线程调用waitForIdle(retryable, failedAttempts);
    • 3、queueIdle方法是在消息队列用完消息时调用,必须在主线程调用
    • 4、在子线程中调用retryable.run(),run()方法调用到了RefWatcher类ensureGoneAsync方法中的run方法
    • 5、如果是RETRY,那么递归调用postWaitForIdle,但是时间会加1s

    接下来我们在看下ensureGone(reference, watchStartNanoTime)方法:

    Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
        
        //1、
        removeWeaklyReachableReferences();
    
        //2、
        if (debuggerControl.isDebuggerAttached()) {
          // The debugger can create false leaks.
          return RETRY;
        }
        
        //3、
        if (gone(reference)) {
          return DONE;
        }
        
        //4、
        gcTrigger.runGc();
        
        //5、
        removeWeaklyReachableReferences();
        //6、
        if (!gone(reference)) {
          long startDumpHeap = System.nanoTime();
          long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
    
          //7、
          File heapDumpFile = heapDumper.dumpHeap();
          if (heapDumpFile == RETRY_LATER) {
            // Could not dump the heap.
            return RETRY;
          }
          long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
    
          HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
              .referenceName(reference.name)
              .watchDurationMs(watchDurationMs)
              .gcDurationMs(gcDurationMs)
              .heapDumpDurationMs(heapDumpDurationMs)
              .build();
    
          //8、
          heapdumpListener.analyze(heapDump);
        }
        return DONE;
      }
    
    private void removeWeaklyReachableReferences() {
        // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
        // reachable. This is before finalization or garbage collection has actually happened.
        KeyedWeakReference ref;
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
          retainedKeys.remove(ref.key);
        }
    }
    
    • 1、调用removeWeaklyReachableReferences,queue.poll()是从ReferenceQueue中取出queue的第一个Reference,如果它是可达的,那么将它从queue中移除并且返回,如果有不可达的Reference(该被回收的),直接返回null。通过while循环queue.poll(),可以将queue中的所有可达对象找出来并且从queue中移除,再调用将其从retainedKeys中remove掉。如果有需要被回收的对象,则直接停止循环
    • 2、如果当前是debug模式下,那么不执行回收策咯,因为在debug模式下可能存在假泄露
    • 3、如果retainedKeys为空,那么表示所有对象均被回收,不存在泄露
    • 4、执行gc
    • 5、再次调用removeWeaklyReachableReferences
    • 6、如果retainedKeys还不为空,那么说明存在不能被回收的弱引用对象,表示已经发生了泄露
    • 7、生成一个heapDumpFile,并且创建一个Notification来通知用户
    • 8、回调到ServiceHeapDumpListener的analyze方法中来分析泄露

    来看下ServiceHeapDumpListener类:

    public final class ServiceHeapDumpListener implements HeapDump.Listener {
    
      ...
    
      @Override public void analyze(@NonNull HeapDump heapDump) {
        checkNotNull(heapDump, "heapDump");
        HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
      }
    }
    

    可以看到,这里执行了HeapAnalyzerService的runAnalysis()方法,为了避免降低app进程的性能或占用内存,这里将HeapAnalyzerService设置在了一个独立的进程中。接着继续分析runAnalysis()方法里面的处理。

    public final class HeapAnalyzerService extends ForegroundService
    implements AnalyzerProgressListener {
    
        ...
    
        public static void runAnalysis(Context context, HeapDump heapDump,
        Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
            ...
            ContextCompat.startForegroundService(context, intent);
        }
    
        ...
        
        @Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
            ...
    
            // 1
            HeapAnalyzer heapAnalyzer =
                new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
    
            // 2
            AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
            heapDump.computeRetainedHeapSize);
            
            // 3
            AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
        }
            ...
    }
    
    

    这里的HeapAnalyzerService实质是一个类型为IntentService的ForegroundService,执行startForegroundService()之后,会回调onHandleIntentInForeground()方法。

    • 1、首先会新建一个HeapAnalyzer对象,顾名思义,它就是根据RefWatcher生成的heap dumps信息来分析被怀疑的泄漏是否是真的。
    • 2、调用它的checkForLeak()方法去使用haha库解析 hprof文件
    • 3、通过sendResultToListener调用到onHandleIntentInForeground中的onHeapAnalyzed,最后调用到DisplayLeakService中的DisplayLeakActivity.createPendingIntent来开启一个泄露的Activity

    到此,我们就分析完了整个过程,整理一下

    • 1、Leakcanary.install中创建了一个RefWatcher
    • 2、通过RefWatcher中的watch()来观察Activity和fragment,观察的方式是为被观察的activity或fragment创建一个WeakReference
    • 3、将WeakReference放入到一个set集合retainedKeys中
    • 4、调用检测方法,在异步线程中,先将retainedKeys中可达的全部移除
    • 5、一旦发现不可达的WeakReference,执行gc
    • 6、如果引用仍然没有被清除(retainedKeys不为空),那么它将会把堆栈信息保存在文件系统中的.hprof文件里。
    • 7、HeapAnalyzerService被开启在一个独立的进程中,并且HeapAnalyzer使用了HAHA开源库解析了指定时刻的堆栈快照文件heap dump。
    • 8、从heap dump中,HeapAnalyzer根据一个独特的引用key找到了KeyedWeakReference,并且定位了泄露的引用。
    • 9、HeapAnalyzer为了确定是否有泄露,计算了到GC Roots的最短强引用路径,然后建立了导致泄露的链式引用。
    • 10、这个结果被传回到app进程中的DisplayLeakService,然后一个泄露通知便展现出来了。

    相关文章

      网友评论

          本文标题:源码分析 - LeakCanary

          本文链接:https://www.haomeiwen.com/subject/wrebnktx.html