LeakCanary making address: square. Making. IO/LeakCanary /

Begin to use

The latest version is version 2.3, which is much cleaner than the version before 2.0. You only need to add the dependency of LeakCanary to dependencies. And debugImplementation only works in Debug mode, so you don’t have to worry about users having LeakCanary collections in a formal environment.

**

dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 2.3'}Copy the code

After adding LeakCanary to the project, you can start to detect the memory leak of the project. After running the project, you can start to click your own project. Here is a Demo project as an example to talk about the process of LeakCanary recording memory leak and how I solved the memory leak. After the project is up and running, you can see the printable message from LeakCanary on the console:

**

D/LeakCanary: Check for retained object found no objects remaining
D/LeakCanary: Scheduling check for retained objects in 5000ms because app became invisible
D/LeakCanary: Check for retained object found no objects remaining
D/LeakCanary: Rescheduling check for retained objects in 2000ms because found only 3 retained objects (< 5 while app visible)
Copy the code

This indicates that LeakCanary is continuously testing for any remaining objects in the project. So how does LeakCanary work? LeakCanary is based on a library called ObjectWatcher Android. It hooks into the Android lifecycle and automatically detects when activities and fragments are destroyed and should be garbage collected. These destroyed objects are passed to the ObjectWatcher, which holds weak references to these destroyed objects. If a weak reference is not removed after waiting five seconds and running the garbage collector, the observed object is considered retained and potentially leakable. LeakCanary outputs these logs in Logcat.

**

D LeakCanary: Watching instance of com.example.leakcanary.MainActivity 
// 5 seconds later...
D LeakCanary: Found 1 retained object
Copy the code

After a few blinds, this message appeared on my phone notification bar:

Dump heap = dump heap = dump heap = dump heap = dump heap = dump heap When the app is visible, it waits until there are five reserved objects before the heap dump is triggered. One thing to add here is that the default threshold is 5 when the application is visible and 1 when the application is not. If you see a notification of reserved objects and switch the application to the background (for example, by clicking the home button), the threshold will change from 5 to 1 and LeakCanary will immediately dump the heap. So what is a heap dump?

Heap dumps

After I clicked on the above notification, the console printed the following statement:

**

D/LeakCanary: Check for retained objects found 3 objects, dumping the heap D/LeakCanary: WRITE_EXTERNAL_STORAGE permission not granted, ignoring I/testapplicatio: hprof: heap dump "/data/user/0/com.example.leakcaneraytestapplication/files/leakcanary/2020-05-28_16-35-28_155.hprof" starting... I/ testApplicatio: hprof: Heap dump completed (22MB) in 2.764 s objects 374548 objects with stack functions 0Copy the code

This is where the heap dump begins, generating the.hprof file to which LeakCanary stores the Java Heap information. A prompt will also appear in the application.

LeakCanary is using shark to transform the.hprof file and locate the objects remaining in the Java heap. If retained objects are not found, they are most likely recycled during the heap dump.

For each retained object, LeakCanary finds the chain of references that prevents the retained object from being reclaimed: the leak path. The leak path is the alias of the shortest strongly referenced path from GC ROOTS to the retained object. Once the leak path is determined, LeakCanary uses its knowledge of the Android framework to find out who is leaking along the leak path.

Resolving memory Leaks

Open the resulting Leaks app and the interface will look something like this. LeakCanary calculates a leak path and displays it on the UI. This is where LeakCanary is friendly, as shown in the UI, you can see the memory leak process directly. Compared to mat and Android Studio’s built-in profiler tools, this is pretty straightforward.

The leak path is also shown in logcat:

**

HEAP ANALYSIS RESULT ==================================== 1 APPLICATION LEAKS References underlined with "~~~" are likely causes. Learn more at https://squ.re/leaks. 111729 bytes retained by leaking objects Signature: E030ebe81011d69c7a43074e799951b65ea73a ┬ ─ ─ ─ │ GC Root: Local trace In native Code │ ├─ Android. OS.HandlerThread Instance bridge: Atmospheric Leaking NO (PathClassLoader↓ is not leaking) │ Thread name: 'LeakCanary - Heap - Dump │ left HandlerThread. ContextClassLoader ├ ─ dalvik. System. PathClassLoader instance │ Leaking: NO (ToastUtil left is not leaking and this is A never leaking) │ left PathClassLoader. RuntimeInternalObjects ├ ─ Java.lang.object [] array │ Leaking: NO (ToastUtil left is not leaking) │ left Object [], [871] ├ ─ com. Example. Leakcaneraytestapplication. ToastUtil class │ leaking: NO (a class is never leaking) │ ↓ static ToastUtil. MToast │ ~~~~~~ ├─ Android. YES (This toast is done showing (Toast.mTN.mWM ! = null && Toast. MTN. MView = = null)) │ left Toast. MContext ╰ - > com. Example. Leakcaneraytestapplication. LeakActivity instance Leaking: YES (ObjectWatcher was watching this because com.example.leakcaneraytestapplication.LeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true) key = c1de58ad-30d8-444c-8a40-16a3813f3593 watchDurationMillis = 40541 retainedDurationMillis = 35535 ==================================== 0 LIBRARY LEAKSCopy the code

