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'
Copy the code

As you can see, debugCompile and releaseCompile introduce different packages. On the Debug version, integrate the LeakCanary library and perform memory leak detection, while on the release version, integrate a non-operational wrapper. This has no impact on program performance.

2) Add to Application class:

public class LCApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return; } LeakCanary.install(this); // Normal app init code... }}Copy the code

LeakCanary.install() returns a predefined RefWatcher and also enables an ActivityRefWatcher, which automatically monitors activities that leak after calling activity.ondestroy ().

If the activity is simply detected for memory leaks, the above two steps will do the trick. What happens when an activity has a memory leak? LeakCanary will automatically display a notification bar and you will see the stack of references that caused the memory leak.

Specific usage code

1) Application related codes:

public class LCApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return; } LeakCanary.install(this); // Normal app init code... }}Copy the code

2) Leaking activity class code:

public class MainActivity extends Activity {

    private Button next;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        next = (Button) findViewById(R.id.next);
        next.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); finish(); }}); new Thread(newRunnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("= = = = = = = = = = = = = = = = ="); } } }).start(); }}Copy the code

When you click Next to jump to the second screen, LeakCanary will automatically display a notification bar, and you will see the reference stack that caused the overflow, as shown in the image above, so you can easily locate the thread that referenced the current activity and failed to release it.

1) refWatcher object is obtained in Application.

public class LCApplication extends Application {

    public static RefWatcher refWatcher;

    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return; } refWatcher = LeakCanary.install(this); // Normal app init code... }}Copy the code

2) Use RefWatcher to monitor fragments:

public abstract class BaseFragment extends Fragment {
  @Override public void onDestroy() { super.onDestroy(); RefWatcher refWatcher = LCApplication.refWatcher; refWatcher.watch(this); }}Copy the code

This listens for the fragment as if it were an activity. In fact, this method can be applied to any object, such as images, custom classes, etc., very convenient.

public static RefWatcher install(Application application) {
        return((AndroidRefWatcherBuilder)refWatcher(application).listenerServiceClass(DisplayLeakService.class).excludedRefs(AndroidEx cludedRefs.createAppDefaults().build())).buildAndInstall(); }Copy the code

ListenerServiceClass (displayLeakService.class) : Analyzes the memory leak result and sends a notification to the user. ExcludedRefs (AndroidExcludedRefs createAppDefaults (). The build ()) : set the object you want to ignore, such as some system vulnerability does not need statistics. BuildAndInstall () : The way to actually detect memory leaks, which we’ll explore below.

public RefWatcher buildAndInstall() {
        RefWatcher refWatcher = this.build();
        if(refWatcher ! = RefWatcher.DISABLED) { LeakCanary.enableDisplayLeakActivity(this.context); ActivityRefWatcher.installOnIcsPlus((Application)this.context, refWatcher); }return refWatcher;
    }
Copy the code

The RefWatcher method does three things: 1. Instantiate the RefWatcher object, which is used to detect if any of the RefWatcher objects have not been reclaimed, causing memory leaks; 2. Make the APP icon visible; 3. Check memory

public static void enableDisplayLeakActivity(Context context) {
        LeakCanaryInternals.setEnabled(context, DisplayLeakActivity.class, true);
    }
Copy the code
public static void setEnabled(Context context, final Class<? > componentClass, final boolean enabled) { final Context appContext = context.getApplicationContext(); executeOnFileIoThread(newRunnable() {
            public void run() { LeakCanaryInternals.setEnabledBlocking(appContext, componentClass, enabled); }}); }Copy the code
public static void setEnabledBlocking(Context appContext, Class<? > componentClass, boolean enabled) { ComponentName component = new ComponentName(appContext, componentClass); PackageManager packageManager = appContext.getPackageManager(); int newState = enabled? 1:2; packageManager.setComponentEnabledSetting(component, newState, 1); }Copy the code

Visible, the last call packageManager. SetComponentEnabledSetting () method, realize the application icon to hide and display.

