Android 排查内存神器-LeakCanary

2,735 阅读6分钟
原文链接: www.jianshu.com

前言

LeakCanary是Square公司提供的用于Android检测内存的小工具,他能帮助我们快速定位代码隐藏的BUG,减少OOM的机会。

此处为git地址链接:github.com/square/leak…

题外话:Square真的是家良心公司,提供了很多有名的组件。后续会整理目前市面上有名的组件。比如Facebook的开源组件... 现在先介绍下Square有哪些开源组件

OKHttp 一个开源稳定的Http的通信依赖库,感觉比HttpUrlConnection好用,
    okhttp现在已经得到Google官方的认可了。

okie  OKHttp依赖这个库

dagger 快速依赖注入框架。现在已经由google公司维护了,
现在应该是dagger2.官网地址:https://google.github.io/dagger/

picasso 一个图片缓存库,可以实现图片的下载和缓存功能

retrofit 是一个RESTFUL(自行百度)的Http网络请求框架的封装,基于OKHttp,retrofit在于对接口的封装,实质是使用OKHttp进行网络请求

leakcanary 一个检测内存的小工具,本文说的就是这个

otto Android事件总线,降低代码的耦合性,可以跟EventBus做比较

...

回到正文,现在开始讲解LeakCanary的使用

使用LeakCanary

其实可以参考leakcanary的Sample介绍

  1. 首先在build.gradle中引用
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.2.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha9'
    testCompile 'junit:junit:4.12'

    // LeakCanary
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
  1. 在Application的onCreate添加方法
    public class ExampleApp extends Application{
    @Override
    public void onCreate() {
        super.onCreate();
        // LeakCanary初始化
        LeakCanary.install(this);
    }
}
  1. 在App中添加内存泄漏代码,本文就参照sample中的的例子,写了一个SystemClock.sleep(20000);
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Button asynTaskBtn = (Button) this.findViewById(R.id.async_task);
        asynTaskBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startAsyncTask();
            }
        });
    }

    private void startAsyncTask() {
        // This async task is an anonymous class and therefore has a hidden reference to the outer
        // class MainActivity. If the activity gets destroyed before the task finishes (e.g. rotation),
        // the activity instance will leak.
        new AsyncTask<Void, Void, Void>() {
            @Override protected Void doInBackground(Void... params) {
                // Do some slow work in background
                SystemClock.sleep(20000);
                return null;

            }
        }.execute();
    }
}
  1. 运行,会发现出现一个LeakCanary的图标。后续会介绍这个图标是如何出现的,当出现内存泄漏的时候,会在通知栏显示一条内存泄漏通知,点击通知会进入内存泄漏的具体问题。

    LeakCanary图标

通知栏显示内存泄漏

内存泄漏详情

根据图标显示我们能够看出内存泄漏在AsyncTask中,根据AsyncTask中进行内存修改

讲解完如何使用,现在开始讲解LeakCanary。

LeakCanary详解

代码目录结构

.
├── AbstractAnalysisResultService.java 
├── ActivityRefWatcher.java -- Activity监控者,监控其生命周期
├── AndroidDebuggerControl.java --Android Debug控制开关,就是判断Debug.isDebuggerConnected()
├── AndroidExcludedRefs.java -- 内存泄漏基类
├── AndroidHeapDumper.java --生成.hrpof的类
├── AndroidWatchExecutor.java -- Android监控线程,延迟5s执行
├── DisplayLeakService.java -- 显示通知栏的内存泄漏,实现了AbstractAnalysisResultService.java
├── LeakCanary.java --对外类,提供install(this)方法
├── ServiceHeapDumpListener.java 
└── internal --这个文件夹用于显示内存泄漏的情况(界面相关)
    ├── DisplayLeakActivity.java --内存泄漏展示的Activity
    ├── DisplayLeakAdapter.java 
    ├── DisplayLeakConnectorView.java 
    ├── FutureResult.java
    ├── HeapAnalyzerService.java 在另一个进程启动的Service,用于接收数据并发送数据到界面
    ├── LeakCanaryInternals.java
    ├── LeakCanaryUi.java
    └── MoreDetailsView.java

