Skip to content

How does a thread work with Handler in Android?

In the code section given below, I have run a thread on a button click. At first, the thread will set text to the textView, then it will sleep for 6 seconds. But, in reality on the button click, at first, the thread is sleeping for 6 seconds and then it is setting text to the textView. Now, why is this mismatch of statement execution flow happening?

public class MainActivity extends AppCompatActivity {

    EditText editText;
    Button button;
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = findViewById(R.id.editText);
        button = findViewById(R.id.button);
        textView = findViewById(R.id.textView);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                button.setEnabled(false);
                runthread();
            }
        });

    }

    private void runthread() {
        final String s1 = editText.getText().toString();
        Handler handler = new Handler();
        handler.post(new Runnable() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText(s1);
                        button.setEnabled(true);
                        try {
                            Thread.sleep(6000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        });
    }
}

Answer

Android main thread is built upon two core concepts Looper and Handler. It translates to a message loop related to the thread, and messages interactor. All the ui staff benefit on that. For instance used by you setText is nothing more then sending a new message to the looper, after some time the message is resolved by some handler. As you may guess firstly each message is enqueued.

If you look at your example through the prism of above, you see steps listed below.(All steps are taken on main thread / main looper)

Handler handler = new Handler(); <-- this handler is related to main looper
        handler.post(new Runnable() {   <-- enqueue new message to main looper
            @Override
            public void run() {
                runOnUiThread(new Runnable() { <-- enqueue new message
                    @Override
                    public void run() {
                        textView.setText(s1); <-- enqueue new message of setting text
                        button.setEnabled(true); <-- enqueue new message of enabling button
                        try {
                            Thread.sleep(6000); <-- suspend thread for 6 seconds
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        });

According to above, you suspend thread before all the messages including setting text and enabling button are handled. The consequence of that is reversed order.

#UPDATE Then, how can I enqueue this sleep() method also?

     val handler = Handler();
        handler.post {
            runOnUiThread {
                textView.text = "ABC";
                button.isEnabled = true;

            }
        }
        handler.postDelayed({
            try {
                Thread.sleep(6000);
            } catch (e: InterruptedException) {
                e.printStackTrace();
            }
        }, 16) <-- 16 milliseconds is a time between frames