Android内存泄漏&leakcanary🐤2.7原理

2,261 阅读13分钟

一、内存泄漏

1.1 内存泄漏简介

  内存泄漏,是指一些对象已经不再需要,但是无法成功被gc回收,导致这部分内存无法释放,造成资源的浪费。当大量的内存泄漏堆积时,严重时还容易间接引发OOM。
  例如:当Activity被销毁后,理论上activity已经不再需要,内存空间理应被释放,但是如果有个静态变量持有了这个activity的引用,就会导致gc无法回收activity,造成内存泄漏。
  以下代码模拟了上述场景:MainActivity启动了TestActivity,然后再从TestActivity后退到MainActivity,理论上从TestActivity退出后执行了onDestroy(),就不再需要TestActivity,但是因为Utils的静态对象持有了TestActivity,导致TestActivity无法被gc回收,造成内存泄漏。实际开发中应该避免这种写法。

package com.bc.example;

public class Utils {
    public static Context cacheContext;
}

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

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

1.2 常见内存泄漏原因

  内存泄漏发生的原因:长生命周期对象持有短生命周期对象,导致短生命周期对象在不再需要时无法被gc回收。一般是由于代码bug引起,常见的内存泄漏原因有:

1.2.1 静态变量或单例导致的内存泄漏

例子如1.1中所示。
解决办法:静态变量或单例不要持有activity或view等对象的引用,如果必须持有引用可以改为WeakReference。

1.2.2 内部类导致的内存泄漏

内部类会持有外部类对象的引用,例如:

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

上述代码在编译后,生成的内部类MainActivity$InnerClass.class文件如下:

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

可见,内部类持有外部类的引用,所以对于内部类的使用需要注意内部类的对象是否被持久引用,导致外部类无法被释放。
解决办法:考虑是否可以使用静态内部类实现,静态内部类相当于普通类,不会持有外部类的引用。

1.2.3 匿名内部类导致的内存泄漏

匿名内部类同样会持有外部类对象的引用,以下面匿名内部Handler类为例:

public class MainActivity extends Activity {

    private Handler handler = new Handler() {

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

上述代码在编译后,会生成匿名内部类MainActivity$1.class文件如下:

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

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

  可见,匿名内部handler类持有了外部Activity的引用,这样,当Activity销毁后,如果该Handler仍有message在队列中,因为message.target指向该handler,而handler又持有了activity,导致activity无法被gc回收,发生内存泄漏。
  解决办法:在销毁activity时调用handler.removeCallbacksAndMessages();考虑是否可以使用静态内部类实现,静态内部类相当于普通类,不会持有外部类的引用;当业务确实需要调用activity内的方法时,以WeakReference的方式进行调用。

1.2.4 动画导致的内存泄漏

首先,分析Animator动画启动的源码如下:

public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {

    private void start(boolean playBackwards) {
        // ...省略部分代码
        // 重点代码
        addAnimationCallback(0);
    }

    private void addAnimationCallback(long delay) {
        // 将自己添加到单例AnimationHandler的FrameCallback中
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }
    
    @Override
    public void end() {
        // ...省略
        endAnimation();
    }
    
    @Override
    public void cancel() {
        // ...省略
        endAnimation();
    }

    private void endAnimation() {
        // ...省略
        removeAnimationCallback();
    }

    private void removeAnimationCallback() {
        // 将自己从AnimationHandler的FrameCallback中移除
        getAnimationHandler().removeCallback(this);
    }
}

/**
* AnimationHandler是单例实现,用来接收Choreographer.FrameCallback回调完成动画
*/
public class AnimationHandler {
    public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
    private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
            new ArrayList<>();

    public static AnimationHandler getInstance() {
        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));
        }
    }
}

  由以上代码分析可知,Animator在start()后会被一个单例类持有,在动画end或cancel后从callback列表中移除。
  在开发过程中,如果Animator定义的匿名内部类listener又持有了activity,而动画是无限循行ValueAnimator.INFINITE,就导致动画start后如果不cancel,会发生内存泄漏。

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);
        // 匿名内部类listener会持有外部类的引用
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                Float alpha = (Float) animator.getAnimatedValue();
                textView.setAlpha(alpha);
            }
        });
    }
    
    @Override
    protected void onDestroy() {
        animator.cancel();
    }
}

