I'm beginner in Android Devloping, I have a trouble with UI in Android
I have a code look like this:
public class MainActivity extends Activity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv1 = (TextView) findViewById(R.id.textView1);
TextView tv2 = (TextView) findViewById(R.id.textView2);
tv1.setVisibility(View.GONE);
tv2.setVisibility(View.GONE);
String s="abc";
MyAsyncTask BkGroundTask = new MyAsyncTask();
BkGroundTask.execute(s);
try {
s = BkGroundTask.get();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();}
tv1.setVisibility(View.VISIBLE);
tv2.setVisibility(View.VISIBLE);
}
}
But tv1 and tv2 do not disappear when AsyncTask is running.
What should I do to fix this?
an asynctask executes asynchronously, you are on the assumption that everything pauses when you call execute which is wrong and would defeat the purpose of an asynctask. in reality it is disappearing but then reappearing. it is happening so fast that you probably cant even notice it.
if you want to textview to display when the task is done you need to do that in the onpostexecute of the task
example
#Override
public void onPostExecute(Void void){
tv1.setVisibility(View.VISIBLE);
tv2.setVisibility(View.VISIBLE);
}
This has been discussed many times but I will explain again for what you want since you want a special scenario...
You almost never want to use .get() because it is a blocking call. This means that it will lock up your UI until the task is finished. .execute(), as you have, is what you want.
If you want them to be gone while the task is running and be visible again when the task finishes then you want to put the code to show them inside of onPostExecute() if it is an inner-class and if not then you can use a callback to the Activity from onPostExecute() and show them in the callback method.
This answer discusses how to use an interface with your AsyncTask
Related Post
Related
I am kind of new in android programming so excuse me for asking this question but I needed help.
I want to change color of a textView in an infinite loop I tried this code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.textView);
while(true){
textView.setTextColor(Color.BLUE);
try{
Thread.sleep(500);
}
catch (InterruptedException e) {
Log.e("InterruptedException", "Thread interrupted", e);
}
textView.setTextColor(Color.RED);
}
}
But the problem is that UI doesn't update itself. I even tried to put my code in onStart() but that didn't help either, may somebody please help me where should I put my code so that UI updates itself in an infinite loop?
Don't you worry about being a beginner it's fine.
I'd recommend you never use while() to manipulate any View until you read about Processes and Threads, you must understand what is UI Thread.
In order to solve your problem:
Use Animations, click here for a great tutorial on that subject - it will help you not only change colors for Views but also doing anything else you'd want.
Later in your quest for knowledge:
You will learn that you can create your own Custom Views by extending View.class and Overriding the onDraw() method, then everything you ever want to draw/color/manipulate is possible for you, click here for more information.
Have fun :)
It looks like your current code is blocking the main thread of the application, which will prevent the UI from properly handling user interaction, view updates, etc.
If you want to make a change on a schedule, you might want to look into posting a Runnable on to the view. So, something like this:
TextView textView;
Runnable changeTextColorRunnable = new Runnable() {
public void run() {
textView.setTextColor(calculateTextColor());
textView.postDelayed(changeTextColorRunnable, 500);
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
textView.post(changeTextColorRunnable);
}
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.
Basically, do I have to put code I want to run on another thread inside doInBackground, or can I call another function/class/whatever-it-is-functions-are-called-in-JAVA within doInBackground and have it run asynchronously? IE: (example code I found online)
protected String doInBackground(String... params) {
for(int i=0;i<5;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
TextView txt = (TextView) findViewById(R.id.output);
txt.setText("Executed");
return null;
}
is how I have seen it done, but can I instead do:
protected String doInBackground(String... params) {
postToServer(x,y,z,h);
}
and have it call a function I already wrote and then have that function run in another thread? Sometimes my HTTP server is a bit slow to respond (it is but a lowly testing server at the moment) and Android automatically pops up the kill process box if my postToServer() call takes more than 5 seconds, and also disables my UI until the postToServer() call finishes. This is a problem because I am developing a GPS tracking app (internally for the company I work for) and the UI option to shut the tracking off freezes until my postToServer() finishes, which sometimes doesn't ever happen. I apologize if this has been answered, I tried searching but haven't found any examples that work the way I'm hoping to make this work.
You can do that, but you will have to move the UI updates to onPostExecute as it is run on the UI thread.
public MyAsyncTask extends AsyncTask<foo, bar, baz> {
...
protected String doInBackground(String... params) {
postToServer(x,y,z,h);
}
protected void onPostExecute(Long result) {
TextView txt = (TextView) findViewById(R.id.output);
txt.setText("Executed");
}
....
}
You may want to pass in the TextView to the constructor of the AsyncTask and store it as a WeakReference.
private final WeakReference textViewReference;
public MyAsyncTask(TextView txt) {
textViewReference = new WeakReference<TextView>(txt);
}
And then in onPostExecute you would make sure that the TextView reference still exists.
protected void onPostExecute(Long result) {
TextView txt = textViewReference.get();
if (txt != null)
txt.setText("Executed");
}
If you want to notify the user that the task is executing I would put that before invoking the AsyncTask.
myTextView.setText("Update in progress...");
new MyAsyncTask().execute();
then in onPostExecute set the TextView to say "Update complete."
Have you tried it the second way?
From what you've posted it seems like it should work fine how you have it in the second example.
However (perhaps unrelated to your question?) in your first example I think it will fail because you are trying to change the UI from a background thread. You'd want to put the parts that manipulate the TextView inside of onPostExecute() rather than doInBackground()
Yes you can, the call to your postToServer method (that's the name in java) will run off the main thread.
Everything inside the doInBackground method of an AsyncTask is run on a pooled thread, but be sure to NOT invoke it directly! Call execute on your asynktask instead, the android framework will do the work for you and run doInBackground on another thread.
try doing something like this:
new AsyncTask<Void, Void, Void>() {
#Override
// this runs on another thread
protected Void doInBackground(Void... params) {
// assuming x, y, z, h are visible here
postToServer(x, y, z, h);
return null;
}
#Override
// this runs on main thread
protected void onPostExecute(Void result) {
TextView txt = (TextView) findViewById(R.id.output);
txt.setText("Executed");
}
}.execute(); // call execute, NOT doInBackGround
Also, notice that every other method of AsyncTask, such as onPostExecute runs on the main thread, so avoid heavy loading them.
Basically The Bottom Line Is the doInBackground() method is Can't interact with The Ui Thread Or The Main thread. that's Why When You are Try To Interact With The TextView in doInBackground () it Will Crash the UI Thread Cuz It's Illegal.
so if anytime You want to Interact with the UI Thread,When You are Working on doInBackground You need to Override
OnPostExecute() //this Function is Called when The doInBackground Function job is Done.
So You can Update The UI Thread Content By this When You're Job is Done In doInBackground () or You are In doInBackground ()
I have an activity class as below.
public class LoginActivity extends Activity implements OnClickListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
button1 = (ImageView) findViewById(R.id.button1);
button1.setOnClickListener(this);
}
#Override
public void onClick(View v) {
loader = (ProgressBar) findViewById(R.id.loader);
Thread processThread = new Thread(loaderThread);
loader.setVisibility(View.VISIBLE);
processThread.start();
try {
Thread.currentThread().join();
Log.i("Activity","gone past join()");
loader.setVisibility(View.INVISIBLE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private Runnable loaderThread = new Runnable() {
public void run() {
ServiceCaller serviceCaller = new ServiceCaller();
boolean status = serviceCaller.checkProcess(url);
}
};
}
Further Question [EDITED]
Here is the scenario. The main activity class creates a thread on a click. The then created thread fetches some data from the server. It is a time consuming task. So a progress bar is displayed on the UI. Currently I am using AsyncTask (not shown here) to accomplish server data retrieval. But the real challenge is wait for the background task to complete and get the value from it. What I am looking for is:
wait until server calls are made and get the results. Meanwhile show the progress bar.
Any thoughts? Apologies in case I confuse you.
Thanks in advance!
You must have a look at AsyncTask
http://developer.android.com/reference/android/os/AsyncTask.html
http://www.vogella.com/articles/AndroidPerformance/article.html
and you can show the ProgressBar in onPreExecute()
do the task in doInBackground()
and hide the ProgressBar in onPostExecute()
Join method blocks the current thread. In your case Onclick method is called in UI thread, so all UI operations are blocked. It is never a good idea to block Ui thread.
So you probably should use either a Handler or Asynctask to keep updating Progressbar
As and when i click to button, it waits for 20 seconds and then getting text to text view, my requirement is that after 1 second, progress bar has to increment by 5 values.
Can any one guide me in following code
public class ProgressBar1 extends Activity{
TextView tvpbview;
ProgressDialog pd1;
ProgressBar pbhr1;
Button btnhp1;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.progressbar1);
btnhp1 = (Button) findViewById(R.id.btnProgress);
pbhr1 = (ProgressBar) findViewById(R.id.pbHori1);
pbhr1.setMax(100);
btnhp1.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
//--- For Horizontal Progress Bar------
for(int i=0;i<=20;i++)
{
pbhr1.incrementProgressBy(5);
tvpbview.setText(""+pbhr1.getProgress()+"% done");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
You approach is incorrect, you need to use correct form of Threads. Especially for your case i recommend to you use following:
Handler
AsyncTask
Most likely you need to read some tutorial so have look at
Android Threads, Handlers and AsyncTask -
Tutorial
this is a great source.
Note: AsyncTask is more complex than Handler and also is generic-type and is especially designed for updating UI with some progress.
create an asynchronous task. Android provides the AsyncTask class for this purpose. You create a progress bar on the UI thread, then call the publishProgress() method to update your progress bar. It's really simple to use, the documentation is excellent.
try this
After they have been initially set, if you want to then change the values you need to set it to zero in between:
ProgressBar dataProgressBar = (ProgressBar) findViewById(R.id.progressBarData);
//set to 0 first to stop the bug from happening
dataProgressBar.setMax(0);
dataProgressBar.setProgress(0);
//set your new values
dataProgressBar.setMax((int) dataAllowanceValue);
dataProgressBar.setProgress((int) dataMBytes);