Android memory and performance optimization. This tutorial describes how to optimize the usage of memory and optimize your performance in your Android application. The tutorial is based on Android Studio.
1. Programming tips for providing highly responsive and fast Android applications
1.1. Why you should be careful with Android resources
Android devices have less power than standard desktop or notebook computers. For this reason you must be careful with memory consumption.
Especially on Android devices before Android 5.0 you want to avoid triggering the garbage collector of the Java virtual machine. This results in a freeze of the Android runtime for about 200 ms. This can be a notable delay, if the user is, for example, scrolling down a list.
1.2. Avoid unnecessary object allocation
Avoid creating unnecessary objects, especially in expensive places. Reuse objects if possible. Creating unnecessary objects triggers the garbage collection more frequently, and this should be avoided.
For example avoid object creating in loops or in the onDraw()
method of your custom view.
1.3. Use efficient data structures
Android provides several implementations of Sparse*Array
classes. Consider the following code.
Map<Integer, String> map = new HashMap<Integer, String>();
Using this code results in unnecessary Integer
objects created.
Android provides data structures which are more efficient for mapping values to other objects. If possible use these objects, they avoid object creation as in the case of using HashMap. Object creation can be expensive and should be avoided to reduce the number of times the garbage collector needs to run.
The table give examples for SparseArrays.
Memory structure | Description |
---|---|
SparseArray<E> |
Maps integers to Objects, avoid the creation of Integer objects. |
SparseBooleanArray |
Maps integers to booleans. |
SparseIntArray |
Maps integers to integers |
To improve the above example, prefer to use the following data structure.
SparseArray<String> map = new SparseArray<String>();
map.put(1, "Hello");
2. Handling bitmaps
Bitmaps can allocate lots of memory if loaded at full size. It is recommended to load the bitmaps in the desired size into memory. Assume you have an application which displays an image in 100x100 dp, you should load the image in exactly this size.
A common way is to first measure the bitmap without loading it via a
flag passed to the
BitmapFactory
.
// instruct BitmapFactory to only the bounds and type of the image
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
// get width and height
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
// type of the image
String imageType = options.outMimeType;
Afterwards you can load the scaled version of the image. Android is really good in scaling images by a power of two. You can use the following method (from the official Android documentation) to calculate the scale factor on a basis of 2.
public static Bitmap decodeBitmapWithGiveSizeFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
This method can be used to assign the image to a view as demonstrated in the following example.
viewWidth = imageView.getWidth();
viewHeight = imageView.getHeight();
imageView.
imageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, viewWidth, viewHeight));
3. Using caches
3.1. Using a cache
A cache allows reusing objects which are expensive to create. If you load on object into memory, you can think of this as a cache for the object. For example, if you downloading images from the Internet to display them in a list you should hold them in memory to avoid that you download them several times.
At some point you need to recycle some of your objects, otherwise you run out of memory. A good approach to do this, is to recycle the objects which have not been used the longest in your application.
The Android platform provides the
LruCache
class, as of API 12 (or in the support-v4 library). The
LruCache
class provides a
_least recently used cache _
(LRU cache)
cache implementation.
A LRU cache
keeps track of the usage of its
members. It has a given size and if this
size is
exceeded, it removes
the
items
which have
not
be accessed the longest. This behavior is
depicted in the
following
graphic.
The following example code demonstrates a possible implementation of the LruCache
class for caching images.
public class ImageCache extends LruCache<String, Bitmap> {
public ImageCache( int maxSize ) {
super( maxSize );
}
@Override
protected int sizeOf( String key, Bitmap value ) {
return value.getByteCount();
}
@Override
protected void entryRemoved( boolean evicted, String key, Bitmap oldValue, Bitmap newValue ) {
oldValue.recycle();
}
}
Its usage is simple and demonstrated by the following example code.
LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>();
For determining the initial size of the cache, it is good practice to determine the size based on the total memory available on the device.
For determining the available memory you can the MemoryClass
.
This is demonstrated by the following code.
int memClass = ( ( ActivityManager) activity.getSystemService( Context.ACTIVITY_SERVICE ) ).getMemoryClass();
int cacheSize = 1024 * 1024 * memClass / 8;
LruCache cache = new LruCache<String, Bitmap>( cacheSize );
3.2. Cleaning up your cache
As of API 14 you can override the onTrimMemory()
method in Android components.
This method is called by the Android system asking you to cleanup your memory in case the Android system requires resources for foreground processes.
4. Links and Literature
Nothing listed.
4.1. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting