A memory leak is a situation in which a useless object (an object that does not need to be used) is still referred to by other objects, so that the object cannot be reclaimed by the system, so that the memory unit occupied by the object in the heap cannot be freed, resulting in a waste of memory space.

In Android development, some bad programming habits can lead to memory leaks in our apps. Here are some common memory leak scenarios and optimizations for Android development.

Singleton causes memory leak

The singleton pattern is often used in Android development, 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. If an object is no longer useful, but the singletons still hold a reference to it, it cannot be properly reclaimed for the entire life of the application, resulting in a memory leak. Here’s an example:

public class AppSettings {

    private static AppSettings sInstance;
    private Context context;

    private AppSettings(Context context) {
        this.context = context;
    }

    public static AppSettings getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new AppSettings(context);
        }
        returnsInstance; }}Copy the code

Singletons like the one in the above code can cause a memory leak if we pass in Context parameters such as Activity, Service, etc. when we call getInstance(Context Context). For example, when we start an Activity and call getInstance(Context Context) to get the singleton of AppSetting, passing in activity. this as the Context, So the singleton sInstance of the AppSettings class holds a reference to the Activity, and when we exit the Activity, the Activity becomes useless, However, because sIntance, as a static singleton (which exists throughout the life of the application), continues to hold a reference to the Activity, the Activity object cannot be recycled and freed, which causes a memory leak. To avoid memory leaks caused by such singletons, we can change the context parameter to the global context:

    private AppSettings(Context context) {
        this.context = context.getApplicationContext();
    }
Copy the code

The global Application Context is the Context of the Application and has the same lifetime as the singleton, thus avoiding memory leaks. The singleton pattern corresponds to the life cycle of the Application, so we try to avoid using the Activity context when constructing the singleton and use the Application context instead.

Static variables cause memory leaks

Static variables are stored in the method area, and their life cycle begins with class loading and ends with the entire process. Once a static variable is initialized, the references it holds are not released until the process ends. For example, sInfo is used as a static variable in an Activity to avoid repeated creation of info:

public class MainActivity extends AppCompatActivity {

    private static Info sInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(sInfo ! =null) {
            sInfo = new Info(this); }}}class Info {
    private Info(Activity activity) {}}Copy the code

Info is a static member of the Activity and holds a reference to the Activity, whereas sInfo is a static variable and must have a long life. So when the Activity exits, sInfo still references the Activity, and the Activity cannot be reclaimed, causing a memory leak. In the Android development, static hold most of the time may be inconsistent because of its using life cycle and cause a memory leak, so we held at the new static variables when need to consider more references to the relationship between individual members, and using static variables hold less as far as possible, to avoid memory leaks. Of course, we can also avoid memory leaks by resetting the static quantity to NULL when applicable so that it no longer holds references.

Non-static inner classes cause memory leaks

Handler

Non-static inner classes (including anonymous inner classes) hold references to external classes by default, which can lead to memory leaks when non-static inner class objects have a longer lifetime than external class objects. A typical scenario for Android development is the use of a Handler. Many developers write this when using a Handler:

public class MainActivity extends AppCompatActivity {

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

        start();
    }

    private void start(a) {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler(Looper.myLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            if (msg.what == 1) {
                //something}}}; }Copy the code

One might argue that mHandler does not hold a reference to the Activity as a static variable, so its lifetime may not be longer than that of the Activity, and should not necessarily result in a memory leak. Obviously not! If you’re familiar with Handler messaging, you know that mHandler is stored as a member variable in the sent message MSG, which holds a reference to mHandler, which is a non-static internal class instance of the Activity. MHandler holds a reference to the Activity, so MSG indirectly holds a reference to the Activity. MSG is sent to MessageQueue, and then waits for the polling process of Looper (MessageQueue and Looper are associated with the thread, MessageQueue is the member variable referenced by Looper, Looper is stored in ThreadLocal). When the Activity exits, MSG may still be unprocessed or being processed in the MessageQueue MessageQueue, which will cause the Activity to be unable to be reclaimed, resulting in a memory leak for the Activity. When using inner classes in Android development, but to avoid memory leaks, we usually use static inner classes + weak references:

public class MainActivity extends AppCompatActivity {

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
        start();
    }

    private void start(a) {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private static class MyHandler extends Handler {

        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            MainActivity activity = activityWeakReference.get();
            if(activity ! =null) {
                if (msg.what == 1) {
                    //something
                }
            }

        }
    }
}
Copy the code

MHandler holds the Activity by weak reference, and when the GC performs garbage collection, the Activity is reclaimed and all occupied memory units are freed. This will prevent memory leaks. The above approach does avoid memory leaks caused by the Activity. The MSG sent no longer has a reference to the Activity, but it may still be in the MessageQueue MessageQueue. All the better to remove mHandler’s callbacks and sent messages when the Activity is destroyed.

    @Override
    protected void onDestroy(a) {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
Copy the code

Thread, AsyncTask

Another case where non-static inner classes cause memory leaks is when Thread or AsyncTask is used. For example, in an Activity, we directly new a child Thread:

public class ThreadActivity extends AppCompatActivity {

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

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                //Something
                try {
                    Thread.sleep(2000);
                } catch(InterruptedException e) { e.printStackTrace(); } } }).start(); }}Copy the code

Or create an AsyncTask:

public class AsyncTaskActivity extends AppCompatActivity {

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

        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                //something
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null; } }.execute(); }}Copy the code

