Android memory leak

Memory leak (Memory Leak) refers to the fact that the dynamically allocated heap memory in the program is not released or cannot be released for some reason, resulting in a waste of system memory, resulting in serious consequences such as slowing down the program and even system crash (OOM).

1. LeakCanary, a memory leak checking tool

LeakCanary It is a tool provided by Square for Android developers to automatically detect memory leaks. LeakCanary is essentially an open source tool for automatic detection of memory leaks in Android applications based on MAT. We can integrate the jar package provided by LeakCanary into our own project. Once a memory leak is detected, LeakCanary just dumps the memory information, and analyzes the memory leak information through another process and displays it, so as to find and locate the memory leak problem at any time, without having to draw out a special person in the development process every time. Memory leak detection greatly facilitates the development of Android applications.

LeakCanary shows the page for memory leaks:

LeakCanary's Android Studio integration

1. Add the dependency package of LeakCanary in build.gradle. So far, the latest version of leakcanary is 1.6.1:

dependencies {
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.6.1'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
}

In development, debug and release versions are generally integrated at the same time, among which:

  • com.squareup.leakcanary:leakcanary-android:1.6.1 is the debug version. The debug version is compiled in your app and the jar package is loaded. Once a memory leak occurs, the developer will be notified in the notification bar that a memory leak has occurred. ;

  • com.squareup.leakcanary:leakcanary-android-no-op:1.6.1 is the release version. If your app is compiled in the release version, the jar package is loaded. No-op means No Operation Performed, which means no Will do any operation and will not interfere with the use of official users;

2. Register LeakCanary in the onCreate method of our custom 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);
}
  • Explain why you need to judge LeakCanary.isInAnalyzerProcess(this) first

Before registering, determine whether LeakCanary is already running on the phone. For example, if you have multiple APPs integrated with LeakCanary at the same time, and other apps have already run LeakCanary, you do not need to re install.

  • Under normal circumstances, after the Application calls LeakCanary.install(this), it can monitor the memory leak of the app program normally;

LeakCanary monitors the memory leak of the specified object

If we want LeakCanary to monitor the memory leak of the specified object, we need to use the watch function of RefWatcher, which is used as follows:

  • Call the install method in Application's onCreate and get the RefWatcher object:
private static RefWatcher sRefWatcher;

@Override
public void onCreate() {
    super.onCreate();
    sRefWatcher = LeakCanary.install(this);
}

public static RefWatcher getRefWatcher() {
    return sRefWatcher;
}

Note: Because the sRefWatcher object needs to be obtained at this time, sRefWatcher = LeakCanary.install(this) must be executed without judging LeakCanary.isInAnalyzerProcess(this).

 

In order to facilitate the demonstration of using LeakCanary to obtain and solve the problem of memory leaks, let's first write a memory leak scenario. We know that the most common memory leak is the single-column mode using the Activity's Context scenario, so we also use the single-column mode to demonstrate:

public class Singleton {
    private static Singleton singleton;
    private Context context;
    private Singleton(Context context) {
        this.context = context;
    }

    public static Singleton newInstance(Context context) {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null){//double checked locking
                    singleton = new Singleton(context);
                }
            }
        }
        return singleton;
    }
}

Call the watch method of RefWatcher in the object that needs to be monitored to monitor. For example, if I want to monitor an Activity, we can add DemoApp.getRefWatcher().watch(this); in the onCreate method of the Acitivity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    DemoApp.getRefWatcher().watch(this);
    setContentView(R.layout.activity_second);
    Singleton singleton = Singleton.newInstance(this);
}

LeakCanary memory leak showcase page

At the same time, in the above steps, when we are running the app program, after a memory leak occurs, after a short period of time, we will notify the memory leak in the notification bar:

At the same time, a Leaks icon will be generated on the desktop, which is to display the memory leak list. The memory leak list page is as follows:

This is a list of memory leaks, we can click to view the content of the leak

You can also click the "+" sign on the right to view more detailed information. If the content is too long, no screenshots will be taken. There is a detailed description of the calling process inside;

Second, the common situation of Android memory leak

1. Static variables

The lifetime of static variables is as long as the lifetime of the application. If a static variable holds the context of an Activity, the corresponding Activity cannot be released, resulting in a memory leak. If you hold the context of the application, there is no problem.

Common ones are:

  • Singleton pattern: Internal implementations are static variables and methods
  • Static View: The view holds the Activity's context by default
  • Static Activity
public class MainActivity extends AppCompatActivity {


private static Context StaticVarible;

private Handler mHandler;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

btn = (Button)findViewById(R.id.button);

btn.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

MainActivity.this.finish();

}

});

StaticVarible = this;

LeakCanary.install(getApplication());

}


}

