- 9 ways to avoid memory leaks in Android
- What are memory leaks?
- So how do I check if my app is leaking?
- So what are some of the common mistakes that lead to memory leaks?
- 1. Broadcast Receivers:
- 2. Static Activity or View Reference:
- 3. Singleton Class Reference:
- 4. Inner Class Reference:
- 5. Anonymous Class Reference:
- 6. AsyncTask Reference:
- 7. Handler Reference:
- 8. Threads Reference:
- 9. TimerTask Reference:
- Memory Management in Android
- Android Memory Management
- Garbage collection
- How to Improve Memory Usage
- How to Avoid Memory Leaks
- Android Profiling Tools
- Using Allocation Tracker:
- Steps for starting Allocation Tracker in Android DDMS
9 ways to avoid memory leaks in Android
May 16, 2018 · 6 min read
I have been an android developer for quite some time now. And I realised that most of that time, I tend to spend on adding new features to an app or working on visually enhancements of the app, rather than focusing on the core issues like performance and quality.
This was me a while back when I would be asked to optimise or refactor code
But these things have a way of catc h ing up to you and I spent a long time wishing I had not ignored the important stuff. So in this article, I thought I would focus on one of the most important optimisation techniques in android: Memory leaks.
There are a lot of articles on memory leaks and how to fix them. But when I was learning myself, I found that none of them were in one place and it was hard to keep track of it all. So I thought I would collectively post an article on it so that it might help people in the future.
What are memory leaks?
A memory leak happens when memory is allocated but never freed. This means the garbage collector is not able to take out the trash once we are done with the takeout. Initially, this might not be a problem. But imagine if you don’t take the trash out for 2 weeks! The house starts to smell right?
Similarly, as the user keeps on using our app, the memory also keeps on increasing and if the memory leaks are not plugged, then the unused memory cannot be freed up by the Garbage Collection. So the memory of our app will constantly increase until no more memory can be allocated to our app, leading to OutOfMemoryError which ultimately crashes the app.
So how do I check if my app is leaking?
I am sure everyone on the planet is aware of it but just in case, there is an awesome library called LeakyCanary that is great for finding out the leaks in our app along with the stack trace.
So what are some of the common mistakes that lead to memory leaks?
1. Broadcast Receivers:
Consider this scenario — you need to register a local broadcast receiver in your activity. If you don’t unregister the broadcast receiver, then it still holds a reference to the activity, even if you close the activity.
How to solve this? Always remember to call unregister receiver in onStop() of the activity.
Note: As Artem Demyanov pointed out, if the broadcast Receiver is registered in onCreate() , then when the app goes into the background and resumed again, the receiver will not be registered again. So it is always good to register the broadcastReceiver in onStart() or onResume() of the activity and unregister in onStop() .
2. Static Activity or View Reference:
Consider the below example — You are declaring a TextView as static (for whatever reason). If you reference an activity or view directly or indirectly from a static reference, the activity would not be garbage collected after it is destroyed.
How to solve this? Always remember to NEVER use static variables for views or activities or contexts.
3. Singleton Class Reference:
Consider the below example — you have defined a Singleton class as displayed below and you need to pass the context in order to fetch some files from the local storage.
The SingletonSampleClass. java
How to solve this?
Option 1: Instead of passing activity context i.e. this to the singleton class, you can pass applicationContext().
Option 2: If you really have to use activity context, then when the activity is destroyed, ensure that the context you passed to the singleton class is set to null.
4. Inner Class Reference:
Consider the below example — you have defined a inner class called LeakyClass.java as displayed below and you need to pass the activity in order to redirect to a new activity.
How to solve this?
Option 1: As mentioned before never create a static variable of an inner class.
Option 2: The class should be set to static. Instances of anonymous classes do not hold an implicit reference to their outer class when they are “static” .
Option 3: Use a weakReference of any view/activity. Garbage collector can collect an object if only weak references are pointing towards it.
5. Anonymous Class Reference:
This follows the same theory as above. Sample implementation for fixing memory leak is given below:
6. AsyncTask Reference:
Consider the following example — You are using an asyncTask to get a string value which is used to update the textView in OnPostExecute().
How to solve this?
Option 1: 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.
Option 2: NEVER reference a class inside the activity. If we definitely need to, we should set the class as static as static inner classes don’t hold any implicit reference to its parent activity class.
Option 3: Use a weakReference of the textview.
7. Handler Reference:
Consider the following example — You are using a Handler to redirect to a new screen after 5 seconds.
How to solve this?
Option 1: NEVER reference a class inside the activity. If we definitely need to, we should set the class as static. This is because when a Handler is instantiated on the main thread, it is associated with the Looper’s message queue. Messages posted to the message queue will hold a reference to the Handler so that the framework can call Handler#handleMessage(Message) when the Looper eventually processes the message.
Option 2: Use a weakReference of the Activity.
8. Threads Reference:
We can repeat this same mistake again with both the Thread and TimerTask classes.
How to solve this?
Option 1: Non-static anonymous classes hold an implicit reference to their enclosing class.
Option 2: Close thread in activity onDestroy() to avoid thread leak.
9. TimerTask Reference:
The same principle is as Threads can be followed for TimerTask as well. Sample implementation for fixing memory leak is given below:
How to solve this?
Option 1: Cancel timer in activity onDestroy() to avoid memory leak.
So basically to summarise:
1. Use applicationContext() instead of activity context when possible. If you really have to use activity context, then when the activity is destroyed, ensure that the context you passed to the class is set to null.
2. Never use static variables to declare views or activity context.
3. Never reference a class inside the activity. If we need to, we should declare it as static, whether it is a thread or a handler or a timer or an asyncTask.
4. Always make sure to unregister broadcastReceivers or timers inside the activity. Cancel any asyncTasks or Threads inside onDestroy().
5. Always use a weakReference of the activity or view when needed.
And that’s it folks! Thanks for reading! I hope you enjoyed this article and found it useful, if so please hit the Clap button. Let me know your thoughts in the comments section.
Источник
Memory Management in Android
Oct 31, 2017 · 9 min read
Android Memory Management
Android uses paging and mmap instead of providing swap space, which means any memory your application touches cannot be paged out unless you release all references.
The Dalvik* Virtual Machine’s heap size for application processes is limited. Applications start up with 2 MB, and the maximum allocation, marked as “largeHeap,” is limited to 36 MB (depending on the specific device configuration). Examples of large heap applications are Photo/Video Editor, Camera, Gallery, and Home Screen.
Android stores background a pplication processes in a LRU cache. When the system runs low on memory, it will kill processes according to the LRU strategy, but it will also consider which application is the largest memory consumer. Currently the maximum background process count is 20 (depending on the specific device configuration). If you need your app to live longer in the background, de-allocate unnecessary memory before moving to the background and the Android system will less likely generates error message or even terminates the app.
Garbage collection
A managed memory environment, like the ART or Dalvik virtual machine, keeps track of each memory allocation. Once it determines that a piece of memory is no longer being used by the program, it frees it back to the heap, without any intervention from the programmer. The mechanism for reclaiming unused memory within a managed memory environment is known as garbage collection. Garbage collection has two goals: find data objects in a program that cannot be accessed in the future; and reclaim the resources used by those objects.
Android’s memory heap is a generational one, meaning that there are different buckets of allocations that it tracks, based on the expected life and size of an object being allocated. For example, recently allocated objects belong in the Young generation. When an object stays active long enough, it can be promoted to an older generation, followed by a permanent generation.
Each heap generation has its own dedicated upper limit on the amount of memory that objects there can occupy. Any time a generation starts to fill up, the system executes a garbage collection event in an attempt to free up memory. The duration of the garbage collection depends on which generation of objects it’s collecting and how many active objects are in each generation.
Even though garbage collection can be quite fast, it can still affect your app’s performance. You don’t generally control when a garbage collection event occurs from within your code. The system has a running set of criteria for determining when to perform garbage collection. When the criteria are satisfied, the system stops executing the process and begins garbage collection. If garbage collection occurs in the middle of an intensive processing loop like an animation or during music playback, it can increase processing time. This increase can potentially push code execution in your app past the recommended 16ms threshold for efficient and smooth frame rendering.
Additionally, your code flow may perform kinds of work that force garbage collection events to occur more often or make them last longer-than-normal. For example, if you allocate multiple objects in the innermost part of a for-loop during each frame of an alpha blending animation, you might pollute your memory heap with a lot of objects. In that circumstance, the garbage collector executes multiple garbage collection events and can degrade the performance of your app.
Android studio includes a debugging tool called the Dalvik Debug Monitor Service (DDMS). DDMS provides services like screen capture on the device, threading, heap information on the device, logcat, processes, incoming calls, SMS checking, location, data spoofing, and many other things related to testing your Android application.
DDMS connects the IDE to the applications running on the device. On Android, every application runs in its own process, each of which hosts its own virtual machine (VM). And each process listens for a debugger on a different port.
When it starts, DDMS connects to ADB ( Android Debug Bridge which is a command-line utility included with Google’s Android SDK.). An Android Debugger is used for debugging the Android app and starts a device monitoring service between the two. This will notify DDMS when a device is connected or disconnected. When a device is connected, a VM monitoring service is created between ADB and DDMS, which will notify DDMS when a VM on the device is started or terminated.
How to Improve Memory Usage
Android is a worldwide mobile platform and millions of Android developers are dedicated to building stable and scalable applications. Here is a list of tips and best practices for improving memory usage in Android applications:
- Be careful about using a design pattern with “abstraction”. Although from the point of view of design pattern, abstraction can help to build more flexible software architect. In mobile world, abstraction may involve side effect for its extra code to be executed, which will cost more time and memory. Unless abstraction can provide your application a significant benefit, you would be better not to use it.
- Avoid using “enum”. Enum will double memory allocation than ordinary static constant, so do not use it.
- Try to use the optimized SparseArray, SparseBooleanArray, and LongSparseArray containers instead of HashMap. HashMap allocates an entry object during every mapping which is a memory inefficient action, also the low performance behavior — “autoboxing/unboxing” is spread all over the usage. Instead, SparseArray-like containers map keys into plain array. But please remember that these optimized containers are not suitable for large numbers of items, when executing add/remove/search actions, they are slower than Hashmap if your data set is over thousands of records.
- Avoid creating unnecessary objects. Do not allocate memory especially for short-term temporary objects if you can avoid it, and garbage collection will occur less when fewer objects are created.
- Check the available heap of your application. Invoke ActivityManager::getMemoryClass() to query how many heap(MB) is available for your application. OutofMemory Exception will occur if you try to allocate more memory than is available. If your application declares a “largeHeap” in AndroidManifest.xml, you can invoke ActivityManager::getLargeMemoryClass() to query an estimated large heap size.
- Coordinate with the system by implementing onTrimMemory() callback. Implement ComponentCallbacks2::onTrimMemory(int) in your Activity/Service/ContentProvider to gradually release memory according to latest system constraints. The onTrimMemory(int) helps overall system response speed, but alsokeep your process alive longer in the system.
- When TRIM_MEMORY_UI_HIDDEN occurs, it means all the UI in your application has been hidden and you need to free UI resources. When your application is foreground, you may receive TRIM_MEMORY_RUNNING[MODERATE/LOW/CRITICAL], or in the background you may receive TRIM_MEMORY_[BACKGROUND/MODERATE/COMPLETE]. You can free non-critical resources based on the strategy to release memory when system memory is tight.
- External libraries should be used carefully. External libraries are often written for non-mobile device and can be inefficient in Android. You must take into account the effort in porting and optimizing the library for mobile before you decide to use it. If you are using a library for only one or two features from its thousands of other uses, it may be best to implement it by yourself.
- Services should be used with caution. If you need a service to run a job in the background, avoid keeping it running unless it’s actively performing a task. Try to shorten its lifespan by using an IntentService, which will finish itself when it’s done handling the intent. Services should be used with the caution to never leave one running when it’s not needed. Worst case, the overall system performance will be poor and users will find your app and uninstall it (if possible).
- But if you are building an app that needs to run for a long period of time, e.g., a music player service, you should split it into two processes: one for the UI and the other for the background service by setting the property “android:process” for your Service in AndroidManifest.xml. The resources in the UI process can be released after hidden, while the background playback service is still running. Keep in mind that the background service process MUST NOT touch any UI; otherwise, the memory allocation will be doubled or tripled!
How to Avoid Memory Leaks
Use memory carefully with above tips can bring benefit for your application incrementally, and make your application stay longer in system. But all benefit will lost if memory leakage happens. Here are some familiar potential leakage that developer needs to keep in mind.
- Remember to close the cursor after querying the database. If you want to keep the cursor open long-term, you must use it carefully and close it as soon as the database task finished.
- Remember to call unregisterReceiver() after calling registerReceiver().
- Avoid Context leakage. If you declare a static member variable “Drawable” in your Activity, and then call view.setBackground(drawable) in onCreate(), after screen rotate, a new Activity instance will be created and the old Activity instance can never be de-allocated because drawable has set the view as callback and view has a reference to Activity (Context). A leaked Activity instance means a significant amount of memory, which will cause OOM easily.
- There are two ways to avoid this kind of leakage:
- Do not keep long-lived references to a context-activity. A reference to an activity should have the same life cycle as the activity itself.
- Try using the context-application instead of a context-activity.
5. Be careful about using Threads. Threads in Java are garbage collection roots; that is, the Dalvik Virtual Machine (DVM) keeps hard references to all active threads in the runtime system, and as a result, threads that are left running will never be eligible for garbage collection. Java threads will persist until either they are explicitly closed or the entire process is killed by the Android system. Instead, the Android application framework provides many classes designed to make background threading easier for developers:
- Use Loader instead of a thread for performing short-lived asynchronous background queries in conjunction with the Activity lifecycle.
- Use Service and report the results back to the Activity using a BroadcastReceiver.
- Use AsyncTask for short-lived operations.
Android Profiling Tools
Android Profiling Tool will help you in managing your memory on the Android device. The Android SDK provides two ways of profiling app memory:
- Using Allocation Tracker
- Using Heap Dump
Using Allocation Tracker:
Allocation Tracker records each memory allocation that your application performs during the profiling cycle. The Allocation Tracker is useful when you want to find out which type of memory allocation is taking place. But it does not give you any information about the application’s heap which is the memory set aside for dynamic memory allocation.
The Allocation Tracker displays the memory allocation for the selected process. It shows the specific objects that are being allocated along with the thread, method and the line code that allocated them.
DDMS provides a feature to track objects that are being allocated to memory and to see which classes and threads are allocating the objects. This allows you to track where the objects are being allocated in real time, when you perform certain actions in your application. This data is valuable for measuring memory usage that can otherwise affect application performance.
Steps for starting Allocation Tracker in Android DDMS
Источник