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!
Related
There are other similar SO questions but none really helped.
I just want to implement a progress bar, that on button click, starts from 0, moves gradually and stops at a point (which is read from sharedPreferences).
However things are working but that progress bar is not gradually updating , instead it appears straight at the end point.
A simplified code I'm pasting here:
public class MainActivity extends ActionBarActivity {
int primaryProgress = 0;
int finalPrimaryProgress = 60;
ProgressBar progressBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
progressBar.setMax(100);
progressBar.setProgress(0);
}
public void click(View view) {
while (primaryProgress < finalPrimaryProgress)
{
progressBar.setProgress(primaryProgress+=1);
Thread.sleep(100); //This is inside Try-catch in actual code.
}
}
}
I know the problem is with Threads, but I am not too friendly with threads so unable to understand the problem.
I'm assuming your "click" method is happening on the UI thread (otherwise, you'd probably get an exception when you try to alter the view on a background thread).
Think about what you're doing here - the moment you click a button, you begin a loop. On each iteration of the loop, you change the value of the progress bar, and then make the (main) thread sleep for 100ms, then repeat.
Problem is, you're never letting the runtime react to what change you made. Once you change a value of a view (for instance, change the text of a TextView), the Android runtime needs to have time to react to your change, adjusting the view's size, position and then redraw. Your code basically blocks anything from happening before you change the value again, because you're telling the main thread to sleep, and then you immediately change the value again.
The expected result here would be seeing the progress bar drawn in its final state once you stopped changing its value without letting the runtime actually do anything with it.
As an alternative to your current click method, try doing something with postDelayed. This will allow the runtime to perform a full measure-layout-draw cycle between every iteration:
private void increaseProgressBar()
{
if (progressBar.getProgress() < progressBar.getMax())
progressBar.postDelayed(new Runnable() {
#Override
public void run() {
progressBar.setProgress(progressBar.getProgress() + 1); // increase the value of the progress bar.
increaseProgressBar(); // call the function again, but after a delay.
}
}, 100);
}
public void click(View v)
{
increaseProgressBar();
}
Note: Keep in mind that currently, clicking the button multiple times will cause erratic behaviour, since your basically adding additional runnables to this sequence, causing the progress bar to grow faster. You could defend against such a case fairly easily with a simple flag.
I have a download process that runs in the background, and updates the UI with progress (a ListView adapter). It works fine until I leave the activity and come back. After loading the activity again there is a "new" ListView object that is not the same one that is bound to the BG download process. How can I structure my code so that the background process can always talk to the ListView in my activity?
The specific line that does this is:
adapter.notifyDataSetChanged();
Here is the shell of the Download class:
public class Download
{
}
protected void start()
{
TransferManager tx = new TransferManager(credentials);
this.download = tx.download(s3_bucket, s3_dir + arr_videos.get(position), new_video_file);
download.addProgressListener(new ProgressListener()
{
public void progressChanged(final ProgressEvent pe)
{
handler.post( new Runnable()
{
#Override
public void run()
{
if ( pe.getEventCode() == ProgressEvent.COMPLETED_EVENT_CODE )
{
Download.this.onComplete();
}
else
{
Download.this.onProgressUpdate();
}
}
});
}
});
}
protected void onProgressUpdate()
{
this.download_status = "downloading";
Double progress = this.download.getProgress().getPercentTransfered();
Integer percent = progress.intValue();
//Log.v("runnable", percent + "");
downloaded_data.edit().putInt(position+"", percent).commit();
adapter.notifyDataSetChanged();
}
}
The short answer is simply "no". There's no simple way to find/keep the reference to a ListView in a destroyed/recreated Activity.
One way you can get around this is to use BroadcastReceiver. You can broadcast progress intents, and have the Activity register/deregister from those intents in onCreate() and onPause().
Another (arguably easier) hack you can do it is to persist the state (along the lines of what you're doing with downloaded_data.edit()), and have a thread in your Activity that regularly polls this state and updates the ListView accordingly.
You can save data of listview in file, then in function onCreate callback to take it. Using File may be a solution. Once your Activity is destroyed, all datas are lost
Make tasks detachable, like an Executor service placed in a component that is always there between Activity changes:
Use a Service: clients can connect to it and request what tasks are running etc.
Implement Application class and let it hold references to tasks that are running, exposed via a static field.
I'm creating a startup activity for an application that downloads and parses some data. While it is loading i want to view my startup_loading.xml file, which contains a progressbar and a textview which is set to "loading".
Everything i need to do in the startup is put in a thread, the startUp thread. After running this thread the data is loaded, and then i want to show a new view: startup_loaded.xml which contains two buttons which send you to different activities in the application.
But i have troubles using two views and displaying them at the time i want to.
I thought that if i put the
setContentView(R.layout.startup_loaded);
at the end of the runnable of my thread it would work, but i get an error that that is impossible
CalledFromWrongThreadException: Only the original thread that created
a view hierarchy can touch its views.
I thought i could fix it like this:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
startUp.start();
String language = Locale.getDefault().getISO3Language();
if (startUp.isAlive()) {
setContentView(R.layout.startup_loading);
tv1=(TextView)findViewById(R.id.tvloading);
if(language.equals("nld")) {
tv1.setText("bijwerken");
} else {
tv1.setText("loading");
}
pbar=(ProgressBar)findViewById(R.id.progressBar1);
pbar.setVisibility(1);
} else {
setContentView(R.layout.startup_loaded);
}
}
Thread startUp= new Thread() {
public void run() {
// all the loading
}
};
But this didn't work either: my startup_loaded never appeared.
I tried finishing my thread (finish(); at the end of the runnable), but it finished by total app. Anyone knows what to do?
Use Handler object. Or, use AsyncTask and set new contrent in onPostExecute() method.
I can't understand the implementation of a while loop in android.
Whenever I implement a while loop inside the onCreate() bundle, (code shown below)
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
TextView=(TextView)findViewById(R.id.TextView);
while (testByte == 0)
updateAuto();
}
nothing boots up, and the program enters a "hanging" state after a while and I can't understand why. Testbyte is as follows:
byte testByte == 0;
and updateAuto() is supposed to update the code per 1 second and display inside the textView portion. I've been using setText inside updateAuto() as shown below and everything works fine, but once i implement the while loop all i see is a black screen and then an option to force close after a few seconds due to it "not responding".
TextView.setText(updateWords);
I've changed it to a button format (meaning i have to click on the button to update itself for now), but i want it to update itself instead of manually clicking it.
Am i implementing the while loop in a wrong way?
I've also tried calling the while loop in a seperate function but it still gives me the black screen of nothingness.
I've been reading something about a Handler service... what does it do? Can the Handler service update my TextView in a safer or memory efficient way?
Many thanks if anyone would give some pointers on what i should do on this.
Brace yourself. And try to follow closely, this will be invaluable as a dev.
While loops really should only be implemented in a separate Thread. A separate thread is like a second process running in your app. The reason why it force closed is because you ran the loop in the UI thread, making the UI unable to do anything except for going through that loop. You have to place that loop into the second Thread so the UI Thread can be free to run. When threading, you can't update the GUI unless you are in the UI Thread. Here is how it would be done in this case.
First, you create a Runnable, which will contain the code that loops in it's run method. In that Runnable, you will have to make a second Runnable that posts to the UI thread. For example:
TextView myTextView = (TextView) findViewById(R.id.myTextView); //grab your tv
Runnable myRunnable = new Runnable() {
#Override
public void run() {
while (testByte == 0) {
Thread.sleep(1000); // Waits for 1 second (1000 milliseconds)
String updateWords = updateAuto(); // make updateAuto() return a string
myTextView.post(new Runnable() {
#Override
public void run() {
myTextView.setText(updateWords);
});
}
}
};
Next just create your thread using the Runnable and start it.
Thread myThread = new Thread(myRunnable);
myThread.start();
You should now see your app looping with no force closes.
You can create a new Thread for a while loop.
This code will create a new thread to wait for a boolean value to change its state.
private volatile boolean isClickable = false;
new Thread() {
#Override
public void run() {
super.run();
while (!isClickable) {
// boolean is still false, thread is still running
}
// do your stuff here after the loop is finished
}
}.start();
Im doing a little app, its a memory game, you choose one card, it turns up, you choose the second card, it turns up, if they are the same they are out of the game, if they dont match, they are turned down again.
I have
public class PlayActivity extends Activity implements OnClickListener.
The flip events are trigged by click handlers, declared at public void onCreate(Bundle savedInstanceState) they work fine.
When the first card is selected, it calls my method Action, this sets the image from default (the card back) to the 'real' image (the card front). Fine so far.
My problem is the second card: when its selected, it calls method Action, where it should set the front image (lets call it 'middle action'), then a litle pause (a while loop doing nothing until x milliseconds), and then it checks what to do (if they match or not) and turn them down or take the out of the game. You can see where is the problem: the screen only displays the result after x milliseconds (the 'middle action' is not being draw).
Since I have done some little games with XNA, I know the loop Update-Draw, so I know here im updating the same thing twice so always the last one is drawn. But here, the only updating I can have is when click events are trigged, I need a periodic, constant update.
Help?
You can probably use a TimerTask in order to handle that. You can implement it like the following.
This probably isn't the most robust way to do it, but it is an idea. If I figure out a better way to do it in a short time I'll edit my post. :)
Also I would like to add that if you want to make a game that uses an update / draw loop you may need to use a SurfaceView to draw your game. Look at the example here http://developer.android.com/resources/samples/JetBoy/index.html
public class TestGameActivity extends Activity {
/* UIHandler prevents exceptions from
performing UI logic on a non-UI thread */
private static final int MESSAGE_HIDE_CARD = 0;
private class UIHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_HIDE_CARD:
ImageView cardView = (ImageView) msg.obj;
cardView.setImageResource(R.drawable.faceDownCard);
break;
}
}
}
private UIHandler handler = new UIHandler();
// Handle my click. V is the card view
public void onClick(View v) {
final int viewID = v.getId();
// Create a hide task
TimerTask hideTask = new TimerTask() {
#Override
public void run() {
// Construct a message so you won't get an exception
Message msg = new Message();
msg.what = MESSAGE_HIDE_CARD;
msg.obj = findViewById(viewID);
handler.sendMessage(msg);
}
};
// Schedule the task for 2 seconds
new Timer().schedule(hideTask, 2000);
}
}