Android App Performance Tips

The main reason of low performance Android app is that it runs GC very frequently.

Simple in one line : The time for which GC is running the actual app is not running.

When the android app runs it allocates so many object on the basis of your code and when the objects that are no longer referred the system call GC when there is memory pressure to deallocate those objects , so if the object allocation and deallocation is occurring on regular basis , the GC is running on regular basis to release memory , So more time the GC is running , your app is not running for that time . So it seems the app is lagging.
The app updates its UI every 16ms(considering 60FPS -> 1000ms/60 = 16.67ms~16ms) for smooth UI rendering . So if the GC is running for that time the app is unable to update UI for that time , leads to skipping of few frames , so it seems that the app is lagging. So the actual reason is that the GC was running or may the work is doing too much in main thread so that the app did not get time to render it’s UI smoothly.
Another reason is that may be the app is doing too much in main thread , so at that time if any method is taking more time than 16ms the app will be unable to update UI , means lag will be there in app for that time.
So , these are the reasons for low performance Android App.
How to optimize it ?
  • Reduce GC running time
  • Do not do much in main thread
Tips to achieve better Android App performance
  • Do not allocate any object if not required
  • Do not allocate object early , allocate object only when it is required
  • Avoid Auto-Boxing as Integer, Boolean, etc takes more memory as classes like Integer takes more memory so use int where ever possible instead of Integer
  • Use concept of object pools to avoid memory churn
  • Do not allocate large amount of unnecessary objects
  • Avoid using enums as a single reference to an enum constant occupy 4 bytes
  • Keep heavy work away from the main thread
  • Use Static Final For Constants
  • Avoid Internal Getters/Setters (direct field access is 3x faster)
  • Don’t leak contexts in inner classes
  • Use static inner classes over non static
  • Use LRU cache for bitmap — avoid redundant decoding of bitmap — reduces GC calling again and again

Tips for Image heavy applications

Image heavy applications have to decode many images, so there will be continuous allocation and deallocation of memory in application. This results in frequent calling of the Garbage Collector (GC). And if you call the GC too many times, your application UI freezes.


Glide , Fresco and Android Networking all use the Bitmap Pool Concept to load images efficient

By using the Bitmap pool to avoid continuous allocation and deallocation of memory in your application, you reduce GC overhead, which results in a smooth-running application.
One way to do this is to use inBitmap (which reuses bitmap memory).
 
Suppose we have to load few bitmaps in an Android application.
When we load bitmapOne, it will allocate the memory for bitmapOne.
Then if when we no longer need bitmapOne, do not recycle the bitmap (as recycling involves calling GC). Instead, use this bitmapOne as an inBitmap for bitmapTwo. This way, the same memory can be reused for bitmapTwo.
Bitmap bitmapOne = BitmapFactory.decodeFile(filePathOne);
imageView.setImageBitmap(bitmapOne);
// lets say , we do not need image bitmapOne now and we have to set // another bitmap in imageViewfinal BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePathTwo, options);
if (canUseForInBitmap(bitmapOne, options)) { 
//canUseForInBitmap check if the image can be reuse or not by //checking the image size and other factors
    options.inMutable = true;
    options.inBitmap = bitmapOne;
}
options.inJustDecodeBounds = false;
Bitmap bitmapTwo = BitmapFactory.decodeFile(filePathTwo, options);
imageView.setImageBitmap(bitmapTwo);

public static boolean canUseForInBitmap(
        Bitmap candidate, BitmapFactory.Options targetOptions) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // From Android 4.4 (KitKat) onward we can re-use if the byte size of
        // the new bitmap is smaller than the reusable bitmap candidate
        // allocation byte count.
        int width = targetOptions.outWidth / targetOptions.inSampleSize;
        int height = targetOptions.outHeight / targetOptions.inSampleSize;
        int byteCount = width * height * getBytesPerPixel(candidate.getConfig());

        try {
            return byteCount <= candidate.getAllocationByteCount();
        } catch (NullPointerException e) {
            return byteCount <= candidate.getHeight() * candidate.getRowBytes();
        }
    }
    // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
    return candidate.getWidth() == targetOptions.outWidth
            && candidate.getHeight() == targetOptions.outHeight
            && targetOptions.inSampleSize == 1;
}
private static int getBytesPerPixel(Bitmap.Config config) {
    if (config == null) {
        config = Bitmap.Config.ARGB_8888;
    }

    int bytesPerPixel;
    switch (config) {
        case ALPHA_8:
            bytesPerPixel = 1;
            break;
        case RGB_565:
        case ARGB_4444:
            bytesPerPixel = 2;
            break;
        case ARGB_8888:
        default:
            bytesPerPixel = 4;
            break;
    }
    return bytesPerPixel;
}
So, we are reusing the memory of bitmapOne while decoding bitmapTwo.
InBitmap works here.
So inBitmap is important here.
Similarly , while implementing it in listview, recyclerview — we will be able to get smooth scrolling. Glide and Fresco load the image smoothly by using this bitmap pool concept.
Without Bitmap Pool, there will be flickering and lagging in UI while scrolling.
In this way, we can avoid continuous allocation and deallocation of memory in application, reduce the GC overhead, and maintain a smooth-running application.
But there’s one problem: there are a few restrictions for using BitMap Pools in versions of Android that are older than Honeycomb. And only few android version older than Kitkat will only allow us to use inSampleSize = 1. But any version newer than KitKat will support BitMap with only a few minor issues.
So, all these types of cases are handled in this library Glide Bitmap Pool, which you can use to optimize your Android application.
As all the cases are handled in this library, you just have to decode images using this library, and recycle these image using this library. There’s no need to handle version-wise cases.