对外方法LeakCanary.install(this)

实际上LeakCanary对外提供的方法只有

LeakCanary.install(this);

从这里开始切入,对应源码

/**
 * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
 * references (on ICS+).
 */
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) {
    // 判断是否在Analyzer进程
    if (isInAnalyzerProcess(application)) {
        return RefWatcher.DISABLED;
    }
    // 允许显示内存泄漏情况Activity
    enableDisplayLeakActivity(application);

    HeapDump.Listener heapDumpListener =
            new ServiceHeapDumpListener(application, listenerServiceClass);

    RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
    ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
    return refWatcher;
}

为什么LeakCanary要求在4.0以上

<mark>通过注释能看出这个LeakCanary是用于4.0以上的方法

references (on ICS+).

为什么要使用在4.0以上呢?

ActivityRefWatcher.installOnIcsPlus(application, refWatcher);

这句方法告诉我们这个是用在Ics+(即4.0版本以上),那这个类ActivityRefWatcher具体使用来干什么的呢

@TargetApi(ICE_CREAM_SANDWICH) public final class ActivityRefWatcher {

  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();
  }

  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);
        }
      };

  private final Application application;
  private final RefWatcher refWatcher;

  /**
   * Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
   * after they have been destroyed.
   */
  public ActivityRefWatcher(Application application, final RefWatcher refWatcher) {
    this.application = checkNotNull(application, "application");
    this.refWatcher = checkNotNull(refWatcher, "refWatcher");
  }

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

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

  public void stopWatchingActivities() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
  }
}

application.registerActivityLifecycleCallbacks(lifecycleCallbacks);这个方法使用在Android4.0上的,用于观察Activity的生命周期。从上面代码看出,LeakCanary监听Activity的销毁操作

  ActivityRefWatcher.this.onActivityDestroyed(activity);

LeakCanary如何出现LeakCanry的图标

public static void setEnabled(Context context, final Class<?> componentClass,
                                  final boolean enabled) {
        final Context appContext = context.getApplicationContext();
        // 耗时操作
        executeOnFileIoThread(new Runnable() {
            @Override
            public void run() {
                ComponentName component = new ComponentName(appContext, componentClass);
                PackageManager packageManager = appContext.getPackageManager();
                int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
                // Blocks on IPC.
                packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
            }
        });
    }

在install的方法执行的时候调用了

 // 允许显示内存泄漏情况Activity
enableDisplayLeakActivity(application);

这个方法执行了上面显示的方法setEnable.最核心的方法是packageManager.setComponentEnabledSetting。
这个方法可以用来隐藏/显示应用图标
具体可以参照android 禁用或开启四大组件setComponentEnabledSetting

[重点]LeakCanary如何捕获内存泄漏

通过Debug.dumpHprofData()方法生成.hprof文件,然后利用开源库HAHA(开源地址:github.com/square/haha)解析.hprof文件,并发送给DisplayLeakActivity进行展示

public final class AndroidHeapDumper implements HeapDumper {

  private static final String TAG = "AndroidHeapDumper";

  private final Context context;
  private final Handler mainHandler;

  public AndroidHeapDumper(Context context) {
    this.context = context.getApplicationContext();
    mainHandler = new Handler(Looper.getMainLooper());
  }

  @Override public File dumpHeap() {
    if (!isExternalStorageWritable()) {
      Log.d(TAG, "Could not dump heap, external storage not mounted.");
    }
    File heapDumpFile = getHeapDumpFile();
    if (heapDumpFile.exists()) {
      Log.d(TAG, "Could not dump heap, previous analysis still is in progress.");
      // Heap analysis in progress, let's not put too much pressure on the device.
      return NO_DUMP;
    }

    FutureResult<Toast> waitingForToast = new FutureResult<>();
    showToast(waitingForToast);

    if (!waitingForToast.wait(5, SECONDS)) {
      Log.d(TAG, "Did not dump heap, too much time waiting for Toast.");
      return NO_DUMP;
    }

    Toast toast = waitingForToast.get();
    try {
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      cancelToast(toast);
      return heapDumpFile;
    } catch (IOException e) {
      cleanup();
      Log.e(TAG, "Could not perform heap dump", e);
      // Abort heap dump
      return NO_DUMP;
    }
  }

