Android common memory leaks

Memory leak: In Android program development, if an object is no longer needed to be used and should be recycled, but another object is still holding a reference to the object, which will cause it to fail to be recycled by GC. There will be a memory leak. One of the main reasons for OOM issues in Android programs when memory leaks. So when we write code, we must carefully deal with this type of problem

Memory leaks caused by the singleton design pattern

  • The static nature of the singleton design pattern will make its life cycle as long as the life cycle of the application, which means that if an object is no longer in use, and the singleton object is still holding a reference to the object, then GC will not be able to recycle the object, resulting in a memory leak

    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;
        }
    }
    
  • The above code is a most common singleton pattern, but there are two problems to pay attention to:

    • If the Context we pass in is the Application's Context, there is no problem, because the Application's Context life cycle is as long as the application's life cycle
    • If the Context we pass in is the Context of the Activity, then if we destroy the Activity because of demand, the Context will also be destroyed with the Activity, but the singleton still holds a reference to this class of objects, then the will cause a memory leak. The correct singleton pattern should be written like this:
    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;
        }
    }
    
  • In this case, no matter what Context we pass in, the Context of the Application is ultimately used, and the life cycle of the singleton is as long as the application, so that there will be no memory leaks.

Memory leak caused by static instance created by non-static inner class

  • Sometimes we will start an Activity frequently because of demand. At this time, in order to avoid creating the same data source frequently, we usually do the following:

    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 {
            //...
        }
    }
    
  • In this way, a non-static inner class is created in the Activity. The non-static inner class holds the reference of the Activity class by default, but its life cycle is still as long as the application, so when the Activity is destroyed, the object reference of the static inner class will not be Reclaimed by GC, it will cause memory overflow. The solution:

    • Change inner class to static inner class
    • Encapsulate this inner class into a singleton, and Context uses Application's Context

Memory leak caused by Handler

  • Let's take a look at the non-standard Handler writing first

    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);
        }
    }
    
  • The handler here is also a non-static anonymous inner class. Like the above, it also holds a reference to Activity. We know that the handler runs in a Looper thread, and the Looper thread polls to process messages in the message queue. Suppose we have ten messages to process, and when he executes the sixth message, the user clicks the back button and destroys the current Activity. At this time, the message has not been processed yet, and the handler is still holding a reference to the Activity. , at this time, it will not be reclaimed by GC, resulting in a memory leak. The correct way is:

    public class MainActivity extends AppCompatActivity {
    	//new a custom Handler
        private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView ;
     
    	//Custom static inner class inherits from Handler
        private static class MyHandler extends Handler {
            private WeakReference<Context> reference;
    		//Use weak references in the constructor to refer to the context object
            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();
    		//Remove all Runable s and messages from the queue
    		//Here you can also use mHandler.removeMessage and mHandler.removeCallBacks to remove the specified Message and Runable
         	mHandler.removeCallbacksAndMessages(null);
         }
    }
    
  • Create a static inner class that inherits from the handler, and then make a weak reference to the object held by the handler in the construction parameters, so that the object held by the handler will be recycled when it is recycled. Another modification is made here, that is, when we When the object held by the handler is recovered, that is, when the Activity is destroyed, if there are still unprocessed messages in the handler, we need to remove the messages in the message queue in the OnDestry method.

Memory leaks caused by threads

  • Memory leaks caused by improper use of threads are also very common. Here are two examples:

    //---test1
    new AsyncTask<Void, Void, Void>() {
        @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();
    
  • The above are two inner classes. When our Activity is destroyed, these two tasks are not completed, which will make the Activity's memory resources unable to be recycled, resulting in a memory leak. The correct way is to use a static inner class: as follows

    static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        private WeakReference<Context> 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);
        }
    }
    //---
    new Thread(new MyRunnable()).start();
    new MyAsyncTask(this).execute();
    
  • This avoids memory leaks. Of course, when the Activity is destroyed, remember to call the AsyncTask.cancal() method in OnDestry to cancel the corresponding task. Avoid wasting resources running in the background.

Memory leak caused by unclosed resources

When using resources such as BroadcastReceiver, ContentObserver, File, Cursor, Stream, Bitmap, etc., be sure to close, log off or release memory in OnDestry in Activity, otherwise these resources will not be recycled by GC, which will cause memory leaks

Tags: Android

Posted by cityguru on Wed, 07 Sep 2022 12:08:39 +0300