Issue
AsyncTask is a standard way to perform long running operations asynchronously on a background thread without holding up the UI thread. One should not perform any UI interactions from the doInBackground() method.
My question: What are examples of UI interactions that are forbidden? Would it be any of the following:
- LayoutInflater.inflate()
- View.findViewById()
- TextView.setText()
I'm inclined to say yes, but we have some code right now that does all of these (and more) and is called from the doInBackground() method, and yet the code is working. I've seen other people indicate they receive an exception when attempting to perform UI activity from doInBackground(), but that is not our experience.
Our code is generating an on-screen report that is not visible until the entire operation is complete. On rare occasion (hard to reproduce) when attempting to cancel the operation very quickly, we will see the application get into a weird state, but it doesn't crash.
Before changing our code in hopes of finding this rare condition, I wanted to see if anyone had some thoughts on why our code is "working" as-is.
The only other tidbit of information that might be helpful is that our doInBackground method has the following code template:
protected Boolean doInBackground(Void... voids) {
if (null == Looper.myLooper()) {
Looper.prepare();
}
publishProgress(0.0);
// Perform ui/non-ui logic here
Looper myLooper = Looper.myLooper();
if (null != myLooper && Looper.getMainLooper() != myLooper) {
myLooper.quit();
}
return true;
}
The Looper is needed for some of the report generating code (omitted) that uses a new Handler() to generate data. I'm not sure if creating the Looper is somehow making our ui interactions legal.
(I do have a stack trace that clearly shows our UI activity being called from doInBackground, in case you thought we might be spinning off some separate threads to update our UI)
Solution
AsyncTask
is not meant for really long running work, it should complete within a few seconds. It is a one-shot completely managed thread context, which should not have its own Looper
attached to it. That actually will break the backing AsyncTask
functionality - starving off other future AsyncTask
operations you may be starting. If you have something which requires a Looper
, you should be using your own Thread
or ThreadPool
rather than an AsyncTask
. You'll also want to make sure you retain a reference to your AsyncTask
so it can be cancelled appropriately - this is a source of many memory leaks and/or exceptions due to invalid state when onPostExecute()
is called.
The intent of the publishProgress()
method is to give your app the ability to get updates it can reflect on the UX. You are correct, setText()
, etc. should not be run in the doInBackground()
callback. That callback is executed in arbitrary thread context in which you do not control and cannot make UI updates.
You may be able to use inflateLayout()
and findViewById()
, but this is not a good practice to do this outside of initialization as these are potentially expensive operations. Inflation has to parse the binary layout and create view objects on the fly. Finding by ID walks the entire view hierarchy to find the component you desire. A better practice would be to cache these at creation (for an Activity
or Fragment
) or when creating a view as part of an adapter (such as a ViewHolder
in RecyclerView
.
Answered By - Larry Schiefer
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.