public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
        if(VERSION.SDK_INT >= 14) { ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); activityRefWatcher.watchActivities(); }}Copy the code

This method instantiates the ActivityRefWatcher object, which listens for the activity’s lifecycle, as shown below:

public void watchActivities() {
        this.stopWatchingActivities();
        this.application.registerActivityLifecycleCallbacks(this.lifecycleCallbacks);
    }
Copy the code
private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() { public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } public void onActivityStarted(Activity activity) { } public void onActivityResumed(Activity activity) { } public void onActivityPaused(Activity activity) { } public void onActivityStopped(Activity activity) { } public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } public void onActivityDestroyed(Activity activity) { ActivityRefWatcher.this.onActivityDestroyed(activity); }};Copy the code

public void watch(Object watchedReference) {
        this.watch(watchedReference, "");
    }

    public void watch(Object watchedReference, String referenceName) {
        if(this ! = DISABLED) { Preconditions.checkNotNull(watchedReference,"watchedReference");
            Preconditions.checkNotNull(referenceName, "referenceName"); long watchStartNanoTime = System.nanoTime(); String key = UUID.randomUUID().toString(); this.retainedKeys.add(key); KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue); this.ensureGoneAsync(watchStartNanoTime, reference); }}Copy the code

1. Generate a random key and store it in the retainedKeys collection, which is used to determine whether the object is reclaimed; 2. Put the current Activity into KeyedWeakReference (a subclass of WeakReference); 3. By searching the ReferenceQueue, check whether the Acitivity exists. If the Acitivity exists, it can be recovered normally; if the Acitivity does not, it may have memory leakage. The first two things are very simple, here mainly look at the third thing processing, and ensureGoneAsync method source:

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        this.watchExecutor.execute(new Retryable() {
            public Result run() {
                returnRefWatcher.this.ensureGone(reference, watchStartNanoTime); }}); } Result ensureGone(KeyedWeakReference reference, long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); this.removeWeaklyReachableReferences();if(this.debuggerControl.isDebuggerAttached()) {
            return Result.RETRY;
        } else if(this.gone(reference)) {
            return Result.DONE;
        } else {
            this.gcTrigger.runGc();
            this.removeWeaklyReachableReferences();
            if(! this.gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); File heapDumpFile = this.heapDumper.dumpHeap();if(heapDumpFile == HeapDumper.RETRY_LATER) {
                    return Result.RETRY;
                }

                long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
                this.heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, this.excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
            }

            returnResult.DONE; }}Copy the code

This method first performs removeWeaklyReachableReferences (), from ReferenceQueue query whether there is the weak reference object in the queue, if it is not empty, the system has been recycled, The corresponding random number key is deleted from the retainedKeys collection.

 private void removeWeaklyReachableReferences() {
        KeyedWeakReference ref;
        while((ref = (KeyedWeakReference)this.queue.poll()) != null) {
            this.retainedKeys.remove(ref.key);
        }
    }
Copy the code

Then determine whether the object is reclaimed by determining whether the corresponding key exists in the retainedKeys collection.

private boolean gone(KeyedWeakReference reference) {
        return! this.retainedKeys.contains(reference.key); }Copy the code

If not, call gctrigger.rungc () manually; Again after call removeWeaklyReachableReferences method to judge whether the object is being recycled.

GcTrigger DEFAULT = new GcTrigger() {
        public void runGc() {
            Runtime.getRuntime().gc();
            this.enqueueReferences();
            System.runFinalization();
        }

        private void enqueueReferences() { try { Thread.sleep(100L); } catch (InterruptedException var2) { throw new AssertionError(); }}};Copy the code

The third line of code triggers GC manually, followed by the thread sleeping 100 milliseconds to give the System time to reclaim, and then manually calling the Finalize method that has lost reference objects through system.runfinalization (). Heapdumper.dumpheap () generates the.hprof file directory, and calls the Analyze () method using heapdumpListener. Those who are interested can go and see for themselves.


Wechat search [programmer xiao an] “interview series (java&andriod)” article will be published in the public account.