解决方案:在activity销毁时,调用动画的cancel方法。

1.2.5 资源未关闭导致的内存泄露

资源性对象(例如InputStream/OutputStream,Cursor,File文件等)未及时close,也会导致gc无法回收这部分内存。

1.2.6 Eventbus未取消注册导致内存泄漏

Eventbus的实现原理是一个单例中的map持有已注册的对象(详见:Eventbus原理分析),所以如果不取消注册(EventBus.getDefault().unregister(this))就会造成内存泄漏。

1.2.7 其他

此外,还有注册了BroadcastReceiver、listener、RxJava subscription但是未取消注册等,也会造成内存泄漏。在开发中可能造成内存泄漏的bug很多,对于listener、receiver、observer、callback等对象,开发者应该仔细检查引用关系是否必要或合理,是否可能造成内存泄漏。

1.3 四种引用类型

(1)强引用(StrongReference):在GC时,不会被回收;
(2)软引用(SoftReference):内存不足时,如果一个对象只有软引用,才会被回收;
(3)弱引用(WeakReference):在GC时,如果一个对象只有弱引用,则会被回收;
(4)虚引用(PhantomReference):任何时候都有可能被回收;

二、hprof文件

2.1 hprof文件获取方式

  hprof即heap profile文件,表示当前堆的内存快照。通过分析hprof文件,可以查看当前哪些对象存在泄漏。
获取hprof文件的方式有三种:
(1)AS的Profiler工具
Android studio的Profiler工具,在memory工具栏点击Dump Java heap即可捕捉当前堆内存快照。也可以导入hprof文件展示。
image.png (2)adb命令行获取

adb shell am dumpheap <processname> <filename>

(3)代码获取

// 下面代码会暂时挂起所有线程,并将当前堆快照保存到指定的fileName文件
Debug.dumpHprofData(fileName)

2.2 hprof文件分析工具

2.2.1 AS的Profiler工具

  以1.1中的代码为例,先启动SecondActivity,然后再返回到MainActivity;先点击profiler中的强制垃圾回收,再点击dump后,profiler工具会列出当前堆快照,并列出存在内存泄漏对象,如下所示::
image.png 在点击leak后,可看到泄漏的SecondActivity对象的引用链如下: image.png 可根据GC root引用链修复内存泄漏。

2.2.2 Shark

Shark是leakcanary2中用到的堆内存分析工具。运行速度快,占用内存少,适用于在客户端上分析hprof。
如果项目中需要在app运行过程中分析hprof,可考虑使用,暂不深入研究。leakcanary1.x的Haha使用已经较少使用,不再介绍。

2.2.3 eclipse的MAT工具

eclipse的MAT工具的使用方式与AS的Profiler工具的使用方式类似,也可以强制垃圾回收,导出当前堆快照hprof文件,分析hprof后展示引用链。暂不详细介绍。

三、leakcanary简介

  综上所述,比较原始的修复内存泄漏的办法是:在Activity退出后,导出当前堆快照hprof,然后使用分析工具打开hprof文件,分析是否存在内存泄漏,并切断引用链来修复内存泄漏。
  在实际开发中,这种做法很繁琐,需要每次Activity销毁后由开发者导出hprof并分析,我们希望有一个工具可以在发生内存泄漏的时候自动导出hprof文件并分析,将内存泄漏的引用链展示出来,leakcanary就提供了这样的功能。
  leakcanary是一个安卓内存泄漏检测库,用来帮助开发者检测Activity、FragmentAndViewModel、RootView、Service四种对象是否存在内存泄漏。

3.1 基础用法

leakcanary用法很简单,只需要在app的build.gradle中加入如下依赖

dependencies { 
    // debugImplementation because LeakCanary should only run in debug builds. 
    // 可以看出,只有在编译debug包时,才会将leakcanary的代码打入包内
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' 
}

至此,leakcanary就可以正常工作了。在app启动后,可以看到leakcanary输出的log表示leakcanary正在运行:

D LeakCanary: LeakCanary is running and ready to detect leaks