  /**
   * Call this on app startup to clean up all heap dump files that had not been handled yet when
   * the app process was killed.
   */
  public void cleanup() {
    LeakCanaryInternals.executeOnFileIoThread(new Runnable() {
      @Override public void run() {
        if (isExternalStorageWritable()) {
          Log.d(TAG, "Could not attempt cleanup, external storage not mounted.");
        }
        File heapDumpFile = getHeapDumpFile();
        if (heapDumpFile.exists()) {
          Log.d(TAG, "Previous analysis did not complete correctly, cleaning: " + heapDumpFile);
          heapDumpFile.delete();
        }
      }
    });
  }

  private File getHeapDumpFile() {
    return new File(storageDirectory(), "suspected_leak_heapdump.hprof");
  }

  private void showToast(final FutureResult<Toast> waitingForToast) {
    mainHandler.post(new Runnable() {
      @Override public void run() {
        final Toast toast = new Toast(context);
        toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
        toast.setDuration(Toast.LENGTH_LONG);
        LayoutInflater inflater = LayoutInflater.from(context);
        toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
        toast.show();
        // Waiting for Idle to make sure Toast gets rendered.
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle() {
            waitingForToast.set(toast);
            return false;
          }
        });
      }
    });
  }

  private void cancelToast(final Toast toast) {
    mainHandler.post(new Runnable() {
      @Override public void run() {
        toast.cancel();
      }
    });
  }
}

检测时机

在Activity销毁的时候会执行RefWatch.watch方法,然后就去执行内存检测

这里又看到一个比较少的用法,IdleHandler,IdleHandler的原理就是在messageQueue因为空闲等待消息时给使用者一个hook。那AndroidWatchExecutor会在主线程空闲的时候,派发一个后台任务,这个后台任务会在DELAY_MILLIS时间之后执行。LeakCanary设置的是5秒。

public void watch(Object watchedReference, String referenceName) {
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    if (debuggerControl.isDebuggerAttached()) {
      return;
    }
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    watchExecutor.execute(new Runnable() {
      @Override public void run() {
        ensureGone(reference, watchStartNanoTime);
      }
    });
  }
public final class AndroidWatchExecutor implements Executor {

  static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
  private static final int DELAY_MILLIS = 5000;

  private final Handler mainHandler;
  private final Handler backgroundHandler;

  public AndroidWatchExecutor() {
    mainHandler = new Handler(Looper.getMainLooper());
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
    handlerThread.start();
    backgroundHandler = new Handler(handlerThread.getLooper());
  }

  @Override public void execute(final Runnable command) {
    if (isOnMainThread()) {
      executeDelayedAfterIdleUnsafe(command);
    } else {
      mainHandler.post(new Runnable() {
        @Override public void run() {
          executeDelayedAfterIdleUnsafe(command);
        }
      });
    }
  }

  private boolean isOnMainThread() {
    return Looper.getMainLooper().getThread() == Thread.currentThread();
  }

  private void executeDelayedAfterIdleUnsafe(final Runnable runnable) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        backgroundHandler.postDelayed(runnable, DELAY_MILLIS);
        return false;
      }
    });
  }
}

Fragment如何使用LeakCanary

如果我们想检测Fragment的内存的话,可以在Application中将返回的RefWatcher存下来,可以在Fragment的onDestroy中watch它。

public abstract class BaseFragment extends Fragment {

  @Override public void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
    refWatcher.watch(this);
  }
}

参考LeakCanary开源项目

其他参考资料

LeakCanary 内存泄露监测原理研究

Android 内存泄漏检查工具LeakCanary源碼浅析