An overview,

In Android development, we often find ourselves in a situation where we need to execute a time-consuming piece of code after doing something on the UI. For example, when we click a “download” button on the UI, we need to execute a network request, which is a time-consuming operation because we don’t know when it will be completed. To make sure it doesn’t affect the UI thread, we create a new thread to execute our time-consuming code. When our time-consuming operation is complete, we need to update the UI to inform the user that the operation is complete.

Ii. Incorrect usage scenarios

You might write code like this:

package com.example.myapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class TestHandlerActivity extends AppCompatActivity {

    private TextView textview;
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_handler);
        textview = (TextView)findViewById(R.id.textview);
        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { DownloadThread downloadThread = new DownloadThread(); downloadThread.start(); }}); } class DownloadThread extends Thread{ @Override public voidrun() {
            try{
                System.out.println("Start downloading files"); Thread.sleep(5000); // let the thread DownloadThread sleep for 5 seconds, simulating the file's time-consuming process system.out.println ("File download completed");
                textview.setText("success"); // Update UI}catch (InterruptedException e){e.printStackTrace(); }}}}Copy the code

1, describe,

The above code demonstrates that clicking the download button will start a new thread to perform the actual download operation and then update the UI. But in actual operation to the code textview. SetText (” success “), an error is as follows: android. The ViewRootImpl $CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

2, the reason

The error means that only the original thread that created the View can update the View. The reason for this error is that the View in Android is not thread-safe. When the Android application starts, it automatically creates a thread, namely the main thread of the program, which is responsible for the UI display and UI event message distribution processing, etc., so the main thread is also called the UI thread. Textview is created in the UI thread, and when we update the TextView created in the UI thread in the DownloadThread, we will naturally report the error.

3. Solutions

Android UI controls are not thread-safe, in fact, many platforms are not thread-safe UI controls, such as C#. UI controls in the Net Framework are also non-thread-safe, so it’s not just the Android platform that has the problem of updating UI controls created in the UI thread from a new thread. Different platforms provide different solutions to update UI controls across threads, and Android has introduced a Handler mechanism to solve this problem.

What is Handler

So what exactly is Handler? Handler is a mechanism introduced in Android that lets developers participate in handling message loops in threads. Each Hanlder is associated with a thread, and each thread maintains a MessageQueue MessageQueue, so that the Handler is actually associated with a MessageQueue MessageQueue. The Message and Runnable objects can be sent to MessageQueue, the Message queue of the thread associated with the Handler. The MessageQueue then cycles out a Message and processes it. Then the next Message is taken out and the process continues, over and over again. When a Handler is created, it is bound to the thread that currently created the Hanlder. From this point on, the Hanlder can send Message and Runnable objects to the Handler’s corresponding Message queue, and when a Message Message is fetched from MessageQueue, the Handler will process it.

The function of Handler

A Handler can be used to communicate between multiple threads. Updating a UI control in a UI thread in another thread is a typical example of Handler usage.

In addition, you can control one thread within another through handlers. Each Handler is bound to a thread, so let’s say there are two threads ThreadA and ThreadB, and the Handler is bound to ThreadA, and when the code in the ThreadB executes somewhere, for some reason, we need to tell ThreadA to execute some code, At this point we can use handlers, and we can add some information to the Handler era in ThreadB to tell ThreadA that it’s time to do some processing. Handler is the representative of Thread. It is the bridge of communication between multiple threads. Through Handler, we can control another Thread to do something in one Thread.

5. Use of Handler

Handler provides two ways to solve this problem (updating the UI controls in the main thread in a new thread), either through the POST method or by calling the sendMessage method.

1. Use the post method as follows:

package com.example.myapplication; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.widget.Button; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; public class TestHandlerActivity extends AppCompatActivity { private TextView textview; private Button button; Private Handler mHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_test_handler);
        textview = (TextView)findViewById(R.id.textview);
        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println("Main thread id"+ Thread.currentThread().getId()); DownloadThread downloadThread = new DownloadThread(); downloadThread.start(); }}); } class DownloadThread extends Thread{ @Override public voidrun() {
            try{
                System.out.println("Download thread id" + Thread.currentThread().getId());
                System.out.println("Start downloading files"); Thread.sleep(5000); // let the thread DownloadThread sleep for 5 seconds, simulating the file's time-consuming process system.out.println ("File download completed"); // Update UI mhandler. post(new) after the file is downloadedRunnable() {
                    @Override
                    public void run() {
                        System.out.println("Runnable thread id + Thread.currentThread().getId()); textview.setText("success"); }}); }catch (InterruptedException e){ e.printStackTrace(); }}}}Copy the code

We create a Handler member variable in our Activity called mHandler. Handler has a special feature. When we execute new Handler(), by default the Handler binds to the thread in which the code is executing. So mHandler automatically binds to the main thread, the UI thread. When we have finished executing the time-consuming code in the DownloadThread, we pass a Runnable object to the Handler via post. The Handler will tell the main thread to execute the Runnable code when appropriate, and the Runnable will execute in the main thread. This correctly updates the UI in the main thread. The following is the result of the program:

2. Use sendMessage as follows:

package com.example.myapplication; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.Button; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; public class TestHandlerActivity extends AppCompatActivity { private TextView textview; private Button button; Private Handler mHandler = newHandler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case 1:
                    System.out.println("handleMessage thread id " + Thread.currentThread().getId());
                    System.out.println("msg.arg1:" + msg.arg1);
                    System.out.println("msg.arg2:" + msg.arg2);
                    textview.setText("success");
                    break; }}}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_test_handler);
        textview = (TextView)findViewById(R.id.textview);
        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println("Main thread id "+ Thread.currentThread().getId()); DownloadThread downloadThread = new DownloadThread(); downloadThread.start(); }}); } class DownloadThread extends Thread{ @Override public voidrun() {
            try{
                System.out.println("Download thread id " + Thread.currentThread().getId());
                System.out.println("Start downloading files"); Thread.sleep(5000); // let the thread DownloadThread sleep for 5 seconds, simulating the file's time-consuming process system.out.println ("File download completed"); MSG = new Message(); MSG. What = 1; MSG. What = 1; MSG. Arg1 = 123; arg1 = 123; arg1 = 123; arg1 = 123; arg1 = 123; msg.arg2 = 321; // Send this Message to the corresponding Handler mhandler.sendMessage (MSG); }catch (InterruptedException e){ e.printStackTrace(); }}}}Copy the code

The steps to communicate with the Handler via Message are:

  1. Override the Handler’s handleMessage method to do different processing operations depending on the what value of the Message
  2. Create a Message object, although the constructor for Message is public, We can also obtain a Message object by message.obtain () or handler.obtainMessage () (which actually calls message.obtain () internally).
  3. Message.what is a Message identifier that can be used to identify different messages in the Handler’s handleMessage method so that different operations can be performed.
  4. Set the data carried by Message. Simple data can be assigned by two ints, field arg1 and arg2, and can be read in handleMessage.
  5. If Message needs to carry complex data, you can set the obj field for Message. Obj is of type Object and can be assigned to any type of data. Or you can assign data of type Bundle by calling the setData method of Message. You can get the Bundle data by calling the getData method.
  6. We pass a Message to the Handler through the handler.sendMessage (Message) method to process it in the handleMessage.

Note that if you do not need to determine the type of Message in the handleMessage, you do not need to set the what value of Message. And it doesn’t have to be a Message to carry data, only when it needs to. If you really need Message to carry data, try arg1 or arg2 or both, not obj if you can do it with arg1 and arg2, because arg1 and arg2 are more efficient. The running results of the program are as follows:

Six, summarized

The principles of Android threads:

  1. UI can no longer be updated in a non-UI thread;
  2. Can’t do time-consuming operations in the UI thread.

1. Why can’t I update the UI in a non-UI thread

Because Android UI threads are not thread-safe. The invalidate() method is not thread-safe, which means that when the UI is updated in a non-UI thread, other threads or UI threads may also be updating the UI, causing the interface updates to be out of sync. So we can’t update the UI in a non-UI main thread.

2. Why can’t we do time-consuming operations on the main thread

Prevent ANR. You can’t do time-consuming operations in the UI main thread, so you can do time-consuming operations in another worker thread. When the operation is complete, the UI main thread is notified to respond accordingly. This needs to master the communication between threads -Handler mechanism.

Special thanks to:

This article from blog.csdn.net/iispring/ar… And did the processing