Modifying views in AsyncTask doInBackground() does not (always) throw exception - android

I just came across some unexpected behaviour when playing around with some sample code.
As "everybody knows" you cannot modify UI elements from another thread, e.g. the doInBackground() of an AsyncTask.
For example:
public class MainActivity extends Activity {
private TextView tv;
public class MyAsyncTask extends AsyncTask<TextView, Void, Void> {
#Override
protected Void doInBackground(TextView... params) {
params[0].setText("Boom!");
return null;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
tv = new TextView(this);
tv.setText("Hello world!");
Button button = new Button(this);
button.setText("Click!");
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new MyAsyncTask().execute(tv);
}
});
layout.addView(tv);
layout.addView(button);
setContentView(layout);
}
}
If you run this, and click the button, you're app will stop as expected and you'll find the following stack trace in logcat:
11:21:36.630: E/AndroidRuntime(23922): FATAL EXCEPTION: AsyncTask #1
...
11:21:36.630: E/AndroidRuntime(23922): java.lang.RuntimeException: An error occured while executing doInBackground()
...
11:21:36.630: E/AndroidRuntime(23922): Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
11:21:36.630: E/AndroidRuntime(23922): at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
So far so good.
Now I changed the onCreate() to execute the AsyncTask immediately, and not wait for the button click.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// same as above...
new MyAsyncTask().execute(tv);
}
The app doesn't close, nothing in the logs, TextView now displays "Boom!" on the screen. Wow. Wasn't expecting that.
Maybe too early in the Activity lifecycle? Let's move the execute to onResume().
#Override
protected void onResume() {
super.onResume();
new MyAsyncTask().execute(tv);
}
Same behaviour as above.
Ok, let's stick it on a Handler.
#Override
protected void onResume() {
super.onResume();
Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
new MyAsyncTask().execute(tv);
}
});
}
Same behaviour again. I'm running out of ideas and try postDelayed() with a 1 second delay:
#Override
protected void onResume() {
super.onResume();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
new MyAsyncTask().execute(tv);
}
}, 1000);
}
Finally! The expected exception:
11:21:36.630: E/AndroidRuntime(23922): Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Wow, this is timing related?
I try different delays and it appears that for this particular test run, on this particular device (Nexus 4, running 5.1) the magic number is 60ms, i.e. sometimes is throws the exception, sometimes it updates the TextView as if nothing had happened.
I'm assuming this happens when the view hierarchy has not been fully created at the point where it is modified by the AsyncTask. Is this correct? Is there a better explanation for it? Is there a callback on Activity that can be used to make sure the view hierachy has been fully created? Timing related issues are scary.
I found a similar question here Altering UI thread's Views in AsyncTask in doInBackground, CalledFromWrongThreadException not always thrown but there is no explanation.
Update:
Due to a request in comments and a proposed answer, I have added some debug logging to ascertain the chain of events...
public class MainActivity extends Activity {
private TextView tv;
public class MyAsyncTask extends AsyncTask<TextView, Void, Void> {
#Override
protected Void doInBackground(TextView... params) {
Log.d("MyAsyncTask", "before setText");
params[0].setText("Boom!");
Log.d("MyAsyncTask", "after setText");
return null;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout(this);
tv = new TextView(this);
tv.setText("Hello world!");
layout.addView(tv);
Log.d("MainActivity", "before setContentView");
setContentView(layout);
Log.d("MainActivity", "after setContentView, before execute");
new MyAsyncTask().execute(tv);
Log.d("MainActivity", "after execute");
}
}
Output:
10:01:33.126: D/MainActivity(18386): before setContentView
10:01:33.137: D/MainActivity(18386): after setContentView, before execute
10:01:33.148: D/MainActivity(18386): after execute
10:01:33.153: D/MyAsyncTask(18386): before setText
10:01:33.153: D/MyAsyncTask(18386): after setText
Everything as expected, nothing unusual here, setContentView() completed before execute() is called, which in turn completes before setText() is called from doInBackground(). So that's not it.
Update:
Another example:
public class MainActivity extends Activity {
private LinearLayout layout;
private TextView tv;
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
tv.setText("Boom!");
return null;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
layout = new LinearLayout(this);
Button button = new Button(this);
button.setText("Click!");
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tv = new TextView(MainActivity5.this);
tv.setText("Hello world!");
layout.addView(tv);
new MyAsyncTask().execute();
}
});
layout.addView(button);
setContentView(layout);
}
}
This time, I'm adding the TextView in the onClick() of the Button immediately before calling execute() on the AsyncTask. At this stage the initial Layout (without the TextView) has been displayed properly (i.e. I can see the button and click it). Again, no exception thrown.
And the counter example, if I add Thread.sleep(100); into the execute() before setText() in doInBackground() the usual exception is thrown.
One other thing I have just noticed now is, that just before the exception is thrown, the text of the TextView is actually updated and it displays properly, for just a split second, until the app closes automatically.
I guess something must be happening (asynchronously, i.e. detached from any lifecycle methods/callbacks) to my TextView that somehow "attaches" it to ViewRootImpl, which makes the latter throw the exception. Does anybody have an explanation or pointers to further documentation about what that "something" is?

