工作中遇到的Android内存优化问题-leakcanary源码解析

Posted ccx-_-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了工作中遇到的Android内存优化问题-leakcanary源码解析相关的知识,希望对你有一定的参考价值。

今天我们来看一下一个内存泄漏检测神器 leakcanary(https://github.com/square/leakcanary)

首先我们来看一下leakcanary的使用说明


就这么多,只需要一行代码,太简单了,简单得都有点怀疑它了。

我们来看一下一个简单的例子,也是它官方源码中提供的一个例子,这个因为太小了我就截了个图


从例子中可以看到,AsyncTask执行了sleep操作,但是由于AsyncTask声明为了一个内部匿名类,此类持有外部类的对象,导致用户退出此Activity时,此Activity不能被gc回收,安装此例子到手机,点击START NEW ASYNCTASK,退出app,观察手机,会弹出一个内存泄漏通知如下图



很神奇吧,连泄漏的堆栈调用信息都能查到,比我们在前两篇用到的工具方便多了


leakcanary很神奇,就像魔术一样,我们很想知道它背后的运行机制,现在我们就来解析一下leakcanary的源码。首先从我们应用Application入手,因为leakcanary在使用中只有一行代码,我们就从这行代码慢慢跟踪一下源码。

public class ExampleApplication extends Application 

  @Override public void onCreate() 
    super.onCreate();
    LeakCanary.install(this);
  

首先我们进入install方法,install方法调用了另一个install方法

  public static RefWatcher install(Application application) 
    return install(application, DisplayLeakService.class,
        androidExcludedRefs.createAppDefaults().build());
  


  /**
   * Creates a @link RefWatcher that reports results to the provided service, and starts watching
   * activity references (on ICS+).
   */
  public static RefWatcher install(Application application,
                                   Class<? extends AbstractAnalysisResultService> listenerServiceClass,
                                   ExcludedRefs excludedRefs) 
    if (isInAnalyzerProcess(application)) 
      return RefWatcher.DISABLED;
    
    enableDisplayLeakActivity(application);
    //此Listener很重要,在后面会扮演重要角色
    HeapDump.Listener heapDumpListener =
            new ServiceHeapDumpListener(application, listenerServiceClass);
    //从名字我们就可以看出它是监视内存泄漏对象的
    RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
    //
    ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
    return refWatcher;
  



接着进入installOnIcsPlus方法,此方法就到了关键的地方,能解开为什么我们只用一个方法,就能监听所有的内存泄漏

  public static void installOnIcsPlus(Application application, RefWatcher refWatcher) 
    if (SDK_INT < ICE_CREAM_SANDWICH) 
      // If you need to support Android < ICS, override onDestroy() in your base activity.
      return;
    
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    activityRefWatcher.watchActivities();
  


 ActivityRefWtacher提供了一个方法 watchActivitys()

  public void watchActivities() 
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  

Android4.0以上的Application中提供了registerActivityLifecycleCallbacks方法,此方法从名字就可以看出是监听Activity生命周期的,我们再来看看参数lifecycleCallbacks的定义,

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() 
        @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) 
        

        @Override public void onActivityStarted(Activity activity) 
        

        @Override public void onActivityResumed(Activity activity) 
        

        @Override public void onActivityPaused(Activity activity) 
        

        @Override public void onActivityStopped(Activity activity) 
        

        @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) 
        

        @Override public void onActivityDestroyed(Activity activity) 
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        
      ;

看到了吧,所有Activity们只要调用了onDestroy方法,就会被回调方法onActivityDestroyed知道,然后传入 ActivityRefWatcher的方法 onActivityDestroyed中,此方法很简单

  void onActivityDestroyed(Activity activity) 
    refWatcher.watch(activity);
  

refWatcher就是我们上面install方法中创建的,进入watch方法

 public void watch(Object watchedReference, String referenceName) 
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    if (debuggerControl.isDebuggerAttached()) 
      return;
    
    final long watchStartNanoTime = System.nanoTime();
    //首先生成了一个id,此id是用来唯一标识这个检测对象的
    String key = UUID.randomUUID().toString();
    //将id存起来
    retainedKeys.add(key);
    //KeyWeakReference集成自 WeakReference(弱引用),WeakReference使用来跟踪这个对象的,
    //弱引用大家都明白,它不会影响gc回收,构造WeakReference时,可以传入一个ReferenceQueue,
    //这个ReferenceQueue的主要作用是当对象不可达时也就是可以被gc回收时,对象所对应的WeakReference就会被放入
    //ReferenceQueue中,只要检测ReferenceQueue是否有我们的对象的WeakReference,就可以判断对象是否可能泄漏
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    watchExecutor.execute(new Runnable() 
      @Override public void run() 
        //此方法就是为了确认对象是否可回收
        ensureGone(reference, watchStartNanoTime);
      
    );
  

进入ensureGone方法

