Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AsyncLayoutInflator Pitfalls

Android has added a new AsyncLayoutInflater class to their support library ver 24.0 and higher and can be used in Android SDK 4.0 or higher (which is almost all devices available).
As per Android’s documentation;

Helper class for inflating layouts asynchronously. To use, construct an instance of AsyncLayoutInflater on the UI thread and call inflate(int, ViewGroup, OnInflateFinishedListener). The AsyncLayoutInflater.OnInflateFinishedListener will be invoked on the UI thread when the inflate request has been completed.

This is intended for parts of the UI that are created lazily or in response to user interactions. This allows the UI thread to continue to be responsive & animate while the relatively heavy inflate is being performed.

I actually find this useful, as you may or may have not realized, inflating a complex view is an expensive action timewise, sometimes taking 100’s of milliseconds. Thus when it’s inflated on the Main Thread, the UI may become sluggish.

Another benefit I found, is the quicker load time for Activities, as the UI thread can continue with the next step in loading, while the view is being inflated separately. I have sometimes been able to significantly speed up App launch time using this method.

That being said, there are pitfalls that can happen when using a separate thread to inflate views. We will explain them in the answer.

like image 279
lionscribe Avatar asked Jul 14 '17 17:07

lionscribe


1 Answers

As per Android documentation;

For a layout to be inflated asynchronously it needs to have a parent whose generateLayoutParams(AttributeSet) is thread-safe and all the Views being constructed as part of inflation must not create any Handlers or otherwise call myLooper(). If the layout that is trying to be inflated cannot be constructed asynchronously for whatever reason, AsyncLayoutInflater will automatically fall back to inflating on the UI thread.

This is something you have to watch out for. Though your application will not crash, as AsyncLayoutInflater will automatically fall back to inflating on the UI thread, it will now take double the time to inflate, first on the separate thread, and then on the UI Thread. So watch the LogCat carefully, and see if AsyncLayoutInflater complains that it is inflating on UI thread, and deal with it.

You may have to test on multiple devices and OS versions, for it may be possible (not confirmed), that the same View creates a handler in some devices, and does not in others. The good news is that you don’t have to be perfect, as in worst case, it will still not crash, as AsyncLayoutInflater deals with it.

Very often,the issue is a single custom view that is creating a Handler. A very simple workaround to this, is to replace that view with a ViewStub, and then simply inflate it when the OnInflateFinishedListener callback is called. For example:

<ViewStub android:id="@+id/stub"
android:inflatedId="@+id/subTree"
android:layout="@layout/mySubTree"
android:layout_width="120dip"
android:layout_height="40dip" />

And then in the callback

 ViewStub stub = findViewById(R.id.stub);
 View inflated = stub.inflate();


There is another dangerous pitiful, which costed me many hours of debugging. It has to do with the Android Activity and Fragment Lifecycles. As we know, Android can kill an Activity at any time when it’s not in the foreground. When that happens, Android will recreate the Activity when it needs to be displayed. It uses the savedInstance bundle to restore the Activity and Fragments to their previous state.

But there is a big difference between Activities and Fragments. For Activities, the OS will NOT recreate the views, the app has to recreate them. Once they are recreated, then the OS restores the state of each view with the call of onRestoreState.
But by Fragments, the OS actually recreates the entire Fragment View, and the app only has to initialize class members.

This causes a serious issue when using the AsyncLayoutInflater, for when the Activity is created the first time, though the onCreate function finishes before the View is inflated (as it is being inflated on separate thread), we still don’t create and attach any fragments, until the OnInflateFinishedListener callback is called. Thus when the Fragment is created, we already have a full working Activity with a working view.

But when the Activity is recreated by the OS, as soon as the Activity onCreate function finishes, which is before the view is inflated (as it is being inflated in separate thread), the OS figures it’s time to recreate the Fragment. It will then call onCreateView and even onActivityCreated, even though the Activity hasn’t yet had it’s view inflated and setup. This will cause many crashes, and a non visible fragment!

The solution for this, is to check in the onCreate function of the Activity, if it is being recreated, by checking if the savedBundle passed is not null, and not using an AsyncLayoutInflater in that case. To do this, you will assign the AsyncLayoutInflater.OnInflateFinishedListener instance to a variable, and call it directly after inflating the View. This will cause a slight complexity in the code, but it is not much. It will look something like this;

public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    final AsyncLayoutInflater.OnInflateFinishedListener callback = new AsyncLayoutInflater.OnInflateFinishedListener()
    {
        @Override
        public void onInflateFinished(View view, int resid, ViewGroup parent)
        {
            // setup here
        }
    };
    if (savedInstanceState == null) {
        AsyncLayoutInflater inflater = new AsyncLayoutInflater(this);
        inflater.inflate(R.layout.main, null, callback);
    } else {
        View view = getLayoutInflater().inflate(R.layout.main, null);
        Callback.onInflateFinished(view, R.layout.main, null)
    }
}

Well that’s it for now. If you have any suggestions or comments, please feel free to comment below. Good Luck, Lionscribe

like image 153
lionscribe Avatar answered Nov 18 '22 16:11

lionscribe