Each node in the path corresponds to a Java object. Students familiar with Java memory reclamation should know about the “reachable analysis algorithm”. LeakCanary uses the reachable analysis algorithm to search from GC ROOTS all the way down to the reference chain. If an object is not connected to any reference chain from GC ROOTS, it is proven that the object is “unreachable” and can be recycled.

We look down from the top:

**

GC Root: Local variable in native code
Copy the code

At the top of the leak path is GC Root. GC Root is a special object that is always reachable. Followed by:

**

Heavy Metal Bridge bridge bridge bridge bridge bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge Bridge bridge 'LeakCanary - Heap - Dump │ left HandlerThread. ContextClassLoaderCopy the code

Here’s the Leaking state (YES, NO, UNKNOWN). Then we have to keep looking down.

**

├ ─ dalvik. System. PathClassLoader instance │ Leaking: NO (ToastUtil left is not leaking and this is A never leaking) │ left PathClassLoader. RuntimeInternalObjectsCopy the code

The upper node tells us the Leaking is still NO, so look down there.

**

Bridge bridge: YES (Chinese bridge bridge) (Chinese bridge bridge) MView == null)) │ ↓ toast.mContextCopy the code

There is some Leaking on the atmospheric Leaking (YES).” This toast is done showing (Toast.mTN.mWM ! Android.widget.toast This is the Toast control of the system, which means that the Toast object is most likely created during the process of using the Toast. However, it could not be recycled when it was supposed to be recycled, resulting in a memory leak.

**

╰ - > com. Example. Leakcaneraytestapplication. LeakActivity instance Leaking: YES (ObjectWatcher was watching this because com.example.leakcaneraytestapplication.LeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)Copy the code

The memory leak occurred in the activity. We found the corresponding activity and found the code related to Toast:

ToastUtil: ToastUtil: ToastUtil: ToastUtil: ToastUtil: ToastUtil: ToastUtil: ToastUtil: ToastUtil Here we define a static Toast object type, then create the object when showToast, and then no more. The static Toast object only needs to be displayed for a few seconds. The static Toast object is created and not destroyed, so there is a memory leak. So we either don’t use the static modifier here, or we set Toast to null after we’re done using it.

**

public class ToastUtil {

    private static Toast mToast;

    public static void showToast(Context context, int resId) {
        String text = context.getString(resId);
        showToast(context, text);
    }

    public static void showToast(Context context, String text){
        showToast(context, text, Gravity.BOTTOM);
    }

    public static void showToastCenter(Context context, String text){
        showToast(context, text, Gravity.CENTER);
    }

    public static void showToast(Context context, String text, int gravity){
        cancelToast();
        if (context != null){
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View layout = inflater.inflate(R.layout.toast_layout, null);
            ((TextView) layout.findViewById(R.id.tv_toast_text)).setText(text);
            mToast = new Toast(context);
            mToast.setView(layout);
            mToast.setGravity(gravity, 0, 20);
            mToast.setDuration(Toast.LENGTH_LONG);
            mToast.show();
        }
    }

    public static void cancelToast() {
        if (mToast != null){
            mToast.cancel();
        }
    }
}
Copy the code

In fact, the essence of memory leaks is that long-period objects hold references to short-period objects. As a result, short-period objects cannot be reclaimed when they should be reclaimed, resulting in memory leaks. We just have to follow the LeakCaneray reference chain one by one to find where the memory leak occurred and cut the reference chain to release the memory.

Here’s another thing: there is NO Leaking of UNKNOWN in this example, normally there is not YES or NO, UNKNOWN means there might be a memory leak, you need to take some time to investigate these references and find out what is wrong. Leaking is generally inferred to be a reference between the last Leaking node (Leaking: NO) and the first Leaking node (Leaking: YES).

Reference:

Android memory leak detection for LeakCanary use