void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) 
    long gcStartNanoTime = System.nanoTime();

    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    //此方法是循环ReferenceQueue,如果对象的ReferenceQueue在里面,就从retainedKeys中移除对象的key,
    //因为此对象已经可回收,是安全的
    removeWeaklyReachableReferences();
    //判断我们要检测的reference是否还在retainedKeys中,如果不在说明已经被移除了,也就是可以被gc回收了
    if (gone(reference) || debuggerControl.isDebuggerAttached()) 
      return;
    
    //执行垃圾回收,但是只是建议,并不是一定会执行
    gcTrigger.runGc();
    //再次从retainedKeys移除安全的key
    removeWeaklyReachableReferences();
    //如果此对象的WeakReference还是不能被回收,那么此对象就有可能泄漏了,只是可能,因为gc在上一步可能没有运行
    if (!gone(reference)) 
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      //此方法获得内存Heap的hprof文件,LeakCanary之所以这么好用,主要是在这里,它分析了hprof文件,来确认内存泄漏,
      //我们在上一篇也分析过hprof文件,原来LeakCanary也是分析这个文件,只是不需要人工分析了,LeakCanary用了一个自己
      //的开源hprof分析库haha(https://github.com/square/haha)此库是基于google的perflib.
      File heapDumpFile = heapDumper.dumpHeap();

      if (heapDumpFile == HeapDumper.NO_DUMP) 
        // Could not dump the heap, abort.
        return;
      
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      //heapdumpListener主要就是启动服务分析hprof文件
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    
  

heapdumpListener在前面创建的时候是一个ServiceHeapDumpListener对象,进入此对象的analyze方法

  @Override public void analyze(HeapDump heapDump) 
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  

  public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) 
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  

启动服务分析hprof文件,接着我们来看看这个服务

  @Override protected void onHandleIntent(Intent intent) 
    if (intent == null) 
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    //分析hprof的核心类
    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
    //检查我们的对象是否内存泄漏
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  
进入checkForLeak方法
 public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) 
    long analysisStartNanoTime = System.nanoTime();

    if (!heapDumpFile.exists()) 
      Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    

    try 
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      //解析器解析文件
      HprofParser parser = new HprofParser(buffer);
      //解析过程,是基于google的perflib库,根据hprof的格式进行解析,这里就不展开看了
      Snapshot snapshot = parser.parse();
      //分析结果进行去重
      deduplicateGcRoots(snapshot);
      //此方法就是根据我们需要检测的类的key,查询解析结果中是否有我们的对象,获取解析结果中我们检测的对象
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);
      //此对象不存在表示已经被gc清除了,不存在泄露因此返回无泄漏
      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) 
        return noLeak(since(analysisStartNanoTime));
      
      //此对象存在也不能也不能确认它内存泄漏了,要检测此对象的gc root
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
     catch (Throwable e) 
      return failure(e, since(analysisStartNanoTime));
    
  

我们重点看一下findLeakingReference方法

  private Instance findLeakingReference(String key, Snapshot snapshot) 
    //因为需要检测的类都构造了一个KeyedWeakReference,因此先找到KeyedWeakReference,就可以找到我们的对象
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    List<String> keysFound = new ArrayList<>();
    //循环所有KeyedWeakReference实例
    for (Instance instance : refClass.getInstancesList()) 
      List<ClassInstance.FieldValue> values = classInstanceValues(instance);
      //找到KeyedWeakReference里面的key值,此值在我们前面传入的对象唯一标示
      String keyCandidate = asString(fieldValue(values, "key"));
      //当key值相等时就表示是我们的检测对象
      if (keyCandidate.equals(key)) 
        return fieldValue(values, "referent");
      
      keysFound.add(keyCandidate);
    
    throw new IllegalStateException(
        "Could not find weak reference with key " + key + " in " + keysFound);
  
最后一步,也是最核心的方法,确认是否内存泄漏,和我们手动分析hprof的方法几乎相同
  private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef) 

    //这两行代码是判断内存泄露的关键,我们在上篇中分析hprof文件,判断内存泄漏
    //判断的依据是展开调用到gc root,所谓gc root,就是不能被gc回收的对象,
    //gc root有很多类型,我们只要关注两种类型1.此对象是静态 2.此对象被其他线程使用,并且其他线程正在运行,没有结束
    //pathFinder.findPath方法中也就是判断这两种情况
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
    // 找不到引起内存泄漏的gc root,就表示此对象未泄漏
    // False alarm, no strong reference path to GC Roots.
    if (result.leakingNode == null) 
      return noLeak(since(analysisStartNanoTime));
    

    //生成泄漏的调用栈,为了在通知栏中显示
    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);

    String className = leakingRef.getClassObj().getClassName();

    // Side effect: computes retained size.
    snapshot.computeDominators();

    Instance leakingInstance = result.leakingNode.instance;

    //计算泄漏的空间大小
    long retainedSize = leakingInstance.getTotalRetainedSize();

    retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);

    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
        since(analysisStartNanoTime));
  

核心的代码我们已经看完了,是不是有一种豁然开朗的感觉,这就是好的软件,将重复繁琐的工作封装起来,只给我们留下一个两行的使用说明









以上是关于工作中遇到的Android内存优化问题-leakcanary源码解析的主要内容,如果未能解决你的问题,请参考以下文章

工作中遇到的Android内存优化问题

工作中遇到的Android内存优化问题

工作中遇到的Android内存优化问题

工作中遇到的Android内存优化问题-leakcanary源码解析

工作中遇到的Android内存优化问题-leakcanary源码解析

Android遇到内存泄漏和性能优化,需要采取以下措施