The checkThread() method of ViewRootImpl.java is responsible for throwing this exception.
This check is suppressed using member mHandlingLayoutInLayoutRequest until performLayout() i.e all the initial drawing traversals are complete.
hence it throws exception only if we use delay.
Not sure if this is a bug in android or intentional :)

Based on RocketRandom's answer I've done some more digging and came up with a more comprehensive answer, which I feel is warranted here.
Responsible for the eventual exception is indeed ViewRootImpl.checkThread() which is called when performLayout() is called. performLayout() travels up the view hierarchy until it eventually ends up in ViewRootImpl, but it originates in TextView.checkForRelayout(), which is called by setText(). So far so good. So why does the exception sometimes not get thrown when we call setText()?
TextView.checkForRelayout() is only called if the TextView already has a Layout (mLayout != null). (This check is what inhibits the exception from being thrown in this case, not mHandlingLayoutInLayoutRequest in ViewRootImpl.)
So, again, why does the TextView sometimes not have a Layout? Or better, since obviously it starts out not having one, when and where does it get it from?
When the TextView is initially added to the LinearLayout using layout.addView(tv);, again, a chain of requestLayout() is called, travelling up the View hierarchy, ending up in ViewRootImpl, where this time, no exception is thrown, because we're still on the UI thread. Here, ViewRootImpl then calls scheduleTraversals().
The important part here is that this posts a callback/Runnable onto the Choreographer message queues, which is processed "asynchronously" to the main flow of execution:
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
The Choreographer will eventually process this using a Handler and run whatever Runnable ViewRootImpl has posted here, which will eventually call performTraversals(), measureHierarchy(), and performMeasure() (on ViewRootImpl), which will perform a further series of View.measure(), onMeasure() calls (and a few others), travelling down the View hierarchy until it finally reaches our TextView.onMeasure(), which calls makeNewLayout(), which calls makeSingleLayout(), which finally sets our mLayout member variable:
mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
effectiveEllipsize, effectiveEllipsize == mEllipsize);
After this happens, mLayout isn't null any more, and any attempt to modify the TextView, i.e. calling setText() as in our example, will lead to the well known CalledFromWrongThreadException.
So what we have here is a nice little race condition, if our AsyncTask can get its hands on the TextView before the Choreographer traversals are complete, it can modify it without penalties. Of course this is still bad practice, and shouldn't be done (there are many other SO posts dealing with this), but if this is done accidentally or unknowingly, the CalledFromWrongThreadException is not a perfect protection.
This contrived example uses a TextView and the details may vary for other views, but the general principle remains the same. It remains to be seen if some other View implementation (perhaps a custom one) that doesn't call requestLayout() in every case may be modified without penalties, which might lead to bigger (hidden) issues.

You can write in doInBackground to a TextView if it is not part of the GUI yet.
It is only part of the GUI after statement setContentView(layout);.
Just my thought.

Related

Thread is able to change ui if started from inside activity lifecycle methods. why?

