I just found out that some of my code is (unindentionally) running in a worker thread and reads some data from UI elements:
e.g. checkbox.isChecked(), textView.getText()
and to my surprise, this works just fine...
I expected that it would crash with an exception (like the following exception that I get when I want to write to UI elements):
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
According to the Android docs:
Only objects running on the UI thread have access to other objects on
that thread. Because tasks that you run on a thread from a thread pool
aren't running on your UI thread, they don't have access to UI
objects.
So, is it really okay to read data from UI elements from other threads?
If not: why is there no exception?
is it really okay to read data from UI elements from other threads?
No, but not for the reasons that you may be thinking.
First, as soon as you fork the background thread, the user might press BACK or otherwise do something that destroys your activity. It is not safe to try to use widgets of a destroyed activity.
Second, there is little to no thread synchronization logic in the View class or its subclasses. The main application thread may be modifying the data at the same time that you are trying to use it, resulting in possible concurrency issues.
I would pass the data from the widgets into the thread (e.g., thread constructor).
why is there no exception?
AFAIK, that specific exception is only thrown on setters or other things that modify the contents of the widget. This does not mean that any code that does not raise that exception is guaranteed to be safe.
You can't redraw (invalidate) your Views outside main thread which is also UI thread. Setting text for TextView also causes redrawing view ofc... But getting text just returns String (or Editable?), so its working...
Note that there are some other restrictions and limitations, e.g. when you pass EditText to some AsyncTask, but while it's working the main Activity (holding passed view) finish then you might get NPE, because view is also gone... In this and similar cases WeakReferences are very usefull
Related
I have a fragment that takes too long to load due to its OnCreateView() function configuring about 40 textboxes. Is there a way to put this in a background thread and display a progress indicator circle while it loads?
I have tried using AsyncTask, but when the OnCreateView() is called a second time, I get an error stating that I must use the original thread. Also the device is frozen and will not display any indicator while loading through AsyncTask.
You can't do the operation about the UI in the other thread. The official document said:
By design, Android View objects are not thread-safe. An app is expected to create, use, and destroy UI objects, all on the main thread. If you try to modify or even reference a UI object in a thread other than the main thread, the result can be exceptions, silent failures, crashes, and other undefined misbehavior.
So you should try to optimize the code about the fragment's OnCreateView() method instead of trying run it in the background thread. All the UI operation should be excuted in the main thread.
For more information, you can check the official document about the etter performance through threading.
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)
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.
The following code in an AsycnTask:
#Override
protected Boolean doInBackground(View... params) {
try{
Drawable drawPhoto = DataDatero.ImageDownload(taskPhotoName);
((ImageView)params[0]).setImageDrawable(drawPhoto);
((TextView)params[1]).setText(taskItemListText);
((TextView)params[2]).setTextColor(taskColore);
((TextView)params[2]).setText(taskItemStockText);
[...]
}
Throws a CalledFromWrongThreadException , describing that:
Only the original thread that created a view hierarchy can touch its
views
This has been discussed in plenty of questions: example , another example ; and all reach the same conclusion.
But what I'm not only getting the Exception..
I call that execute from a getView in a List adapter, and the images (params[0]) are updated in the view while the exception is thrown for the text. Also, if I leave only the textview updates , the text is updated and no exception is thrown.
If I update the TextView first and the ImageView after, some text and images are updated while some images are not (that imageDownload includes a 2-3 sec network operation)
Why some of them are updated and others are not?
Notes: this was tested only with sdk 4.0, v4 support, on api 16 emulation.
I fixed it and I'm not touching the views in doInBackground
The second example is something similar... is it that the operation gets validated if onCreate is not finished?
I have come across a similar issue and asked a question here (self answered after a good bit of digging).
Essentially what it boils down to is that, contrary to what everybody thinks, you can modify UI elements from an AsyncTask execute() if those views haven't gone through a layout traversal yet. This happens asynchronously to the main flow of execution (activity lifecycle methods/callbacks), so if the View in question is created shortly before execute() is called, you can access them (meaning, the exception isn't thrown, it's of course still really bad practice and not advisable). Because execute() happens on another thread, the layout traversals (which run on the UI thread) may finish while your execute() is running, which explains why only some views may be modified and others throw the exception. It also explains why "leaving only the textview updates" (and presumably removing the ImageView updates) results in those updates "magically" working too. Since this is a timing related issue, it depends on many factors, among other things how long Drawable drawPhoto = DataDatero.ImageDownload(taskPhotoName); takes to run.
PS: I realise this is a late answer, but I think this can be useful for somebody finding this answer first, there aren't many posts dealing with issues like this.
The exception is clear enough. You can not update UI element from a thread different from the UI Thread. doInBackground executes code in a different thread
Why cant you pass the information to update the UI to the onPostExecute method? This is where the UI is intended to be updated.
When you run the execute method of your task, the doInBackground method is executed in a background thread.
And you are not allowed to modify UI from a background thread.
So, don't modify the UI in the doInBackground method.
You should do this UI stuff in onPostExecute method instead, which is guaranteed to be executed in UI thread.
I have some worker thread that gets an address using the geocoder, and when its done, i want to show the result on the application thread's TextView, using setText() from the worker thread leads to an exception, so what is the best practice to do that?
Android UI Views must not be touched from external threads, Any code that calls Methods on Views must run in UI thread only. You Should use AsyncTask . It provides useful callback methods to update UI from task running in a separate thread.
It's hard to say without seeing your code or knowing what exception:
but check if:
your textView object is linked with the one in your layout (findViewById(R.id.textView1))
is it visible in you layout?(sometimes when dimensions of elements dont't add up, views get pushed off screen)
Is it really a String you're trying to set? (not an int or so?)
Try this inside your thread where you want to set text:
// you should finalize your text before using it in another thread,
// else the IDE would show an error
final String text = yourTextToBeSet;
runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText(text);
}
});
The exception you get when you call setText() on non-UI thread is because you can't call anything related to UI when you are on a non-UI thread. You can, as above, easily call runOnUiThread(Runnable) to divert code execution on UI thread.
Keep in mind that this solution is not good enough if your code is not as simple as that, which in such situations, using AsyncTask is recommended.
I have seen some discussion here on Stack Overflow related to using Activity.onRetainNonConfigurationInstance() to maintain a background thread started by one instance of an Activity and pass it to the next instance of the Activity which results, for example, when the phone's orientation changes from portrait to landscape.
The discussions do not specify exactly what can be done with the thread wrapped in the Object returned from onRetainNonConfigurationInstance().
For example:
1. Is there a way to actually keep the background thread running using this technique?
2. Do you need to somehow pause the thread when the previous instance of Activity is going away and then restart it again in the new instance?
Can anyone provide a short example?
Any details would be appreciated.
You can return anything you want to onRetainNonConfigurationInstance(). If you have a Thread that you want passed from one instance of the Activity to another, you can either return it directly, or put it inside another object that you return from onRetainNonConfigurationInstance(). You don't need to pause the thread or interact with it in any way. It just keeps running as if nothing happened.
The only thing you need to be concerned about is how the Thread interacts with the Activity (if at all). If the thread will call the Activity back (to indicate progress or something like that) then you somehow need to give the thread a reference to the new Activity, as the old Activity will be dead.
What do you want to do in your background thread?
EDIT (add more details about threads/activities):
Threads have their own lifetimes which are completely disconnected from Activities. If you create a Thread in an Activity and start it, it will run to completion no matter what your Activity does. The only thing that will stop the thread explicitly is if Android decides to kill your process (which it may do if your process contains no active activities).
The thread will continue to run. For an example of what you can do with this, you can check out the android Ignition project and its IgnitedAsyncTask (and related examples).
The idea is that you will maintain a reference to your thread (usually an AsyncTask) somewhere in your Activity, and occasionally your thread (again, especially if it's an AsyncTask) will require a reference to a Context in order to perform some kind of UI update upon the conclusion of its background task. You will need to make sure that the Context (and anything derived from it--like a TextView or the like) to which your thread has a reference is non-null, or else it will crash.
You might use getLastNonConfigurationInstance() to set your Activity's reference to the thread, and then call a setter on the thread to set its Context reference (to avoid any related null pointer crash).