Threads and asyncTasks created in this way are anonymous internal class objects that implicitly hold references to an external Activity, resulting in memory leaks. To avoid memory leaks, use the same static inner class + weak reference approach as the Handler above.

Memory leak due to unregistered or callback

For example, if we register a broadcast in an Activity, if we do not unregister the broadcast after the Activity is destroyed, the broadcast will persist in the system, holding the Activity reference just like the non-static inner class mentioned above, causing a memory leak. Therefore, it is important to unregister the broadcast after the Activity is destroyed.

public class BroadcastActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_broadcast);
        registerReceiver(mReceiver, new IntentFilter());
    }

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Receive the logic needed to do the broadcast}};@Override
    protected void onDestroy(a) {
        super.onDestroy(); unregisterReceiver(mReceiver); }}Copy the code

Memory leaks can also occur if not cancelled while registering observer mode. For example, observer callbacks that register network requests using Retrofit + RxJava also hold external references as anonymous inner classes, so remember to unregister when you don’t use them or destroy them.

Timer and TimerTask cause memory leaks

Timer and TimerTask are commonly used in Android for timing or loop tasks, such as ViewPager, which implements infinite rotation:

public class TimerActivity extends AppCompatActivity {

    private ViewPager mViewPager;
    private PagerAdapter mAdapter;
    private Timer mTimer;
    private TimerTask mTimerTask;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_timer);
        init();
        mTimer.schedule(mTimerTask, 3000.3000);
    }

    private void init(a) {
        mViewPager = findViewById(R.id.view_pager);
        mAdapter = new ViewPagerAdapter();
        mViewPager.setAdapter(mAdapter);

        mTimer = new Timer();
        mTimerTask = new TimerTask() {
            @Override
            public void run(a) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run(a) { loopViewpager(); }}); }}; }private void loopViewpager(a) {
        if (mAdapter.getCount() > 0) {
            intcurPos = mViewPager.getCurrentItem(); curPos = (++curPos) % mAdapter.getCount(); mViewPager.setCurrentItem(curPos); }}private void stopLoopViewPager(a) {
        if(mTimer ! =null) {
            mTimer.cancel();
            mTimer.purge();
            mTimer = null;
        }
        if(mTimerTask ! =null) {
            mTimerTask.cancel();
            mTimerTask = null; }}@Override
    protected void onDestroy(a) {
        super.onDestroy(); stopLoopViewPager(); }}Copy the code

When our Activity is destroyed, it is possible that the Timer is still waiting to execute the TimerTask. The reference it holds to the Activity cannot be reclaimed, so we cancel the Timer and TimerTask immediately when our Activity is destroyed. To avoid memory leaks.

Objects in the collection are not cleaned up causing a memory leak

This is better understood, if an object into the ArrayList, HashMap, such as the collection, the collection will hold the object reference, when we no longer need this object, also did not remove it from the collection, such as long as the collection is still in use (and the object has been useless), the object will cause a memory leak. In addition, if the collection is statically referenced, the unused objects in the collection will cause memory leaks. Therefore, when using the collection, it is necessary to remove or clear the unused objects from the collection to avoid memory leaks.

The resource is not closed or freed, causing a memory leak

Close it when using IO, File streams, or RESOURCES such as Sqlite and Cursor. These resources are buffered for write operations. If they are not closed, these buffered objects will remain occupied, resulting in memory leaks. So we turn them off when they are no longer needed, so that the buffer can be freed in time to avoid memory leaks.

Property animation causes a memory leak

Animation is also a time-consuming task. For example, the ObjectAnimator is started in the Activity, but the cancel method is not called when it is destroyed. Although we can’t see the animation, the animation will continue to play. The control referenced the Activity, which causes the Activity to not be released properly. Therefore, cancel the property animation when the Activity is destroyed to avoid memory leaks.

    @Override
    protected void onDestroy(a) {
        super.onDestroy();
        mAnimator.cancel();
    }
Copy the code

WebView causes memory leak

As for WebView memory leaks, the WebView takes up memory for a long time after loading the web page and cannot be freed, so we call its destroy() method after the Activity is destroyed to free memory. WebView memory leak: The Callback below the WebView holds the Activity reference, causing the WebView memory to be unable to be freed, even if methods such as webViet.destroy () are called (after Android 5.1). The final solution is to remove the WebView from the parent container before destroying it, and then destroy the WebView. A detailed analysis of the process can be found in this article: WebView Memory Leak Resolution.

    @Override
    protected void onDestroy(a) {
        super.onDestroy();
        if(mWebView ! =null) {
            ((ViewGroup) mWebView.getParent()).removeView(mWebView); // Remove the WebView from the parent control.
            mWebView.stopLoading();
            mWebView.getSettings().setJavaScriptEnabled(false);
            mWebView.clearHistory();
            mWebView.removeAllViews();
            mWebView.destroy();
            mWebView = null; }}Copy the code

conclusion

Memory leaks in Android memory optimization is an important aspect, many times in the program memory leaks occur we may not be able to notice, so develop good habits in the process of coding. To sum up, you can avoid memory leaks in most cases by doing the following:

  • Try not to use Activity references when constructing singletons
  • Static references are used to note that the reference object is empty or less static references
  • Use static inner classes + weak references instead of non-static inner classes
  • Cancel the broadcast or observer registration in time
  • Time consuming tasks, attribute animations remember cancel when the Activity is destroyed
  • Resources such as file flows and cursors are closed in time
  • The removal and destruction of a WebView during Activity destruction