3.2 使用方法

  在安装app后,桌面除了开发者的app图标外,还会多出一个leak的图标可以打开leakcanary的activity。在发生内存泄漏时,leakcanary会在合适的时机(泄漏的对象数量达到默认的阈值5或Application不可见,详见第四小节)自动dump当前堆快照hprof,然后分析是否有内存泄漏,并在leakcanary的activity中展示出来。开发者也可以自己点击'Dump Heap Now'来主动触发内存泄漏检测。
  仍然以1.1中例子为例,在从SecondActivity返回后,点击'Dump Heap Now'可看到leakcanary给出了内存泄漏的引用链,如下: image.png   可见,是因为Utils引用了SecondActivity对象,导致SecondActivity无法被回收,可考虑删掉该引用,或者改用WeakReference来修复内存泄漏。

四、leakcanary原理

在build.gradle中依赖debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'后,可以看到引入了以下aar: image.png   leakcanary自动检测内存泄漏,主要分为四步:
(1)自动监测Activity、FragmentAndViewModel、RootView、Service的销毁,判断是否存在内存泄漏;
(2)在发生内存泄漏时,捕捉内存快照hprof文件;
(3)在HeapAnalyzerService使用Shark分析hprof文件;
(4)将分析出的内存泄漏结果进行归类并展示。

4.1 自动监测Activity等

4.1.1 leackcanary初始化

  在leakcanary1.x中,需要开发者在代码中调用leakcanary的初始化方法才能实现自动监测:

pulib class MyApplication extends Application {
    @Override
    public void onCreate() {
        // leakcanary1.x需要开发者调用后才能实现自动监测
        LeakCanary.install(this);
        super.onCreate();
    }
}

  而在leakcanary2.x中,只需要在build.gradle中添加依赖即可,原因是因为leakcanary2.x中利用了ContentProverder在Application创建后就开始创建的原理(源码分析见第五节),在自定义的ContentProverder调用类似的manualInstall方法实现app启动后自动初始化并监测object。
  leakcanary2.x中实现自动监测的ContentProverder是AppWatcherInstaller,声明见leakcanary-object-watcher-android-2.7.aar的mainfest中:

<?xml version="1.0" encoding="utf-8"?>
<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>

  leakcanary2.x的AppWatcherInstaller源码如下:

internal sealed class AppWatcherInstaller : ContentProvider() {

  internal class MainProcess : AppWatcherInstaller()

  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    // ContentProvider会在Application创建后就执行创建并执行onCreate()
    // 在onCreate()里调用单例AppWatcher的manualInstall方法
    AppWatcher.manualInstall(application)
    return true
  }
}

/**
* 单例AppWatcher
*/
object AppWatcher {

  /**
  * @param watchersToInstall 默认值为appDefaultWatchers(application)
  */
  @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
    ) {
    watchersToInstall.forEach {
      it.install()
    }
  }

  /**
  * 默认的Watcher,有四类: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)
      )
  }
}

  根据上面源码分析可知,leakcanary2.x是在application创建后就调用AppWatcher的manualInstall方法,方法里默认实现了四种watcher(ActivityWatcher、FragmentAndViewModelWatcher、RootViewWatcher、ServiceWatcher)来分别监测Activity、FragmentAndViewModel、RootView、Service的引用,来监测这四种对象是否存在内存泄漏。
  此外,PlumberInstaller也是通过ContentProvider的方式在Application启动后就初始化,来增加对一些比较少见的对象的内存泄漏检测,这里不深入分析。

4.1.2 ObjectWatcher自动监测

  接下来,主要分析AppWatcher默认提供的四种watcher的实现方式,实际上四种Watcher主要负责在Activity、FragmentAndViewModel、RootView、Service销毁时传递引用,objectWatcher以弱引用的形式间隔一段时间分析当前对象是否被回收,如果已经被回收则不存在内存泄漏,否则说明可能存在内存泄漏。以下是源码分析:

object AppWatcher {

  /**
   * objectWatcher用来检测当前正在监测的对象是否可能存在内存泄漏
   */
  val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
      // retainedDelayMillis默认为5s
      mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true }
  )

  fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      // 重点,四种Watcher都将objectWatcher作为参数传递
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }
}

  以ActivityWatcher为例,初始化时时通过注册ActivityLifecycleCallback实现Activity销毁时收到回调,收到回调后将引用传递给objectWatcher,objectWatcher持有该Activity的弱引用,并在一定时间后(默认5s)分析是否被回收。

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

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        // 由ObjectWatcher分析expectWeaklyReachable,判断Activity在destroy后是否可能存在内存泄漏
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

  类似的,FragmentAndViewModelWatcher、RootViewWatcher、ServiceWatcher主要负责在对象销毁时将引用传递给objectWatcher,由objectWatcher判断是否被回收,主要区别是判断销毁的时机有所区别,Activty、Fragment、RootView通过注册系统回调,Service通过Proxy.newProxyInstance()代理方式实现。
  接下来,主要分析ObjectWatcher收到expectWeaklyReachable()回调时如何判断对象是否被回收。

class ObjectWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {
  /**
   * 以弱引用形式保存当前正在观察的对象
   */
  private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
  /**
   * 所有弱引用对象注册到的ReferenceQueue,用来判断弱引用所指向的对象是否被回收
   */
  private val queue = ReferenceQueue<Any>()
  /**
   * 当检测到对象可能发生内存泄漏时的listener,4.2小节再做分析
   */
  private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()

  /**
  * 在Activity销毁后,以弱引用方式持有其引用,用于后续判断是否被回收
  */
  @Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
  ) {
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // 弱引用持有Activity
    val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    // 将Activity的弱引用添加到观察列表里
    watchedObjects[key] = reference
    // 由checkRetainedExecutor间隔5s在主线程判断对象是否被回收,checkRetainedExecutor的创建方法在AppWatcher里,不再分析
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }

  /**
  * 在当前观察的对象列表中,将已经被回收的对象移除,剩余的其他对象就是可能发生泄漏的对象
  */
  @Synchronized private fun moveToRetained(key: String) {
    // 移除掉已经被回收的对象
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      // 剩余可能发生泄漏的对象,则会调Listener的方法做进一步分析(4.2小节分析源码)
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

  private fun removeWeaklyReachableObjects() {
    var ref: KeyedWeakReference?
    do {
      // ReferenceQueue中有该WeakReference,则说明该WeakReference指向的对象已经被回收
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
}

4.1.3 KeyedWeakReference

  上面提到,在Activity执行onDestroy()时,Activity对象以KeyedWeakReference弱引用的形式被ObjectWatcher持有,KeyedWeakReference本质上是WeakReference:

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

  如何判断一个对象是否被回收,是通过WeakReference与ReferenceQueue的关系实现的:在创建WeakReference对象时可以指定一个ReferenceQueue对象,当该WeakReference指向的对象被GC标记可以回收后,该WeakReference会被加入到ReferenceQueue的末尾。WeakReference有两个构造函数,源码如下:

public class WeakReference<T> extends Reference<T> {
    /**
     * 创建一个指向传入对象的弱引用
     */
    public WeakReference(T referent) {
        super(referent);
    }

    /**
     * 创建一个指向传入对象的弱引用,并且将该弱引用注册到指定ReferenceQueue
     * 当弱引用所指向的对象被GC标记可以回收后,该弱引用会被放到ReferenceQueue的末尾
     */
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

4.1.4 总结

  leakcanary2.x利用ContentProvider在Application创建后就创建的原理,在ContentProvider创建时即完成leakcanary初始化,方便开发者使用。
  leakcanary通过ActivityWatcher、FragmentAndViewModelWatcher、RootViewWatcher、ServiceWatcher在对象销毁时将引用传递给objectWatcher,由objectWatcher判断对象是否可能存在内存泄漏。
  objectWatcher在Activity、FragmentAndViewModel、RootView、Service销毁5s后,在主线程判断对象是否只被GC标记回收,如果没有则认为可能存在内存泄漏,接着触发HeapDumpTrigger(在4.2小节分析)在工作线程再次检查是否存在内存泄漏,是则导出hpfo文件并分析内存泄漏的引用链。
  objectWatcher判断观察的对象是否被回收的原理是:在创建WeakReference对象时可以指定一个ReferenceQueue对象,当该WeakReference指向的对象被GC标记可以回收后,该WeakReference会被加入到ReferenceQueue的末尾。

4.2 确认内存泄漏

  上面讲到,objectWatcher在认为可能发生内存泄漏后会回调OnObjectRetainedListener的onObjectRetained()方法:

onObjectRetainedListeners.forEach { it.onObjectRetained() }

4.2.1 addOnObjectRetainedListener的时机

  那么,接下来继续分析AppWatcher.objectWatcher.addOnObjectRetainedListener(this)是在什么时机添加的,添加的时机也是在AppWatcher的manualInstall方法里:

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

internal object LeakCanaryDelegate {

  val loadLeakCanary by lazy {
    try {
      // 实现类是InternalLeakCanary
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE").get(null) as (Application) -> Unit
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
  }
}

收到可能存在内存泄漏的回调后,主要逻辑是在InternalLeakCanary中完成,源码如下:

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

  private var _application: Application? = null

  override fun invoke(application: Application) {
    _application = application
    // 这里就是objectWatcher.addOnObjectRetainedListener的时机,可见是在初始化时就添加好的
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
    // 触发GC的实现
    val gcTrigger = GcTrigger.Default
    val configProvider = { LeakCanary.config }
    // 工作线程
    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)
    // heapDump的触发器,在工作线程
    heapDumpTrigger = HeapDumpTrigger(application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,configProvider)
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    registerResumedActivityListener(application)
    // 添加桌面快捷方式
    addDynamicShortcut(application)

    // logcat相关 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()
            )
          }
        }
      }
    }
  }
}

