Behy
Behy

Reputation: 483

why UI processes before a heavy process are not executed first

Considering a simple code like this:

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInastanceState){
        super.onCreate(savedInastanceState);
        setContentView(R.layout.main);
        final ProgressDialog pdUpdate = new ProgressDialog(this);
        final Button btn = (Button)findViewById(R.id.btnUpdate);
         btn.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view){
                btn.setText(Calendar.getInstance().getTime().toString());
                int i=0;
                while (i<100){
                    i++;
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.i("test","i = " + i);
                }
            }
        });

    }
}

By click on btn two things should happen: changing the text of the button and counting i to 100 with delays of 100 msec (it is just an example to simulate a heavy process like reading file, downloading etc).
I know the correct way of implementing such codes is to use AsyncTask but my question is about how this code is compiled. It is a single thread app. So the compiler reads btn.setText(Calendar.getInstance().getTime().toString()); first and goes to the next lines of code only after this line is executed (please correct me if I am wrong), but it does not happen. Why?
In C# there is a Refresh() method which solves this problem (just call it after UI changes and changes are applied). Is there any similar method in java?
I appreciate any help.

EDIT 1
there question is about the order of the following processes:
Process 1:

 btn.setText(Calendar.getInstance().getTime().toString());

Process 2:

                    i++;
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.i("test","i = " + i);
                }

No matter which one comes first ( I mean either btn.onClick(){process1;process2;} or btn.onClick(){process2;process1;}), always the second process is executed first. I mean first I see the counting of i in Logcat and then see the changing of the button text.

Upvotes: 0

Views: 96

Answers (4)

Marcin Jedynak
Marcin Jedynak

Reputation: 3847

You do not need to call any equivalent of refresh() in this case. However Button.setText() do not redraw the view hierarchy automatically. Instead it passes up the view hierarchy the information that the text changed and that it needs to be redrawn. Ultimately this information reaches the root of the view hierarchy which in turn informs the Choreographer. The Choreographer schedules the drawing for the next frame. This in turn gets stored in your UI thread's message queue.

So when you put your UI thread to sleep, the layout is not redrawn, but is scheduled to be redrawn. As soon as your thread becomes idle, a messages from message queue start being executed. At some point the Choreographer is called with the redraw message and orders the view hierarchy to redraw itself.

Also consider that methods like Handler.post(Runnable), View.post(Runnable) and their postDelayed counterparts can serve as an alternative to AsyncTask if do not need to do a heavy computation, but instead schedule some operation (for example a view update) for later. These use the same mechanism as described above - put a Runnable into the thread's message queue, which in turn gets picked up and executed when the thread is idle.

Upvotes: 3

WDude
WDude

Reputation: 67

As many people pointed out in the comments, since you are doing your while loop within the onClickListenerthe UI thread never gets the chance to invalidate your button, and thus redraw said button.

I would think if you wanted to get around this you could simply have another listener related to the redraw process finishing. Take a look at the following from this answer:

public class DrawListenerView extends View{
    private Callback callback;
    public DrawListenerView(Callback callback){
        this.callback = callback;
    }

    @Override
    protected void onDraw (Canvas canvas){
        super.onDraw(canvas);

        //add your method here you want to call
        //Or use a Callback-pattern 
        callback.finish();
    } 
}

public interface Callback(){
    public void finish();
}

You could do your looped sleeping once the draw of your button has finished. This would give you the new text drawn, and then sleep after.

I'm not sure what the application for this would be, however you might consider just disabling the button for some time after they press it if you don't want the user to click it again for whatever amount of time.

Upvotes: 0

Leito
Leito

Reputation: 114

nonono,only your method run finish can it work(exp:just sleep 100 msc without while loop).you can find in google how the java method works.(my english is very poor)

Upvotes: 0

Marc Plano-Lesay
Marc Plano-Lesay

Reputation: 6968

Android is managing its own drawing cycles. But you're sharing the main thread with it. You can call invalidate() on a view, which is planning a redrawing of the view. But you'll have to free some time on the main thread fro Android to refresh the UI anyway (setText() is probably calling invalidate() anyway).

Upvotes: -1

Related Questions