Memory leaks are a common problem in Android development, and anyone with some experience with Android programming has probably encountered them, but why do they leak? What about memory leaks?



In Android application development, a memory leak occurs when an object is no longer needed and should be recycled, but another object in use holds a reference to it and therefore cannot be recycled. As a result, the object that should be recycled cannot be recycled and remains in the heap.


What are the effects of memory leaks? It is one of the main reasons for the OOM app. Due to the limited memory allocated by the Android system for each application, when a large number of memory leaks occur in an application, it is inevitable that the memory required by the application will exceed the memory quota allocated by the system, resulting in memory overflow and application Crash.


Now that we understand the causes and effects of memory leaks, what we need to do is learn about common memory leaks and try to avoid them in future Android application development. Here are 5 common memory leak problems in Android development and their solutions.


Memory leaks caused by singletons

Android’s singleton pattern is very popular with developers, but can cause memory leaks if used incorrectly. Because of the static nature of singletons, their lifetime is as long as the lifetime of the application. This means that if an object is no longer needed and still holds a reference to that object, it cannot be recycled properly, resulting in a memory leak.


Here’s a typical example:


 public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}
Copy the code


This is a normal singleton pattern. When creating this singleton, since a Context is passed in, the lifetime of the Context is critical:


The Application Context is passed in: this will not be a problem because the singleton’s lifetime is as long as the Application’s;


2, Pass in the Activity Context: When an Activity exits the Context, since the Context has the same lifetime as the Activity (the Activity indirectly inherits from the Context), its memory is not reclaimed when the Activity exits. Because the singleton holds a reference to the Activity.


So the correct singleton should be modified as follows:


 public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context.getApplicationContext();
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

Copy the code


This way whatever Context is passed in will end up using the Application Context, and the singleton lifetime is as long as the Application’s, preventing memory leaks.


Memory leaks caused by static instances created by non-static inner classes

In order to avoid creating the same data resource over and over again, we might use this notation when we start frequent activities:



 public class MainActivity extends AppCompatActivity {
    private static TestResource mResource = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }
        //...
    }
    class TestResource {
        //...
    }
}
Copy the code


This creates a singleton of a non-static inner class inside the Activity, whose data is used each time the Activity is started. This avoids duplication of resources, but it can cause memory leaks because non-static inner classes hold references to external classes by default. A static instance is created using this non-static inner class. The lifetime of this instance is as long as that of the application. As a result, the static instance will always hold references to the Activity, causing the Activity’s memory resources to be recycled. The correct way to do this is:


Make this inner class static or extract it into a singleton. If Context is required, use ApplicationContext.


Memory leaks caused by handlers

Memory leakage caused by the use of Handler should be said to be the most common, usually when handling network tasks or encapsulating some request callback API should be handled with the help of Handler, for the use of Handler code is not standard may cause memory leakage, as shown in the following example:

public class MainActivity extends AppCompatActivity { private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { //... }}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); } private void loadData(){ //... request Message message = Message.obtain(); mHandler.sendMessage(message); }}Copy the code


Since mHandler is an instance of Handler’s non-statically anonymous inner class, it holds a reference to the external Activity class. We know that message queues are polling for messages in a Looper thread. Message in the Message queue holds a reference to the mHandler instance, which in turn holds a reference to the Activity. As a result, the Activity’s memory resources cannot be recycled in time. Causes a memory leak, so the alternative is:


public class MainActivity extends AppCompatActivity { private MyHandler mHandler = new MyHandler(this); private TextView mTextView ; private static class MyHandler extends Handler { private WeakReference reference; public MyHandler(Context context) { reference = new WeakReference<>(context); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) reference.get(); if(activity ! = null){ activity.mTextView.setText(""); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView)findViewById(R.id.textview); loadData(); } private void loadData() { //... request Message message = Message.obtain(); mHandler.sendMessage(message); }}Copy the code


Create a static Handler inner class, and then use a weak reference to the object that the Handler holds, so that it can reclaim the object that the Handler holds. This avoids Activity leakage, but the Looper thread may still have messages waiting to be processed in its message queue. So we should remove messages from the message queue when we Destroy or Stop the Activity. It is more accurate to do this:


public class MainActivity extends AppCompatActivity { private MyHandler mHandler = new MyHandler(this); private TextView mTextView ; private static class MyHandler extends Handler { private WeakReference reference; public MyHandler(Context context) { reference = new WeakReference<>(context); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) reference.get(); if(activity ! = null){ activity.mTextView.setText(""); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView)findViewById(R.id.textview); loadData(); } private void loadData() { //... request Message message = Message.obtain(); mHandler.sendMessage(message); } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }}Copy the code

Using mHandler. RemoveCallbacksAndMessages (null); Remove all messages and all Runnable from message queue. You can also use mHandler.removecallbacks (); Or mHandler. RemoveMessages (); To remove the specified Runnable and Message.


Memory leaks caused by threads

Memory leaks caused by threads are also common. Here are two examples that everyone has probably written about:


//——————test1 new AsyncTask() {@override protected Void doInBackground(Void... params) { SystemClock.sleep(10000); return null; } }.execute(); //——————test2 new Thread(new Runnable() {@override public void run() {systemclock. sleep(10000); } }).start();Copy the code


Both the asynchronous task and Runnable above are anonymous inner classes, so they both have an implicit reference to the current Activity. If the Activity’s task is not completed before it is destroyed, the Activity’s memory resources cannot be reclaimed, causing a memory leak. The correct way to use a static inner class is as follows:


static class MyAsyncTask extends AsyncTask { private WeakReference weakReference; public MyAsyncTask(Context context) { weakReference = new WeakReference<>(context); } @Override protected Void doInBackground(Void... params) { SystemClock.sleep(10000); return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); MainActivity activity = (MainActivity) weakReference.get(); if (activity ! = null) { //... } } } static class MyRunnable implements Runnable{ @Override public void run() { SystemClock.sleep(10000); }} / / -- -- -- the new Thread (new MyRunnable ()). The start (); new MyAsyncTask(this).execute();Copy the code


This prevents the Activity from leaking memory resources. Of course, you should also cancel AsyncTask:: Cancel () while the Activity is being destroyed to avoid wasting resources while the task is executing in the background.


5. Memory leakage caused by unclosed resources

Resources that use BraodcastReceiver, ContentObserver, File, Cursor, Stream, Bitmap, etc., should be shut down or logged out when the Activity is destroyed; otherwise, these resources will not be reclaimed, causing memory leaks.