Solution:

Just empty the static variable in Activity Destory

​
@Override

protected void onDestroy() {

StaticVarible = null;

super.onDestroy();

}

​

2. Anonymous inner class or non-static inner class

Both non-static inner classes and anonymous inner classes will implicitly hold references to their outer classes (otherwise how to access non-static members of outer classes?), static inner classes will not hold references to outer classes.

java allows us to define static classes inside a class. Such as inner classes (nested class es). A class that encloses a nested class is called an outer class. In java, we cannot decorate top level class es with static. Only inner classes can be static.
What is the difference between a static inner class and a non-static inner class? Below are the main differences between the two.
(1) The inner static class does not need to have a reference to the outer class. But a non-static inner class needs to hold a reference to the outer class.
(2) Non-static inner classes can access static and non-static members of outer classes. A static class cannot access non-static members of the outer class. He can only access static members of the outer class.
(3) A non-static inner class cannot be created without the entity of the outer class. A non-static inner class can access the data and methods of the outer class because it is inside the outer class.

Common are:

Handler, AsyncTask, TimerTask, etc., generally when dealing with multi-threaded tasks.

public class Activity6 extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_6);
 
        findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {<br>                    //Simulate time-consuming operations
                    Thread.sleep( 15000 );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
 
    }
}

After running the above code and clicking the finish button, a memory leak occurred after a while.

Why does Activity6 have a memory leak?

Enter the Activity6 interface, and then click the finish button, Activity6 is destroyed, but the thread in Activity6 is still running, and the anonymous inner class Runnable object refers to the instance of Activity6, so the memory occupied by Activity6 cannot be recycled in time by GC.

Solution:

1. Use static inner classes 2. Process non-static inner classes in time before destruction

For example, in the above example, Runnable can be changed to a static non-anonymous inner class:

public class Activity6 extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_6);
 
        findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
 
        new Thread( new MyRunnable()).start();
 
    }
 
    private static class MyRunnable implements Runnable {
 
        @Override
        public void run() {
            try {
                Thread.sleep( 15000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
     
}

The above code has effectively solved the problem that Handler and Runnable refer to Activity instances and cause memory leaks, but this is not enough. Because the core reason for the memory leak is that when an object should be reclaimed by the system, it is referenced by other objects, causing the memory to not be reclaimed. So when we write code, we must always stretch this string. Going back to the above question, when the current Activity calls finish to destroy, should all threads in this Activity cancel the threads in the OnDestory() method? Of course, whether to cancel the asynchronous task depends on the specific requirements of the project. For example, when the Activity is destroyed, start a thread and asynchronously write the log log to the local disk. For this requirement, the thread needs to be started in the OnDestory() method. Therefore, making choices based on the current environment is the correct solution.

So we can also modify the code to: remove all callback s and Message s in onDestroy().

@Override
    protected void onDestroy() {
        super.onDestroy();
 
       //If the parameter is null, all Callbacks and Messages will be cleared.
        handler.removeCallbacksAndMessages( null );
    }

3. The resource is not closed or the listener is not removed (logout)

In development, if resources such as BroadcastReceiver, ContentObserver, File, Cursor, Stream, Bitmap, custom attribute attributeattr, and sensor are used, they should be closed or logged out in time when the Activity is destroyed, otherwise these resources will not be recycled, resulting in memory loss. leakage. for example:

// To use resources such as sensors, you need to log out
SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
sensorManager.unregisterListener(listener);
// Using BroadcastReceiver requires logout
Myreceiver recevier = new Myreceiver();
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
registerReceiver(recevier,intentFilter);
unRegisterReceiver(recevier);
// Custom properties, need to recycle
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AttrDeclareView);
int color = a.getColor(R.styleable.AttrDeclareView_background_color, 0);
a.recycle();

In addition to the above-mentioned common memory leaks, there are also memory leaks including infinite loop animation, using ListView, using collection containers, and using WebView. Among them, the reason for the leak caused by infinite loop animation is that the animation is not stopped in the onDestory of Activity; use The reason for the ListView leak is that the cached convertView is not used when the Adapter is constructed; the reason for the leak caused by the use of the collection container is that the object references stored in the collection are not cleaned up when the related objects are not used. During optimization, all elements (references) in the collection are cleaned up before exiting the program, and then set to null; the reason for the leak caused by using WebView is that the destroy method is not called to destroy it when the WebView is not in use, resulting in its long-term occupation memory and cannot be reclaimed. During optimization, another process can be opened for WebView, which communicates with the main thread through AIDL, so that the process where WebView is located can choose an appropriate time to destroy according to business needs.

 

 

 

Posted by erokar on Fri, 06 May 2022 23:47:28 +0300