For Android Developer, many open source libraries are essential knowledge points for development, from the use of ways to implementation principles to source code parsing, which require us to have a certain degree of understanding and application ability. So I’m going to write a series of articles about source code analysis and practice of open source libraries, the initial target is EventBus, ARouter, LeakCanary, Retrofit, Glide, OkHttp, Coil and other seven well-known open source libraries, hope to help you 😇😇

Official account: byte array

Article Series Navigation:

  • Tripartite library source notes (1) -EventBus source detailed explanation
  • Tripartite library source notes (2) -EventBus itself to implement one
  • Three party library source notes (3) -ARouter source detailed explanation
  • Third party library source notes (4) -ARouter own implementation
  • Three database source notes (5) -LeakCanary source detailed explanation
  • Tripartite Library source note (6) -LeakCanary Read on
  • Tripartite library source notes (7) -Retrofit source detailed explanation
  • Tripartite library source notes (8) -Retrofit in combination with LiveData
  • Three party library source notes (9) -Glide source detailed explanation
  • Tripartite library source notes (10) -Glide you may not know the knowledge point
  • Three party library source notes (11) -OkHttp source details
  • Tripartite library source notes (12) -OkHttp/Retrofit development debugger
  • Third party library source notes (13) – may be the first network Coil source analysis article

The last article on LeakCanary for a more comprehensive source analysis, according to the process, this article should belong to the actual fight, but for some reason is not going to write the actual fight content (in fact, is their own order, write not too out), or to write an expansion on memory leak related reading 😂😂

A significant advantage of Java is the automatic memory Collection mechanism. Java automatically manages the memory Collection process through the Garbage Collection (GC) without the developer having to actively free the memory. This automation saves developers money, but it also leads some developers to believe that Java does not have a memory leak problem, or that memory leaks are something that should be cared about and addressed at the GC or JVM level. This is incorrect, because most of the time memory leaks are caused by flaws in the program itself, and the GC and JVM don’t understand exactly what the program was designed to do, so it’s up to the developer to take the initiative

Memory leak and memory overflow

The concepts Of Memory Leak and Out Of Memory are often talked about together. They are related to each other, but they are actually quite different:

  • Memory leak. A memory leak is a code error that causes the application to retain references to objects that are no longer needed. As a result, the memory allocated to the object cannot be reclaimed. As a result, the available memory of the application gradually decreases, and even causes OOM in serious cases. For example, when an Activity’s onDestroy() method is called, the Activity itself and its associated views, bitmaps, and other objects should normally be reclaimed. However, if a background thread continues to hold a reference to the Activity, the memory occupied by the Activity cannot be reclaimed, leading to an OOM or Crash
  • Memory overflow. When an application is applying for memory, the system does not have enough memory space for it

Both can cause problems, performance degradation, or a crash. The main differences are:

  • Memory leak is one of the causes of memory overflow. If the memory leak is serious, the memory will overflow
  • Memory leaks are caused by code defects and can be avoided by improving the code; You can adjust your configuration to reduce the frequency of memory overruns, but you can’t completely avoid them

For a program with memory leak, even if it only leaks a small amount of memory each time, the available memory of the program will gradually decrease, and after a long run, the program is also hidden the danger of crash

Two, memory management

To determine whether a program is leaking memory, we must first understand how Java manages memory. Memory management in Java is the process of allocating and freeing objects

In Java, we use the new keyword to allocate memory space and create objects (except for basic types), all of which are allocated in the Heap. In general, Java’s memory areas can be divided into three categories:

  1. Static storage: Space that exists for the entire duration of a program and is allocated at compile time, mainly for static data and constants
  2. Stack: When a method is executed, local variables inside the method body are created in stack memory, and memory is automatically freed when the method is finished
  3. Heap: Usually holds objects from new

Object release is done by GC. GC is responsible for monitoring the running status of each object, including its application, reference, being referenced, and assignment behavior. When an object is determined by the GC to be no longer referenced, the GC reclaims and frees the memory space corresponding to the object

There are four ways to reference an object:

  1. StrongReference: the JVM would rather throw an OOM than let the GC reclaim the object with a StrongReference
  2. SoftReference: if an object has only soft references, the object is reclaimed when memory space is running out
  3. WeakReference: if an object only has a WeakReference, the object will be reclaimed in GC regardless of whether the current memory space is sufficient
  4. PhantomReference: Can be collected at any time, when the garbage collector is ready to reclaim an object, if it has a virtual reference, it will be added to the reference queue associated with it before the object’s memory is reclaimed. A program can determine whether an object is to be reclaimed by determining whether a virtual reference to the object exists in the reference queue

The token that an object is no longer referenced is that it is no longer strongly referenced. The JVM uses reference counting or reachability analysis to determine whether an object is still strongly referenced

In Java, a memory leak means a situation occurs where an object is reachable, there are other objects strongly referencing it, but that object is useless and the program will no longer use those objects. An object that satisfies this condition means that the object has been leaked, that the object is not collected by GC (because the object is reachable and does not yet meet GC standards), and yet continues to occupy memory. For example, because a non-static inner class holds an implicit reference to an external class, the outer class cannot be reclaimed until the non-static inner class is reclaimed

Three, common memory leaks

The following is a list of nine common memory leaks in Android and the corresponding solutions

1, and Broadcast Receivers

If you register a BroadcastReceiver in an Activity and forget to unregister it, the BroadcastReceiver will always hold a reference to the Activity. Even if the Activity has already executed onDestroy

public class BroadcastReceiverLeakActivity extends AppCompatActivity {

    private BroadcastReceiver broadcastReceiver;

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

    private void registerBroadCastReceiver(a) {
        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                //your receiver code goes here!}}; registerReceiver(broadcastReceiver,new IntentFilter("SmsMessage.intent.MAIN"));
    }
    
    
    @Override
    protected void onStart(a) {
        super.onStart();
        registerBroadCastReceiver();
    }    


    @Override
    protected void onStop(a) {
        super.onStop();

        /* * Uncomment this line in order to avoid memory leak. * You need to unregister the broadcast receiver since the broadcast receiver keeps a reference of the activity. * Now when its time for your Activity to die, the Android framework will call onDestroy() on it * but the garbage collector will not be able to remove the instance from memory because the broadcastReceiver * is still holding a strong reference to it. * */

        if(broadcastReceiver ! =null) { unregisterReceiver(broadcastReceiver); }}}Copy the code

Developers must remember to call unregisterReceiver when actives.onStop () is used. Note that if a BroadcastReceiver is registered in onCreate(), the BroadcastReceiver will not be registered again when the application switches back to the background. So, it’s best to register in an Activity’s onStart() or onResume() methods, and then unregister at onStop()

2, Static Activity or View Reference

Look at the following example code that declares TextView as a static variable (for whatever reason). An Activity cannot be garbage collected after it is destroyed, whether the Activity or View is referenced directly or indirectly through a static variable

public class StaticReferenceLeakActivity extends AppCompatActivity {

    /* * This is a bad idea! * /
    private static TextView textView;
    private static Activity activity;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
        
        
        textView = findViewById(R.id.activity_text);
        textView.setText("Bad Idea!");
           
        activity = this; }}Copy the code

Never refer to an Activity, View, or Context through static variables

3. Singleton Class Reference

Look at the following example, which defines a Singleton class that needs to pass a Context in order to get some files from local storage

public class SingletonLeakExampleActivity extends AppCompatActivity {

    private SingletonSampleClass singletonSampleClass;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
    /* * Option 1: Do not pass activity context to the Singleton class. Instead pass application Context */      
        singletonSampleClass = SingletonSampleClass.getInstance(this);
    }
  
    
   @Override
   protected void onDestroy(a) {
        super.onDestroy();

    /* * Option 2: Unregister the singleton class here i.e. if you pass activity context to the Singleton class, * then ensure that when the activity is destroyed, the context in the singleton class is set to null. */singletonSampleClass.onDestroy(); }}Copy the code
public class SingletonSampleClass {
  
    private Context context;
    private static SingletonSampleClass instance;
  
    private SingletonSampleClass(Context context) {
        this.context = context;
    }

    public synchronized static SingletonSampleClass getInstance(Context context) {
        if (instance == null) instance = new SingletonSampleClass(context);
        return instance;
    }
  
    public void onDestroy(a) {
       if(context ! =null) {
          context = null; }}}Copy the code

If the context contained in SingletonSampleClass is not set to empty, a memory leak will occur. So how do you solve this problem?

  • Instead of passing the ActivityContext to the Singleton class, you can pass the ApplicationContext
  • If you really must use an ActivityContext, then when the Activity is destroyed, you need to make sure that the Context of the Singleton class is set to NULL

4. Inner Class Reference

Look at the following example, which defines a LeakyClass that you need to pass the Activity in order to redirect to a new Activity

public class InnerClassReferenceLeakActivity extends AppCompatActivity {

  /* * Mistake Number 1: * Never create a static variable of an inner class * Fix I: * private LeakyClass leakyClass; * /
  private static LeakyClass leakyClass;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
        
        new LeakyClass(this).redirectToSecondScreen();

        /* * Inner class is defined here * */
         leakyClass = new LeakyClass(this);
         leakyClass.redirectToSecondScreen();
    }
    
  /* * Mistake Number 2: * 1. Never create a inner variable of an inner class * 2. Never pass an instance of the activity to the inner class */       
    private class LeakyClass {
        
        private Activity activity;
        public LeakyClass(Activity activity) {
            this.activity = activity;
        }
        
        public void redirectToSecondScreen(a) {
            this.activity.startActivity(newIntent(activity, SecondActivity.class)); }}}Copy the code

How to solve this problem?

  • As mentioned earlier, do not create static variables for inner classes
  • LeakyClass is set to static and static inner classes do not hold implicit references to their outer classes
  • WeakReference is used for any View/Activity. If only weak references point to an object, then the garbage collector can reclaim the object
public class InnerClassReferenceLeakActivity extends AppCompatActivity {

  /* * Mistake Number 1: * Never create a static variable of an inner class * Fix I: */
  private LeakyClass leakyClass;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
        
        new LeakyClass(this).redirectToSecondScreen();

        /* * Inner class is defined here * */
         leakyClass = new LeakyClass(this);
         leakyClass.redirectToSecondScreen();
    }
  
  
    /* * How to fix the above class: * Fix memory leaks: * Option 1: The class should be set to static * Explanation: Instances of anonymous classes do not hold an implicit reference to their outer class * when they are "static". * * Option 2: Use a weakReference of the textview or any view/activity for that matter * Explanation: Weak References: Garbage collector can collect an object if only weak references * are pointing towards it. * */
    private static class LeakyClass {
        
        private final WeakReference<Activity> messageViewReference;
        public LeakyClass(Activity activity) {
            this.activity = new WeakReference<>(activity);
        }
        
        public void redirectToSecondScreen(a) {
            Activity activity = messageViewReference.get();
            if(activity ! =null) {
               activity.startActivity(newIntent(activity, SecondActivity.class)); }}}}Copy the code

5, Anonymous Class Reference

Anonymous memory classes cause the same memory leak problem as in the previous section. The solution is as follows:

public class AnonymousClassReferenceLeakActivity extends AppCompatActivity {

    private TextView textView;

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


        textView = findViewById(R.id.activity_text);
        textView.setText(getString(R.string.text_inner_class_1));
        findViewById(R.id.activity_dialog_btn).setVisibility(View.INVISIBLE);

        /* * Runnable class is defined here * */
         new Thread(new LeakyRunnable(textView)).start();
    }



    private static class LeakyRunnable implements Runnable {

        private final WeakReference<TextView> messageViewReference;
        private LeakyRunnable(TextView textView) {
            this.messageViewReference = new WeakReference<>(textView);
        }

        @Override
        public void run(a) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            TextView textView = messageViewReference.get();
            if(textView ! =null) {
                textView.setText("Runnable class has completed its work"); }}}}Copy the code

