AsyncLayoutInflator Pitfalls AsyncLayoutInflator Pitfalls multithreading multithreading

AsyncLayoutInflator Pitfalls


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