4.2.2 onObjectRetained

  根据以上分析,收到可能存在内存泄漏的回调后,接下来主要逻辑是在InternalLeakCanary中完成,接着分析收到回调后做了哪些工作,源码如下:

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

  fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
      // heapDumpTrigger是在上述InternalLeakCanary.invoke方法里创建的
      heapDumpTrigger.scheduleRetainedObjectCheck()
    }
  }
}

4.2.3 HeapDumpTrigger

  HeapDumpTrigger会在工作线程再次校验一次是否存在内存泄漏,检验方式是触发一次gc,然后检查是否仍有对象未被GC标记回收,如果是则进行dump heap,然后调用HeapAnalyzerService对hprof文件做进一步分析:

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并做进一步分析
   * */
  fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
    ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      return
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    // 工作线程调度
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      // 主要方法,下面分析
      checkRetainedObjects()
    }, delayMillis)
  }

  /**
   * 触发gc再次确认是否存在内存泄漏,是则执行dump heap
   * */
  private fun checkRetainedObjects() {
    val config = configProvider()
    // 1.查看当前objectWatcher持有的对象
    var retainedReferenceCount = objectWatcher.retainedObjectCount
    // 2.objectWatcher持有对象时,先触发一次gc
    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }
    // 3.gc后,如果objectWatcher持有的引用数小于5,则return
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
    // 4.gc后,如果objectWatcher持有的引用数大于5,则dump heap做进一步分析
    // 每次dump heap之间的间隔需要>=60s,小于60s则return,并延迟到间隔满足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)
        )
      // 间隔小于60s,则延迟一段时间后再执行heap dump
      scheduleRetainedObjectCheck(
        delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
        )
      return
    }

    dismissRetainedCountNotification()
    val visibility = if (applicationVisible) "visible" else "not visible"
    // 5.执行dump heap
    dumpHeap(
      retainedReferenceCount = retainedReferenceCount,
      retry = true,
      reason = "$retainedReferenceCount retained objects, app is $visibility"
      )
  }

  /**
   * 首先Debug.dumpHprofData,然后调用HeapAnalyzerService对hprof文件做进一步分析
   * */
  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean,
    reason: String
    ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
      // 1.heapDumper.dumpHeap()里面执行了Debug.dumpHprofData(heapDumpFile.absolutePath)来dump heap
    when (val heapDumpResult = heapDumper.dumpHeap()) {
      is HeapDump -> {
        lastDisplayedRetainedObjectCount = 0
        lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
        objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
          // 2.调用HeapAnalyzerService对hprof文件做进一步分析
        HeapAnalyzerService.runAnalysis(
          context = application,
          heapDumpFile = heapDumpResult.file,
          heapDumpDurationMillis = heapDumpResult.durationMillis,
          heapDumpReason = reason
          )
      }
    }
  }
}

4.2.4 GcTrigger

  HeapDumpTrigger中触发垃圾回收是由GcTrigger实现的,源码单独分析如下:

interface GcTrigger {
  fun runGc()

  /**
   * GcTrigger的默认实现
   */
  object Default : GcTrigger {
    override fun runGc() {
      // 调用gc()并不保证一定会执行gc,所以这里gc后线程暂停了100ms,然后又再次触发了gc
      Runtime.getRuntime().gc()
      enqueueReferences()
      System.runFinalization()
    }

    private fun enqueueReferences() {
      // 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()
      }
    }
  }
}

4.2.5 总结

  ObjectWatcher只能检测到当前有对象可能存在内存泄漏,在HeapDumpTrigger会在工作线程再次校验一次是否存在内存泄漏,检验方式是触发一次gc,然后检查是否有对象未被GC标记回收,如果是则表示发生了内存泄漏,接着进行dump heap,然后调用HeapAnalyzerService对hprof文件做进一步分析。
  每次HeapDumpTrigger进行dump heap有60s时间间隔限制,因为dump heap会暂时挂起当前进程中的所有java线程。

4.3 分析hprof文件

4.3.1 HeapAnalyzerService

  在HeapDumpTrigger把hprof文件输出后,会调用HeapAnalyzerService启动服务去分析hprof文件,得到最终的内存泄漏分析结果。源码如下:

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)
      // 将heapDumpFile封装到intent,传给HeapAnalyzerService分析
      intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
      startForegroundService(context, intent)
    }
  }

  override fun onHandleIntentInForeground(intent: Intent?) {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
    // 1.获取hprof文件路径
    val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
    val config = LeakCanary.config
    val heapAnalysis = if (heapDumpFile.exists()) {
      // 2.分析hprof文件
      analyzeHeap(heapDumpFile, config)
    } else {
      missingFileFailure(heapDumpFile)
    }
    val fullHeapAnalysis = when (heapAnalysis) {
      // 3.分析结果被封装成HeapAnalysisSuccess对象
      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.将分析结果回调给listern,更新UI将分析结果展示给开发者
    config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
  }
}

4.3.2 HeapAnalysisSuccess

  对于Shark分析hprof文件的代码暂不深入研究,下面给出了分析结果的封装类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>,
  /**
   * 当前APP内泄漏的对象列表,开发者主要关注
   */
  val applicationLeaks: List<ApplicationLeak>,
  /**
   * 当前APP内第三方库内泄漏的对象列表,开发者不一定可以修复
   */
  val libraryLeaks: List<LibraryLeak>,
  val unreachableObjects: List<LeakTraceObject>
) : HeapAnalysis() {
  /**
   * 所有泄漏的对象
   */
  val allLeaks: Sequence<Leak>
    get() = applicationLeaks.asSequence() + libraryLeaks.asSequence()

}

五、其他

5.1 ContentProvider创建时机分析

  在4.1中介绍过,ContentProvider是在Application创建后就创建的,且ContentProvider的onCreate时机在Application的onCreate时机之前。
  下面从Android-30源码分析,在AMS启动Application后,会执行到ActivityThread.H类中,源码如下:

public final class ActivityThread extends ClientTransactionHandler {

    class H extends Handler {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                // 1.启动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;
                // 退出Application
                case EXIT_APPLICATION:
                    if (mInitialApplication != null) {
                        mInitialApplication.onTerminate();
                    }
                    Looper.myLooper().quit();
                    break;
                // ...省略
            }
        }
    }

    private void handleBindApplication(AppBindData data) {
        // 1.设置进程的一些信息
        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.创建AppContext和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.创建Application
        Application app;
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;
        
        // 4.创建ContentProvider
        if (!data.restrictedBackupMode) {
            if (!ArrayUtils.isEmpty(data.providers)) {
                // 下面分析
                installContentProviders(app, data.providers);
            }
        }
        
        // 5.执行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) {
            // 实例化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.实例化ContentProvider
        localProvider = packageInfo.getAppFactory().instantiateProvider(cl, info.name);
        // 2.调用ContentProvider对象的attachInfo,里面会调用ContentProvider的onCreate()方法
        localProvider.attachInfo(c, info);
    }
}

The End

欢迎关注我,一起解锁更多技能:BC的主页~~💐💐💐 个人信息汇总.png

leakcanary🐤官方文档:square.github.io/leakcanary/
hprof简介:hg.openjdk.java.net/jdk6/jdk6/j…