Search code examples
androidsingle-threaded

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.


Solution

  • 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.