In this article for beginner android developers, I will try to talk about what “memory leaks” in android are, why you should think about them on modern devices that allocate 192MB per application, how to quickly find and fix these leaks in an unfamiliar application and on What you need to pay special attention when developing any application.
The ultimate goal of this article is to answer a simple question:
Where to click to find out which line in the application to fix?
What is a "memory leak"?
To begin with, what is called a "memory leak". In the strict sense, an object can be called a memory leak, if it continues to exist in memory even after all references to it are lost. The problem immediately arises with this definition: the memory for all objects that you create is allocated with the participation of the garbage collector, and the garbage collector remembers all the objects created, regardless of whether you have a link to the object or not.
')
In fact, the garbage collector is extremely primitive (in fact, no - but the principle of operation is really simple): there is a graph in which every existing object is a vertex, and the link from any object to any other object is an edge. Some vertices on this graph are special. These are garbage collection roots - those entities that are created by the system and continue to exist, regardless of whether other objects refer to them or not. If and only if there is any path on the graph from this object to any root, the object will not be destroyed by the garbage collector.
This is the problem - if the object is not destroyed, then there is a chain of links from the root to this object (or, if such a chain does not exist, the object will be destroyed during the next garbage collection). And this means that no object can be a leak memory in the strict sense of the term. In fact, even the fact that the garbage collector itself stores a link to each existing object in the system is already enough.
Attempts to get a “clean” memory leak in java were
made repeatedly and, of course, continue to be made, but none of the ways can make the garbage collector forget the reference to the object without releasing the memory. There are memory leaks associated with the allocation of memory by native code (JNI), but in this article we will not consider them.
Conclusion:
you can lose all references to the object you are interested in, but the garbage collector remembers.
So, the definition of "memory leak" in the strict sense does not suit us. Therefore, we will further understand the memory leak as an object that continues to exist after it should be destroyed.
Next, I will show a few of the most common cases of memory leaks, show you how to detect them and how to avoid them. If you learn to fix these typical memory leaks, then with a probability of 99.9%, there will be no memory leaks in your application that you should worry about.
But, before proceeding to the description of these common mistakes, you need to answer the main question: is it necessary to correct these mistakes at all? The application works ...
Why waste time fixing memory leaks?
Applications do not fall for a long time already because you forgot to squeeze resources into the drawable-ldpi folder. Preparing to write this article, I conducted a simple experiment: I took one of the running applications, and added a memory leak to it in such a way that none of the created activity was ever unloaded from memory (began adding them to the static list). I opened the app and started shouting the screens, waiting for the app to finally fall on my Nexus 5. Finally, after 5 minutes and 55 screens, the app dropped. The irony is that, according to Google Analytics, the user usually visits 3 screens per session.
So do you need to worry about memory leaks if the user can simply not notice them? Yes, and there are three reasons why.
First , if something doesn’t work in your application, it can lead to very serious and hard-to-debug problems.
For example, you have developed an application for a social network. In this application, you can exchange messages between users, where there is a timer on the messaging screen, which makes a request to the server every 10 seconds to receive new messages, but you forgot to turn this timer off when you exit the screen. What does this lead to visually? Yes to anything. You will not notice that the application is doing something wrong. But at the same time, the application will continue to send a request to the server every 10 seconds. Even after you exit the application. Even after you turn off the screen (behavior may vary from phone). If a user enters the screens of communication with three different friends, within an hour you will receive 1000 unnecessary requests to the server and one user very angry with your application, which heavily consumes the battery. These are the results I got with the test application on the phone with the screen off.
You can argue that this is not a memory leak, but just a non-disabled timer and this is a completely different error. It does not matter. It is important that by checking your application for memory leaks, you will find other errors. When we check the application for memory leaks, we want to find all the objects that exist, but should not exist. Finding such objects, we immediately understand what extra operations continue to be performed.
Secondly , not all applications consume little memory, and not all phones allocate a lot of memory.
Remember the application, which fell only after 5 minutes and 55 screens are not unloaded? So for the same application, I receive 1-2 reports of a fall every week with an OutOfMemoryException (mostly from devices up to 4.0; the application has 50,000 installations). And this is despite the fact that there are no memory leaks in the application. Therefore, even now you can pretty much spoil your karma by laying out an application with memory leaks, especially if your application consumes a lot of memory. As usual in the android world, from the brilliant future separates us from the harsh present.
Thirdly , a man should be able to do everything! (I promised that all 3 reasons would be serious)
Now that I, hopefully, have convinced you of the need to catch memory leaks, let's consider the main reasons for their occurrence.
Never save references to activity (view, fragment, service) in static variables
One of the first questions that every novice developer faces is how to transfer an object from one activity to the next. The simplest and most incorrect decision that I occasionally have to see is the recording of the first activity in a static variable and accessing this variable from the second activity. This is a very unfortunate approach. Not only because it instantly causes a memory leak (a static variable will continue to exist as long as the application exists, and the activity to which it refers will never be unloaded). This approach can also lead to a situation where you will exchange information with the wrong screen, because a screen that is invisible to the user can be destroyed and recreated at any time only when the user returns to it.
Why is leakage activity such a big problem? The fact is that if the garbage collector does not collect the activity, then it will not collect all the view and fragment, and with it all the other objects located on the activity. Including pictures will not be released. Therefore, the leakage of any activity is usually the largest memory leak that may be in your application.
Never write links to activity in static variables. Use the
transfer of objects through the Intent , or generally transfer not the object, but the object's id (if you have a database from which this id can then be retrieved).
This item also applies to any objects whose lifetime is directly or indirectly controlled by android. Those. to view, fragment, service, etc ..
View and fragment objects contain a link to the activity in which they are located, so if one single view leaks, everything will flow away at once - the activity and all the views in it, and with them all drawables and everything that any element has There is a link from the screen!
Be careful when you transfer the reference to the activity (view, fragment, service) to other objects.
Consider a simple example: your application for a social network displays the last name, first name and rating of the current user on each screen of the application. An object with a current user profile exists from the moment you log in to the moment you log out of it, and all the screens of your application access the same object for information. This object also periodically updates data from the server, since the rating can change frequently. It is necessary that the object with the profile notifies the current activity about updating the rating. How to achieve this? Very simple:
@Override protected void onResume() { super.onResume(); currentUser.addOnUserUpdateListener(this); }
How to achieve a memory leak in this situation? Also very easy! Just forget to unsubscribe from notifications in the onPause method:
@Override protected void onPause() { super.onPause(); currentUser.removeOnUserUpdateListener(this); }
Because of such a memory leak, the activity will continue to update the interface each time the profile is updated, even after the screen is no longer visible to the user. Worse, in this way the screen can sign 2, 3 or more times on the same notification. This can lead to visible interface brakes when the profile is updated — and not just on this screen.
What to do to avoid this error?
First , of course, you should always carefully monitor the fact that you unsubscribed from all notifications when the activity left for the background.
Secondly , you should periodically check your application for memory leaks.
Thirdly , there is an alternative approach to the problem: you can save not links to objects, but
weak links . This is especially useful for the heirs of the View class - because they have no onPause method and it is not entirely clear at what point they should unsubscribe from the notification. Weak references are not considered garbage collector as a relationship between objects, so an object to which only weak references exist will be destroyed, and the link will no longer refer to the object and will accept null. In order not to bother with weak links that are not very convenient to use, you can use approximately the following template class:
public class Observer<I> { private ArrayList<I> strongListeners = new ArrayList<I>(); private ArrayList<WeakReference<I>> weakListeners = new ArrayList<WeakReference<I>>(); public void addStrongListener(I listener) { strongListeners.add(listener); } public void addWeakListener(I listener) { weakListeners.add(new WeakReference<I>(listener)); } public void removeListener(I listener) { strongListeners.remove(listener); for (int i = 0; i < weakListeners.size(); ++i) { WeakReference<I> ref = weakListeners.get(i); if (ref.get() == null || ref.get() == listener) { weakListeners.remove(i--); } } } public List<I> getListeners() { ArrayList<I> activeListeners = new ArrayList<I>(); activeListeners.addAll(strongListeners); for (int i = 0; i < weakListeners.size(); ++i) { WeakReference<I> ref = weakListeners.get(i); I listener = ref.get(); if (listener == null) { weakListeners.remove(i--); continue; } activeListeners.add(listener); } return activeListeners; } }
Which will work something like this:
public class User { ... public interface OnUserUpdateListener { public void onUserUpdate(); } private Observer<OnUserUpdateListener> updateObserver = new Observer<OnUserUpdateListener>(); public Observer<OnUserUpdateListener> getUpdateObserver() { return updateObserver; } } ... @Override protected void onFinishInflate() { super.onFinishInflate(); currentUser.getUpdateObserver().addWeakListener(this); } ...
Yes, you can get extra updates of this view. But often this is the lesser of evils. And, in any case, you will not receive a memory leak.
There is only one subtlety when using the addWeakListener method: an object that you add must be referenced by someone. Otherwise, the garbage collector will destroy this object before it receives its first notification:
currentUser.getUpdateObserver().addWeakListener(new OnUserUpdateListener() { @Override public void onUserUpdate() { } });
Timers and threads that are not canceled when leaving the screen
I have already mentioned this problem above: so, you have developed an application for a social network. In this application, you can exchange messages between users, and you add a timer to the messaging screen, which makes a request to the server every 10 seconds to receive new messages, but you forgot to turn off this timer when you exit the screen:
public class HandlerActivity extends Activity { private Handler mainLoopHandler = new Handler(Looper.getMainLooper()); private Runnable queryServerRunnable = new Runnable() { @Override public void run() { new QueryServerTask().execute(); mainLoopHandler.postDelayed(queryServerRunnable, 10000); } }; @Override protected void onResume() { super.onResume(); mainLoopHandler.post(queryServerRunnable); } @Override protected void onPause() { super.onPause(); } ... }
Unfortunately, this problem is difficult to avoid. The only two tips you can give are the same as in the previous paragraph: be careful and periodically check the application for memory leaks. You can also use the
approach similar to the previous paragraph
using weak links .
Never save links to fragment in activity or another fragment.
I have seen this mistake so many times. Activity stores links to 5-6 launched fragments even though only 1 is always visible on the screen. One fragment stores a link to another fragment. Fragments that are visible on the screen at different times communicate with each other via direct cached links. FragmentManager in such applications most often plays a rudimentary role - at the right time it replaces the contents of the container with the necessary fragment, and the fragments themselves are not added to the back stack (adding a fragment to which you have a direct link, in back stack, sooner or later that the fragment will be unloaded from memory; after returning to this fragment, a new one will be created, and your link will continue to refer to the existing fragment, but invisible to the user.
This is a very bad approach for a variety of reasons.
First, if you keep direct links to 5-6 fragments in the activity, then this is the same as if you stored links to 5-6 activity. The entire interface, all pictures and all logic 5 unused fragments cannot be unloaded from memory while the activity is running.
Secondly, these fragments become extremely difficult to reuse. Try to move the fragment to another place of the program, provided that it must be necessarily launched in the same activity with fragments, x, y and z, which you do not need to transfer.
Treat fragments like activity. Make them as modular as possible, communicate between fragments only through activity and fragmentManager. This may seem like an overly complex system: why try so hard when you can just pass the link? But, in fact, this approach will make your program better and easier.
On this topic there is a great official article from Google:
"Communicating with Other Fragments" . Re-read this article and never save pointers to fragments again.
Generalized rule
After reading the previous four points, you may have noticed that they are almost the same. All this is a special case of one general rule.
All memory leaks appear if and only if you save a link to an object with a short life cycle (short-lived object) in an object with a long life cycle (long-lived object).
Keep this in mind and always be sensitive to such situations.
This rule does not have a nice short name such as KISS, YAGNI or RTFM, but it applies to all languages with the garbage collector and all objects, not just the activity in android.
Now that I, hopefully, have shown the main sources of memory leaks, let's finally move on to identifying them in the working application.
Where to click to find out which line in the application to fix?
So, you know how to avoid memory leaks, but this does not protect you from typos, bugs and projects that you wrote before you learned how to avoid memory leaks.
In order to determine the presence and source of memory leaks in the application, you need a little time and
MAT . If you have never used MAT before,
install it as a plugin for eclipse , open the DDMS perspective and find the “Dump HPROF file” button. Pressing this button will open the memory dump of the selected application. If you are using Android Studio, the process will be a bit more complicated, since at the moment MAT still does not exist as a plugin for Android Studio. Put MAT as a separate program and use the
stackoverflow instruction.
Perform the following steps:
- Install the application on the device connected to the computer and use it in such a way as to appear on each screen at least once. If one screen can be opened with different parameters, try to open it with all possible combinations of parameters. In general - go around the entire application, as if you were checking it before release. After you have gone through all the screens, press the back button until you exit the application. Do not press the home button - your task is to complete all running activities, and not just hide them.
- Press the Cause GC button several times. If you do not do this, the objects that are to be destroyed by the garbage collector will be visible in the dump, but have not yet been destroyed.
- Make a memory dump of the application by clicking on the "Dump HPROF file" button.
- In the window that opens, make an OQL query: "SELECT * FROM instanceof android.app.Activity"
The list of results must be empty. If there is at least one element in the list, then this element is your memory leak. In the screenshot you see just such an element - HandlerActivity: this is a memory leak. Follow steps 8-10 for each item in the list. - Perform similar queries for successors of Fragment: "SELECT * FROM instanceof android.app.Fragment". , , — . 8-10.
- histogram. , histogram, , OQL , histogram , . package name ( com.examples.typicalleaks) objects ( ). , , 0 . . — , Calculate Precise Retained Size. Retained Heap Retained Heap, 10000.
, , . — , , . , 6 Example Example[]. — Example enum, . HandlerActivity HandlerActivity$1 ( , HandlerActivity.java) — . , list objects, 8-10 . - — ! .
- Merge Shortest Paths to GC Roots — exclude all phantom/weak/soft etc. references.
- . :
. — . , — , . . : , . Those. , mMessages MessageQueue Message, callback, HandlerActivity$1, HandlerActivity this$0. , HandlerActivity Runnable, HandlerActivity.java, Handler post postDelayed. , , Open Source File. - , , . Handler.removeCallbacks(Runnable r) onPause HandlerActivity.
- , , 1, , .
Conclusion
, , 99.9%, .
. , . , uuid — , .