I was testing this code to check if app crashes for changing ui component from background thread. but it didn't.
Here in the code added below. I started a new thread in onCreate() method of MainActivity and It should have crashed as per the android docs which says
In the class, the Runnable.run() method contains the code that's
executed. Usually, anything is allowable in a Runnable. Remember,
though, that the Runnable won't be running on the UI thread, so it
can't directly modify UI objects such as View objects.
So I was expecting it to crash. Which it didn't. See code -
public class MainActivity extends AppCompatActivity {
TextView txt;
Thread thread;
Runnable runnable = new Runnable() {
#Override
public void run() {
txt.setText("bro");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt = (TextView) findViewById(R.id.name_txt);
thread = new Thread(runnable);
thread.start();
}
}
While if i try changing ui while starting thread from onClicklistener() as below it does crash. which is expected.
public class MainActivity extends AppCompatActivity {
TextView txt;
Thread thread;
Runnable runnable = new Runnable() {
#Override
public void run() {
txt.setText("bro");
}
};
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
thread.start();
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt = (TextView) findViewById(R.id.name_txt);
thread = new Thread(runnable);
txt.setOnClickListener(listener);
}
}
Now, that the second code snippet crashes, which is expected and the first one doesn't.
Please explain why is this happening, as I'm creating a new worker thread each time but just at different places. Official docs reference will be appreciated.
I found the reason behind this behavior as pointed out by #krish in the comments on my question. The reason is that, the thread was able to make changes in the TextView object only till it was not visible on UI screen i.e not rendered. It is only after the view rendering, that any thread except the Main thread may not make changes to any UI components. I Tried using view observer to see if the view was rendered before the changes or not. which showed that changes were made before the view rendering.
Here is the code that i tried.
public class MainActivity extends AppCompatActivity {
TextView txt;
Thread thread;
Runnable runnable = new Runnable() {
#Override
public void run() {
txt.setText("bro");
Log.d("ThreadTest", "The Text was changed.");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt = (TextView) findViewById(R.id.name_txt);
thread = new Thread(runnable);
txt.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Log.d("ThreadTest", "The TextView was rendered");
}
});
}
#Override
protected void onResume() {
super.onResume();
thread.start();
}
}
Using the code above. You'll see in the output:
The Text was changed.
The TextView was rendered
Which means text was changed before view rendering. if you try to start thread to makes changes in onGlobalLayout method. App crashes as it should.
The UI is not thread-safe see processes-and-threads, so you were just lucky you did not hit one of the many landmines waiting for you.
If you don't like relying on luck then:
You should be using:
runOnUiThread(runnable);
instead of:
thread = new Thread(runnable);
AsyncTask enables proper and easy use of the UI thread.

Understanding the UI thread

I am a beginner to Android and I have some confusions regarding Android UI Thread. Now, I know that no thread apart from the one that created the UI can modify it.
Great.
Here is the Activity from my first Android app which slightly confuses me.
public class NasaDailyImage extends Activity{
public ProgressDialog modalDialog = null;
//------------------------------------------------------------------------------
#Override
protected void onCreate(Bundle savedInstanceState){
//Instantiate progress dialog, skipping details.
Button b = //get reference to button
b.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
modalDialog.show(); // show modal
Toast.makeText(getApplicationContext(), "Getting feeds", 500).show();
new AsyncRetriever().execute(new IotdHandler()); // Get the feeds !!
}
});
}
//------------------------------------------------------------------------------
public synchronized void resetDisplay(boolean parseErrorOccured,
boolean imageErrorOccured,
IotdHandler newFeeds){
if(parseErrorOccured || imageErrorOccured){
// make a Toast
// do not update display
}else{
// make a Toast
// update display
// based on new feed
}
}
//------------------------------------------------------------------------------
class AsyncRetriever extends AsyncTask<IotdHandler,Void,IotdHandler>{
#Override
protected IotdHandler doInBackground(IotdHandler... arg0) {
IotdHandler handler = arg0[0];
handler.processFeed(); // get the RSS feed data !
return handler;
}
//------------------------------------------------------------------------------
#Override
protected void onPostExecute(IotdHandler fromInBackground){
resetDisplay( // call to update the display
fromInBackground.errorOccured,
fromInBackground.imageError,
fromInBackground);
}
//------------------------------------------------------------------------------
}
1. onCreate is on the UI thread so I can do whatever I want but onClick is not. Why can I make a ProgressDialog and a Toast in that method? Why no error there?
2. The AsyncTask is subclass of the the NasaDailyImage. This means it can access all the methods of NasaDailyImage including resetDisplay() which updates the display. resetDisplay() is called in the onPostExecute which runs on a different thread from UI. So, why can I update the display there and yet get no errors ?
onClick() is indeed on the UI thread. Most of what happens in an Activity happens on the UI thread.
onPostExecte() (and its counterpart onPreExecute()) runs on the UI thread as well. The AsyncTask.onPostExecte() documentation clearly states this. AsyncTask was deliberately designed such that developers could update the UI before and after they do background work.
In general, your code will be running on the UI thread unless you explicitly tell it otherwise. Once you create AsyncTasks, Runnables, or Threads, you need to ensure you understand where your code is executing. In an Activity, it is typically safe to assume you are on the UI thread.
You are extending AsyncTask class , where async task class is calling its sequential method automatically. First onPreExecute then doBackground and finally onPost. If you want to change any ui change you can use onProgressUpdate method.
To use your activity class simple call activityclass.this.resetDisplay(). Because inner class scope sometimes failed to integrate except global varible.
Thanks

