In Android development, when an object is no longer needed and should have been recycled, another object in use holds a reference to it, which prevents it from being recycled and the memory it occupies is not released, resulting in a memory leak.
Memory leaks are an easy problem to occur and are not as easy to locate as programs ANR and Crash.Memory leak issues require a developer's experience and development skills and are one of the most common errors for a developer.There are two main aspects to optimize memory leaks.One is to avoid writing out code that might leak during development, the other is to find out potential memory leak problems and solve them through some analysis tools such as MAT.The following lists some causes and scenarios for memory leaks, as well as the optimal solution for dealing with memory leaks.
Reasons for memory leaks
1. Memory leak caused by singleton mode
The static nature of the singleton pattern makes the life cycle of the singleton object as long as that of the application.If an object is no longer needed and a single object holds a reference to the object, the object will not be recycled properly, and the memory occupied by the object cannot be freed in time, a memory leak will occur
Typical code
public class TestManager {
private static TestManager testManager = null;
private Context mContext;
private TestManager(Context context) {
mContext = context;
}
public static TestManager getInstance(Context context) {
if(testManager == null) {
testManager = new TestManager(context);
}
return testManager;
}
}
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestManager testManager = TestManager.getInstance(this);
}
}
TestManager is a typical singleton pattern class. It needs to pass context in order to get the object of singleton pattern. In MainActivity, the current Activity is passed as context to get testManager. The object and application of singleton pattern have the same life cycle. After the current Activity exits, MainActivity exits memory because testManager holds a reference to Activity.Will not be recycled.
Solution: When a singleton pattern object is constructed, the Context of the Application is passed in, and the lifecycle of the singleton is the same as that of Application, so it has no effect
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestManager testManager = TestManager.getInstance(getApplicationContext());
}
}
2. Non-static internal classes create static instances
Non-static internal classes hold references to external classes by default when they are constructed. If a static internal class instance is created, the static instance will hold references to external classes throughout the life cycle of the application, resulting in external classes that can no longer be used and cannot be recycled in time
Typical code
public class MainActivity extends ActionBarActivity {
public static InnerClass innerClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
innerClass = new InnerClass();
innerClass.innerClassMethod();
}
class InnerClass {
public InnerClass() {
}
public void innerClassMethod() {
System.out.println("This is a inner class method");
}
}
}
InnerClass is an internal class of MainActivity. When an instance is created, an instance of an external class is created by default. Static internal class instances exist throughout the life cycle of the application. After MainActivity exits, instances of internal classes also hold a reference to MainActivity so that MainActivity is not recycled
Solution: Make the internal class static or encapsulate it as a singleton. Static internal classes do not need to rely on external classes to create instances, so they do not hold instances of external classes.
public class MainActivity extends ActionBarActivity {
public static InnerClass innerClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
innerClass = new InnerClass();
innerClass.innerClassMethod();
}
static class InnerClass {
public InnerClass() {
}
public void innerClassMethod() {
System.out.println("This is a inner class method");
}
}
}
3. Memory leak caused by resource not closing
Most typically, when using dynamically registered broadcasts, they are typically registered in the Activity's onStart() method, and memory leaks occur if the broadcasts are not unregistered when the Activity is destroyed.These problems include
BroadcastReceiver uses unregisterReceiver(receiver) to unregister
ContentObserver uses getContentResolver().unregisterContentObserver(observer) to cancel listening
File object closes a file using the close() method after opening it
Cursor object is closed using the close() method
Closing input and output stream objects using the close() method
Recycle Bitmap after use
4. Memory leak caused by Handler
This is the most common type of problem to encounter. Handler is often used in development to communicate between subthreads and main threads. Excessive abuse of Handler can cause memory leaks.When an internal class (including anonymous classes) is used to create a Handler, the Handler object implicitly holds a reference to an external class (usually an Activity), which is mainly used for communication between threads, so it is usually accompanied by a time-consuming background thread that notifies the main thread to update the interface after the task is executed.If the background thread has not finished executing when the activity exits and the thread holds a reference to the Handler and the Handler holds a reference to the Activity, the activity cannot be recycled
Typical code
public class MainActivity extends ActionBarActivity {
Handler handler = new Handler() {
public void handleMessage(Message msg) {
Toast.makeText(MainActivity.this, "Task execute complete", Toast.LENGTH_LONG).show();
};
};
Thread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
thread = new Thread() {
public void run() {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
handler.sendEmptyMessage(0x123);
};
};
thread.start();
}
}
The above code starts a thread to execute a time-consuming task at the start of the activity, notifies the activity to display a Toast when it finishes executing, and if it exits the activity before the thread finishes executing, the thread will continue to hold a reference to the Handler and the Handler will hold a reference to the Activity
Solution: Stop the background thread when closing the Activity. When the thread stops, it will not hold a reference to the Handler and will not affect the Activity recycling.
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
thread.interrupt();
}
In another case, the Handler's postDelay() method is executed, which loads the Handler into a Message and pushes the Message into the MessageQueue. Before the set delay arrives, there will be a holding chain of MessageQueue->Message->Handler->Activity. When the Activity exits, the Handler will also hold a reference to the Activity if the message of the post has not been executed.
Typical code
public class MainActivity extends ActionBarActivity {
Handler handler = new Handler();
Runnable runnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
runnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
handler.postDelayed(runnable, 5000);
}
}
The above code pushes a message to the main thread 5 seconds after the Activity starts, and if the Activity exits before the message is pushed, the Handler holds a reference to the Activity while waiting for the message to be pushed
Solution: Remove the pushed message object from the message queue by using the removeCallbacks() method of Handler when the Activity is destroyed
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
handler.removeCallbacks(runnable);
}
For the above case, you can also declare the Handler as a static class, which does not hold objects of external classes and does not affect Activity recycling.However, static internal classes cannot operate on non-static members of external classes, and need to add a weak reference to the Activity in the Handler. When the garbage collector reclaims memory, objects with weak references are reclaimed regardless of whether there is enough memory, so memory recycling is not affected.
Sample Code
public class MainActivity extends ActionBarActivity {
MyHandler handler = new MyHandler(this);
static class MyHandler extends Handler {
WeakReference<Activity> reference;
MyHandler(Activity activity) {
reference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
final Activity activity = reference.get();
if(activity != null) {
Toast.makeText(activity, "Task execute complete", Toast.LENGTH_LONG).show();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread() {
public void run() {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
handler.sendEmptyMessage(0x123);
};
}.start();
}
}
5. Memory leak caused by property animation
In the property animation, there is an infinite loop of animation. If the animation is played in the Activity and the animation does not end when the Activity is destroyed, the animation will continue to play, although the animation is no longer visible on the interface, then the View in the Activity will be held by the animation, and the View will hold the Activity, and the Activity will not be released in the end.
Typical code
public class MainActivity extends ActionBarActivity {
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button)findViewById(R.id.button);
ObjectAnimator animator = ObjectAnimator.ofFloat(button, "alpha", 1, 0).setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE); //Set the number of animation repeats to infinite
animator.start();
}
}
Solution: End animation when Activity is destroyed
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
animator.cancel();
}