6, AsyncTask Reference

In the following example, AsyncTask is used to get a string value that is used to update textView in the onPostExecute() method

public class AsyncTaskReferenceLeakActivity extends AppCompatActivity {

    private TextView textView;
    private BackgroundTask backgroundTask;

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

        /* * Executing AsyncTask here! * * /
        backgroundTask = new BackgroundTask(textView);
        backgroundTask.execute();
    }

    /* * Couple of things we should NEVER do here: * Mistake number 1. NEVER reference a class inside the activity. If we definitely need to, Implicit reference we should set the class as static as static inner classes don't hold * any implicit reference to its parent activity class * Mistake number 2. We should always cancel the asyncTask when activity is destroyed. This is because the asyncTask will still be executing even if the activity * is destroyed. * Mistake number 3. Never use a direct reference of a view from acitivty inside an asynctask. * */
 private class BackgroundTask extends AsyncTask<Void.Void.String> {    
        @Override
        protected String doInBackground(Void... voids) {

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "The task is completed!";
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s); textView.setText(s); }}}Copy the code

How to solve this problem?

  • When the Activity is destroyed, we should cancel the asynchronous task because the unfinished AsyncTask will continue to execute even if the Activity has been Destoryed
  • Never reference an inner class in an Activity. If we really need to, we should make it a static class, because static inner classes do not contain any implicit references to their outer classes
  • Textview is referenced by weakReference
public class AsyncTaskReferenceLeakActivity extends AppCompatActivity {

    private TextView textView;
    private BackgroundTask backgroundTask;

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

        /* * Executing AsyncTask here! * * /
        backgroundTask = new BackgroundTask(textView);
        backgroundTask.execute();
    }


    /* * Fix number 1 * */
    private static class BackgroundTask extends AsyncTask<Void.Void.String> {

        private final WeakReference<TextView> messageViewReference;
        private BackgroundTask(TextView textView) {
            this.messageViewReference = new WeakReference<>(textView);
        }


        @Override
        protected String doInBackground(Void... voids) {

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "The task is completed!";
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
          /* * Fix number 3 * */          
            TextView textView = messageViewReference.get();
            if(textView ! =null) { textView.setText(s); }}}@Override
    protected void onDestroy(a) {
        super.onDestroy();

        /* * Fix number 2 * */
        if(backgroundTask ! =null) {
            backgroundTask.cancel(true); }}}Copy the code

7, a Handler Reference

Look at the following example, which updates the UI after five seconds with a Handler

public class HandlersReferenceLeakActivity extends AppCompatActivity {

    private TextView textView;

    /* * Mistake Number 1 * */
     private Handler leakyHandler = new Handler();


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

        /* * Mistake Number 2 * */
        leakyHandler.postDelayed(new Runnable() {
            @Override
            public void run(a) { textView.setText(getString(R.string.text_handler_1)); }},5000);
    }
Copy the code

How to solve this problem?

  • Never reference an inner class in an Activity. If we really need to, we should make it a static class. This is because when the Handler is instantiated on the main thread, it will be associated with Looper’s MessageQueue, and messages sent to the MessageQueue will hold references to the Handler so that when Looper finally processes the Message, The framework can call the Handler#handleMessage(message) method
  • The Activity is referenced by weakReference
public class HandlersReferenceLeakActivity extends AppCompatActivity {

    private TextView textView;

    /* * Fix number I * */
    private final LeakyHandler leakyHandler = new LeakyHandler(this);

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

        leakyHandler.postDelayed(leakyRunnable, 5000);
    }

    /* * Fix number II - define as static * */
    private static class LeakyHandler extends Handler {
      
    /* * Fix number III - Use WeakReferences * */      
        private WeakReference<HandlersReferenceLeakActivity> weakReference;
        public LeakyHandler(HandlersReferenceLeakActivity activity) {
            weakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlersReferenceLeakActivity activity = weakReference.get();
            if(activity ! =null) { activity.textView.setText(activity.getString(R.string.text_handler_2)); }}}private static final Runnable leakyRunnable = new Runnable() {
        @Override
        public void run(a) { / *... * /}}Copy the code