TextView stops updating after bringing activity back to the front

I'm trying to figure out threading and have this issue where a TextView stops being updated if the app is sent to the back, and then restored.
How can I ensure that the TextView continues to be updated after the app is brought back to the front?
Or...
How do I reconnect the TextView to the handler in my run-nable thread after restarting the activity?
There is a Progress Bar which works just fine, so I'm somewhat confused. I'd appreciate some advice as I think I may be making a simple mistake.
public class ThreadTestActivity extends Activity {
private Handler handler;
private static ProgressBar progress;
private TextView tv;
private int counter = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ThreadTest);
handler = new Handler();
progress = (ProgressBar) findViewById(R.id.progressBar1);
tv = (TextView) findViewById(R.id.myText);
Button but = (Button) findViewById(R.id.Button01);
but.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
thread_fun();
});}
}
private void thread_fun() {
new Thread(new Runnable() {
public void run() {
try {
while (counter < 100) {
counter += 20;
Thread.sleep(2000);
// Update the progress bar and TextView (in 5 chunks of 20, 0 to 100)
// This works perfectly it the app stays in front
handler.post(new Runnable() {
public void run() {
// but after sending to the back (esc) and bringing the activity back to the front
progress.setProgress(counter); //This progres bar maintains its value and updates correctly
tv.setText(String.valueOf(counter)); //This TextView reverts to its default text and does not update
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
When an Activity is backgrounded and then resumed, it goes through the whole lifecycle. This means a new instance is created, and Activity.onCreate is called again. See the diagram here for details. An important thing to note is that Activities go through this lifecycle and are destroyed and re-created also when the orientation is changed. So it's something you kind of have to be aware of and work with or around.
The reason that your ProgressBar is updated when your Activity is backgrounded and resumed is that you've got a static reference in your Activity that is set upon each call to onCreate. This static reference is shared between the first instance of your Activity and the second instance that is created after you resume. Once you start your thread, it will just update whatever ProgressBar this reference points to, so you get the impression that the same ProgressBar is being updated.
In fact, it's not the same object, it's a different instance. That's why the text in your TextView is not updated, because the thread is updating an older instance of TextView, not the one on the screen. You can confirm all this in the debugger, or more simply just by printing out the hash codes of the objects. Try putting this inside your while loop:
Log.d("thread_fun", "threadid="+Thread.currentThread().getId()+
",progressbar="+progress.hashCode()+
",textview="+tv.hashCode());
Notice that the ProgressBar's hash code changes, because the thread is updating a new ProgressBar, but the hash code of the TextView that is being updated does not.
D/thread_fun(28989): thread=3483,progress=1097438768,textview=1097437160
D/thread_fun(28989): thread=3483,progress=1097438768,textview=1097437160
D/thread_fun(28989): thread=3483,progress=1097528568,textview=1097437160
D/thread_fun(28989): thread=3483,progress=1097528568,textview=1097437160
Now, the answer is not to make the TextView also static. This isn't how you should maintain state between the Activity lifecycle changes, because it can lead to memory leaks if you're not careful, and frankly it's also confusing, as you can see from above. Instead, you should override one of the other lifecycle methods, onPause or onStop, to save the state of the ProgressBar and TextView, and probably kill the thread that is updating it. Then, in onCreate, you'll need to pull that state out of the Bundle and restore the ProgressBar and TextView to the state they were when the user navigated away, and also probably restart the thread. The method you probably want to read up on is called Activity.onSaveInstanceState. There are other options too, but this is something that's covered on StackOverflow in a lot of questions and is described well in the Android SDK docs.
I hope that helps! Let me know if this isn't clear!

NullPointerException not being thrown

There are so many things wrong with the following code, but yet no errors and shown/given.
btn3.setText("fud"); should throw an NullPointerException. It doesn't.
I should not be allowed to modify UI elements from a non UI thread. I can and Android doesn't complain about it.
What is going on here?
public class TestApp1Activity extends Activity {
private static final String TAG = TestApp1Activity.class.getSimpleName();
private Button btn1, btn2, btn3;
private ExecutorService threadPool;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn1 = (Button)findViewById(R.id.button1);
btn2 = (Button)findViewById(R.id.button2);
threadPool = Executors.newFixedThreadPool(4);
threadPool.submit(new Runnable() {
#Override
public void run() {
btn3.setText("fud"); // should give a NullPointerException. Doesn't only stops execution.
btn2.setText("From pool"); // shouldn't be able to touch UI comps from non-UI thread
}
});
new Thread(new Runnable() {
#Override
public void run() {
Log.i(TAG, "Before setting text on btn");
btn1.setText("From thread"); // shouldn't be able to touch UI comps from non-UI thread
Log.i(TAG, "After setting text on btn");
// So below actually throws the error I wanted.
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.cox1);
imageView.setImageBitmap(bm);
}
}).start();
}
}
EDIT: Since I'm getting bad answers, let me answer my own questions.
The ExecutorService.submit wraps the Runnable in a try/catch. If any errors are thrown, the are quietly discarded. This is terribly design by the Android team...gezzz
So apparently you can modify some view components from another thread. Just not all of them.
1) The ExecutorService.submit wraps the Runnable in a try/catch. If any errors are thrown, the are quietly discarded. This is terribly design. Never should an error be quietly suppressed.
2) So apparently you can modify some view components from another thread. Just not all of them. The component just sets it's properties and on the next onDraw the component is repainted. You can actually do this with a lot of components, but the Android team says "you could get unexpected results." So avoid it.

forceLayout(), requestLayout()

My reading of the android documentation finds the methods forceLayout() (which is to produce a layout display at the next layout request) and requestLayout() (which is supposed to post an immediate layout request), but I can not get them to behave as advertised. In particular, if I do one set text before a Thread.Sleep and one after, it waits for the Sleep to finish before setting both texts at once, whether or I call the forceLayout() and requestLayout() in between. Please do not respond with a lot of nonsense about how I should not call a Thread.Sleep in the UI thread. If I wrap the Thread.Sleep in a CountDownTimer it works perfectly well (as long as I have the tick time short enough to not interfere with sleep time, and the duration of the timer long enough to permit the Sleep to finish. The following is an example:
int i=0;
TextView tv2;
TextView tv1;
LinearLayout ll;
Button bt;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ll=new LinearLayout(this);
ll.setOrientation(LinearLayout.VERTICAL);
tv1=new TextView(this);
tv2=new TextView(this);
bt=new Button(this);
bt.setText("Press to start");
ll.addView(bt);
ll.addView(tv1);
ll.addView(tv2);
tv2.setText("");
setContentView(ll);
bt.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
tv1.setText("starting sleep");
new CountDownTimer(6000,50){
public void onTick(long msuf)
{if(i==1)
{
try{
Thread.sleep(4000);
tv2.setText("waking up");
}
catch(InterruptedException e){};
}
i++;
}
public void onFinish(){}}.start();
}
});
}
[lots of nonsense about calling sleep() in UI thread]. If i get it right, you mean having something like:
//...inside onTick()
try {
tv2.setText("almost waking up"); // first setText()
Thread.sleep(4000);
tv2.setText("waking up"); // second seText()
}
If you make your main thread sleep, it will just stop processing anything: the current method, the thread loop and the message queue. Once awake again, it will finish executing the method, with the second setText() overriding the first one, and then leave the thread loop continue and do the UI refresh, showing only the second text.
Not requestLayout() nor forceLayout() can actually make the UI refresh immediately, they will both schedule a layout request in the thread loop. I'm not sure, but I think the difference between them is that requestLayout() is called by a view that has changed its size/position in its parent, and forceLayout() is called by a ViewGroup that needs its children to be re-laid out.
Therefore [more nonsense about calling sleep() in UI thread]. For such things calling postDelayed() on a main thread handler is the best solution probably, if you don't want to mess with multithreading.

Categories

Resources