Ultimate Solution to Android Memory Leak (Part 2)

  • 2020-12-16 06:08:32
  • OfStack

1. An overview of the

In the Final Solution to the MEMORY leak in Android (Part 1), we introduced how to check for a memory leak in an App. In this article, we summarize the typical code for memory leaks and give the corresponding solutions. The main problems with memory leaks can be categorized into the following types:

Memory leaks caused by static variables Memory leaks caused by non-static inner classes Memory leak caused by resource not being closed

2. Memory leak caused by static variables

In java, the lifetime of a static variable begins when the class is loaded and ends when the class is unloaded. In other words, in android its life cycle begins when the process starts and ends when the process dies. So if the process is not killed during the run of the program, the static variable will remain 1 and will not be recycled. If a static variable strongly refers to a variable in Activity, the Activity will not be released, even if the Activity executes onDestroy(do not equate executing onDestroy with being recycled). The solution to this problem is: 1. Find an alternative object that has a similar lifecycle to the static variable. 2. If it cannot be found, change the strong reference to the weak reference. Typical examples are as follows:

Context memory leak caused by singleton


public class IMManager {
  private Context context;
  private static IMManager mInstance;

  public static IMManager getInstance(Context context) {
    if (mInstance == null) {
      synchronized (IMManager.class) {
        if (mInstance == null)
          mInstance = new IMManager(context);
      }
    }
    return mInstance;
  }

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

}

When getInstance is called, if the context passed in is context for Activity. As long as this singleton is not released, the Activity is not released.

The solution
Pass in Application's context, because the life cycle of context of Application is longer than that of Activity, it can be understood that the life cycle of context of Application and singleton is as long as 1, so it is most appropriate to pass in.


public class IMManager {
  private Context context;
  private static IMManager mInstance;

  public static IMManager getInstance(Context context) {
    if (mInstance == null) {
      synchronized (IMManager.class) {
        if (mInstance == null)
          // Will the incoming context Converted to Application the context
          mInstance = new IMManager(context.getApplicationContext());
      }
    }
    return mInstance;
  }

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

}

3. Memory leak caused by non-static inner classes

In java, when you create a non-static inner class instance, you reference its peripheral instance. If the non-static inner class instance does some time-consuming operations, the peripheral objects will not be reclaimed, resulting in a memory leak. The solution to this problem is: 1. Change the inner class to a static inner class; 2. If you have a strong reference to an attribute in Activity, change the way the attribute is referenced to a weak reference. 3. End these time-consuming tasks when Activity executes onDestory, as business permits.

Memory leak caused by internal thread


public class LeakAty extends Activity {

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

  public void test() {
    // Anonymous inner classes refer to their peripheral instances LeakAty.this, This can cause a memory leak 
    new Thread(new Runnable() {

      @Override
      public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }).start();
  }
  }

The solution
Change the non-static anonymous inner class to static anonymous inner class


public class LeakAty extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.aty_leak);
    test();
  }
  // add static , becomes a static anonymous inner class 
  public static void test() {
    new Thread(new Runnable() {

      @Override
      public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }).start();
  }
}

Memory leak caused by Handler


public class LeakAty extends Activity {

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

  }

  private Handler mHandler = new Handler() {
    public void handleMessage(android.os.Message msg) {
      switch (msg.what) {
      case 0:
        //  The refresh data 
        break;
      default:
        break;
      }

    };
  };

  private void fetchData() {
    // To get the data 
    mHandler.sendEmptyMessage(0);
  }
}

mHandler is an anonymous inner class instance that refers to the peripheral object LeakAty.this. If the Handler still has messages to process when Activity exits, the Activity will not be reclaimed.

The solution


public class LeakAty extends Activity {
  private TextView tvResult;
  private MyHandler handler;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.aty_leak);
    tvResult = (TextView) findViewById(R.id.tvResult);
    handler = new MyHandler(this);
    fetchData();

  }
  // The first 1 Step, Handler Change to static inner class. 
  private static class MyHandler extends Handler {
    // The first 2 Step, you will need a reference Activity Change to weak reference. 
    private WeakReference<LeakAty> atyInstance;
    public MyHandler(LeakAty aty) {
      this.atyInstance = new WeakReference<LeakAty>(aty);
    }

    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      LeakAty aty = atyInstance == null ? null : atyInstance.get();
      // if Activity These messages are not processed if they are released for recycling 
      if (aty == null||aty.isFinishing()) {
        return;
      }
      aty.tvResult.setText("fetch data success");
    }
  }

  private void fetchData() {
    //  To get the data 
    handler.sendEmptyMessage(0);
  }

  @Override
  protected void onDestroy() {
    // The first 3 Step in Activity Remove callbacks when exiting 
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
  }
}

4. Memory leak caused by resource not being closed

When BraodcastReceiver, Cursor, Bitmap and other resources are used, they need to be released in time when they are not needed. If they are not released, memory leak will be caused.

To sum up, memory leaks are mainly of the three types mentioned above, which ultimately come down to the point that resources are not released when they are not needed. So in the coding process to pay attention to these details, improve the performance of the program.


Related articles: