Android App Performance Pattern

1. Avoid Creating Unnecessary Objects. Creating unnecessary object trigger Garbage collection more frequently.

For example, following code statements create new string instance every time.

String value = new String("Hello World");

following code statements use single string instance rather than create a new instance every time.

String value = "Hello World";

2. Bitmap takes more memory to load. instead of loading a full bitmap load smaller version of the bitmap into memory.

For example, an image with resolution 2048×1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512×384. Read More

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;
}
public static Bitmap decodeSampledBitmapFromResource(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);
}
// write the following code for set bitmap to image view
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

3. Use Enhanced For Loop. Enhanced for loop is used for collections that implement an iterable interface.

For Example, Below are three methods zero, one and two. zero is slowest because it is getting array length every iteration of a loop.

zero is slowest because it is getting array length every iteration of a loop.

one is faster than zero but slower than two because it pulls every everything out into local variables.

two is fastest.

int[] mArray = new int[]{1,2,3,4,5};

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
        sum += mArray[i];
    }
}

public void one() {
    int sum = 0;
    int[] localArray = mArray;
    int len = localArray.length;

    for (int i = 0; i < len; ++i) {
        sum += localArray[i];
    }
}

public void two() {
    int sum = 0;
    for (int i : mArray) {
        sum += i;
    }
}

4. Use Primitive type and avoid the use of Wrapper class if it is possible. Wrapper classes are slower than primitives because of wrapper classes store information about a complete class.

Integer sum = 0;
for (int index = 0; index < 10; index++) {
    sum+=index;
}
// it is faster tnen wraper Integer
int sum = 0;
for (int index = 0; index < 10; index++) {
    sum+=index;
}

5. Avoid nested weight in layout nested weights are bad for performance. Nesting several instances of LinearLayout that use the layout_weight parameter can be especially expensive as each child needs to be measured twice. If your layout hierarchy is too complex then it will slow down your application performance.

Don’t Do this.

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="horizontal">

    <ImageView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:src="@mipmap/ic_launcher_round" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:text="Title" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:text="Date" />
    </LinearLayout>

</LinearLayout>

Do this way,

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginRight="12dp"
        android:src="@mipmap/ic_launcher_round" />

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/imageView"
        android:gravity="center_vertical"
        android:padding="6dp"
        android:text="Title" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tvTitle"
        android:layout_toRightOf="@id/imageView"
        android:gravity="center_vertical"
        android:padding="6dp"
        android:text="Date" />

</RelativeLayout>

7. Reusing layouts with <include> tag. It allows you create complex reusable layouts. for example, if you create a custom dialog layout for different types of purpose and all the layout have same Yes or No button then you can create Yes or No button layout and include it in Where you need. Or if you have multiple Activities that have the same AppBarLayout then you can include it in activity design file.

Create a separate file for AppBarLayout  appbar_layout.xml.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/AppTheme.AppBarOverlay">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/AppTheme.PopupOverlay"/>

</android.support.design.widget.AppBarLayout>

Now include this layout where you need ToolBar.

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/appbar_layout" />

    <include layout="@layout/content_main" />

</android.support.design.widget.CoordinatorLayout>

8. User <merge> tag to remove redundant view group in your view hierarchy when including one layout within another.

inner_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="One" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Two" />

</merge>

item_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <include layout="@layout/inner_layout" />

</LinearLayout>

9. Use RecyclerView instead of ListView or GridView. and if list item contains images then load images on a background thread or use image loading library Glide or Picasso. Also, cancel image loading request when a view is detached from a window.

User for List View

rvDeviceList.setLayoutManager(new LinearLayoutManager(getContext()));

User for Grid View with 2 columns

rvDeviceList.setLayoutManager(new GridLayoutManager(getContext(), 2));
public class MyListAdapter extends RecyclerView.Adapter<MyListAdapter.ViewHolder> {

    private Context context;
    private ArrayList<ModelClass> mArrayList = new ArrayList<>();

    public MyListAdapter(Context context) {
        this.context = context;
    }

    public void addItem(ModelClass doctor) {
        int pos = mArrayList.size();
        mArrayList.add(doctor);
        notifyItemInserted(pos);
    }

    public void setItems(ArrayList<ModelClass> doctorArrayList) {
        this.mArrayList.clear();
        this.mArrayList.addAll(doctorArrayList);
        notifyDataSetChanged();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
            Picasso.with(context)
                    .load("Image path")
                    .into(holder.ivUserAvatar);
    }

    @Override
    public void onViewDetachedFromWindow(ViewHolder holder) {
        super.onViewDetachedFromWindow(holder);
        Picasso.with(context).cancelRequest(holder.ivUserAvatar);
    }

    @Override
    public int getItemCount() {
        return mArrayList.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        private ImageView ivUserAvatar;
        public ViewHolder(View itemView) {
            super(itemView);
            ivUserAvatar = itemView.findViewById(R.id.ivUserAvatar);
        }
    }
}

 

10. Avoid object creation in onDraw() method because onDraw() method is called every time view is invalidated so that it creates every time new object and allocate memory.

Don’t Do this,

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Rect rect = new Rect(10, 10, 100, 50);
    canvas.drawRect(rect, paint);
}

Do this way,

public class MyView extends View {

    private Paint paint;
    private Rect rect;

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        rect = new Rect();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        rect.set(10, 10, 100, 50);
        canvas.drawRect(rect, paint);
    }
}

11. Do not use context in Singleton class or if it is required then use Application Context. If the reference of context is Activity and activity destroy then it will create context leak.

public class MySingleton {

private static MySingleton instance;
private Context mContext;

public MySingleton(Context context) {
this.mContext = context;
}

public static synchronized MySingleton getInstance%

Want to work with us? We're hiring!