8, the Threads of the Reference

Thread and TimerTask can also cause memory leaks

public class ThreadReferenceLeakActivity extends AppCompatActivity {

    /* * Mistake Number 1: Do not use static variables * */    
    private static LeakyThread thread;

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

        createThread();
        redirectToNewScreen();
    }


    private void createThread(a) {
        thread = new LeakyThread();
        thread.start();
    }

    private void redirectToNewScreen(a) {
        startActivity(new Intent(this, SecondActivity.class));
    }


    /* * Mistake Number 2: Non-static anonymous classes hold an * implicit reference to their enclosing class. * */
    private class LeakyThread extends Thread {
        @Override
        public void run(a) {
            while (true) {}}}Copy the code

How to solve this problem?

  • A non-static anonymous class contains an implicit reference to its outer class, changing LeakyThread to a static inner class
  • Stop the thread in the onDestroy() method of the Activity to avoid thread leaks
public class ThreadReferenceLeakActivity extends AppCompatActivity {

    /* * FIX I: make variable non static * */
    private LeakyThread leakyThread = new LeakyThread();

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

        createThread();
        redirectToNewScreen();
    }


    private void createThread(a) {
        leakyThread.start();
    }

    private void redirectToNewScreen(a) {
        startActivity(new Intent(this, SecondActivity.class));
    }

    @Override
    protected void onDestroy(a) {
        super.onDestroy();
        // FIX II: kill the thread
        leakyThread.interrupt();
    }


    /* * Fix III: Make thread static * */
    private static class LeakyThread extends Thread {
        @Override
        public void run(a) {
            while(! isInterrupted()) { } } } }Copy the code

9, TimerTask Reference

The same principles can be followed for TimerTask, as shown in the following example to fix a memory leak:

public class TimerTaskReferenceLeakActivity extends Activity {

    private CountDownTimer countDownTimer;

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

        startTimer();
    }

    /* * Mistake 1: Cancel Timer is never called * even though activity might be completed * */
    public void cancelTimer(a) {
        if(countDownTimer ! =null) countDownTimer.cancel();
    }

    
    private void startTimer(a) {
        countDownTimer = new CountDownTimer(1000.1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                final int secondsRemaining = (int) (millisUntilFinished / 1000);
                //update UI
            }

            @Override
            public void onFinish(a) {
                //handle onFinish}}; countDownTimer.start(); }}Copy the code

How to solve this problem?

  • Stop the timer in the onDestroy() method of the Activity to avoid memory leaks
public class TimerTaskReferenceLeakActivity extends Activity {

    private CountDownTimer countDownTimer;

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

        startTimer();
    }


    public void cancelTimer(a) {
        if(countDownTimer ! =null) countDownTimer.cancel();
    }


    private void startTimer(a) {
        countDownTimer = new CountDownTimer(1000.1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                final int secondsRemaining = (int) (millisUntilFinished / 1000);
                //update UI
            }

            @Override
            public void onFinish(a) {
                //handle onFinish}}; countDownTimer.start(); }/* * Fix 1: Cancel Timer when * activity might be completed * */  
   @Override
    protected void onDestroy(a) {
        super.onDestroy(); cancelTimer(); }}Copy the code

10 and summarize

Finally, a brief summary:

  1. Whenever possible, use ApplicationContext instead of ActivityContext. If you really must use an ActivityContext, be sure to pass null Context when the Activity is destroyed
  2. Do not refer to views and activities through static variables
  3. Do not reference an inner class in an Activity. If you do need it, declare it static, whether it is Thread, Handler, Timer, or AsyncTask
  4. Remember to unregister the BroadcastReceiver and Timer in the Activity, and cancel any asynchronous tasks and threads in the onDestroy() method
  5. WeakReference is used to hold references to activities and views