Android< Handler mechanism I understand >

Keywords: Android REST JDK Attribute

1. Why need Handler mechanism

In my < How Threads and Processes Work in Android > In this article, it is clearly stated that when the application is started, the system will create an execution thread named "main thread" for the application. This thread is important because it is responsible for dispatching events to corresponding user interface widgets, including drawing events. In addition, it is also a thread for applications to interact with Android UI toolkit components (components from android.widget and android.view packages). So all our UI-related operations have to be executed in this thread, which is a set of Android mechanisms.
The most fundamental reason is:
It is to solve the problem of multithreading concurrency. If there are multiple threads in an activity to update the UI, and there is no locking mechanism, then the update interface will be confused. You can't lock all operations that update the UI, or you'll lose performance.

2.Handler mechanism principle

a).

If the UI thread and the worker thread start at the same time when the application starts, the worker thread needs to update the UI to tell the user that the worker thread has finished executing. But Android does not support updating UI in worker threads (non-UI threads). What should I do?

For example, now we have a button. In the event of the button listening, we open a new worker thread to handle our business. After processing, we need to change the text on the button to notify the user that the worker thread has finished what it needs to do:

public class MainActivity extends AppCompatActivity {
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Open a new thread
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //Here's the rest of the code.


                        //Last update UI
                        button.setText("Finished processing");
                    }
                }).start();
            }
        });
    }
}

What we need is this effect:

Obviously our monitoring event will throw an exception:

b).Handler's debut
Handler appeared to solve problem a). So how does it work out?
Handler instantiates an object in the UI thread, uses the reference in the worker thread, sends the message directly through the reference, and handler in the UI thread processes the message after receiving it.

This is like a person in the office said that I am in a hurry, I want to go to the toilet, but the office is not a toilet, so he went to the toilet. But it is always its own solution to the problem of internal urgency. It is only a change of venue.

Handler is the person, and Message is the toilet thing.

Solution:

package com.example.geekp.mhandler;

import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextClock;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private Button button;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Open a new thread
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //Here's the rest of the code.
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                button.setText("Finished processing");
                            }
                        });
                    }
                }).start();
            }
        });
    }
}

3. How the Handler mechanism is implemented

a). Partial source code
Let's open the Handler source code.

    final Looper mLooper;
    final MessageQueue mQueue;

Seeing a Looper and a MessageQueue reference, these are the two core member variables of the Handler mechanism.
i.Looper

Looper is used to make a thread work in a loop. How does it manifest itself in the Handler mechanism? Let's not go into details here. Look at an example of Looper source code.

  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }

ii.MessageQueue is a message queue

This is a queue that can be loaded with messages. Queues are data structures that follow the "first in first out" order.

Relationships among iii.Handler, Looper and MesageQueue
Here's a more detailed UI to illustrate the problem:

4. A thread can only have one Looper

Open the source code of the Looper class and you can see:

It is very clear here that a thread can only create a Looper. It's easy to understand that if a thread can create multiple Loopers, it means that a thread can create multiple message loop queues. Think about the consequences?
If there are multiple message loop queues, how can Handler safely retrieve messages from the queue?
So how does Android guarantee that only one Looper can be created?

This is mainly due to ThreadLocal.
Look at the explanation of ThreadLocal in JDK:

For example, assuming that we have three threads operating on a variable at the same time, but we also need to ensure that the operations between threads do not affect each other, we can use ThreadLocal to achieve this:

package jdk.lang;

public class MThreadLocal {
    // Override ThreadLocal's initialValue() method by an anonymous inner class, specifying the initial value

    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
        public Integer initialValue() {
            return 0;
        }
    };
    // Get the next sequence value
    public int getNextNum() {
        seqNum.set(seqNum.get() + 1);
        return seqNum.get();
    }

    public static void main(String[] args) {
        MThreadLocal sn = new MThreadLocal();
        //  Three threads share sn and each generates serial numbers
        TestClient t1 = new TestClient(sn);
        TestClient t2 = new TestClient(sn);
        TestClient t3 = new TestClient(sn);
        t1.start();
        t2.start();
        t3.start();
    }

    private static class TestClient extends Thread {
        private MThreadLocal sn;

        public TestClient(MThreadLocal sn) {
            this.sn = sn;
        }

        public void run() {
            // Five sequence values per thread
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ":  " + sn.getNextNum());
            }
        }
    }
}

At this time the console output:

We can see that the console outputs the values of local variables (1-5) for each thread in turn.
Then let's look at the code in Looper:

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

You can see that in Looper's class, ThreadLocal is used to ensure that only one Looper object can be created per thread.

5. Several Common Usages of Handler

a).boolean post (Runnable r)
Sample code:
Instantiate a Handler object in the main thread, and then call the post function in the worker thread. The parameter is a Runnable object. You can write your operation in the run() method of the Runnable object.

public class MainActivity extends AppCompatActivity {
    private Button button;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Open a new thread
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //Here's the rest of the code.
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                button.setText("Finished processing");
                            }
                        });
                    }
                }).start();
            }
        });
    }
}

b).boolean postDelayed (Runnable r, long delayMillis)
This method is similar to method a, except that it delays execution in the run() function of the Runnable object for the second parameter, long delayMillis, for five seconds as follows:

 handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                button.setText("Finished processing");
                            }
                        }, 5000);

c).boolean sendMessage (Message msg)

This approach is useful when there are many worker threads that need to update the UI at the same time.

Let's look at an example. There are two buttons in activity. Both buttons will trigger a listening event to send a message to the UI thread when they are clicked. The UI thread can distinguish which thread sent the message according to the message object's information before the worker thread passed it, using the Message.what attribute.

public class MainActivity extends AppCompatActivity {
    private Button button1;
    private Button button2;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    Toast.makeText(getApplicationContext(), "UI Thread receives a message from Workthread 1", Toast.LENGTH_SHORT).show();
                    break;
                case 2:
                    Toast.makeText(getApplicationContext(), "UI Thread receives message from Workthread 2", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button1 = (Button) findViewById(R.id.button1);
        button2 = (Button) findViewById(R.id.button2);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Open a new thread one
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //Here's the rest of the code.
                        //Create message
                        Message message = new Message();
                        message.what = 1;
                        message.obj = "This is thread one message.";
//                        Send to Message Loop Queue
                        handler.sendMessage(message);
                    }
                }).start();
            }
        });


        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = handler.obtainMessage();
                message.what = 2;
                message.obj = "This is Thread 2 message.";
//                handler.sendMessage(message);
                message.sendToTarget();
            }
        });
    }
}

Look at the effect:

What I need to mention here is that I use two different Message expressions in listening events for two more buttons:

Method 1:
Message message = new Message();
message.what = 1;
message.obj= "This is thread one message";
// Send to Message Loop Queue
handler.sendMessage(message);

Method two:
Message message = handler.obtainMessage();
message.what = 2;
message.obj = message of thread 2;
// handler.sendMessage(message);
message.sendToTarget();

Note: handler.sendMessage(message) is the only way to send messages when using method one, but handler.sendMessage(message) or message.sendToTarget() can be used in the second method.

d).boolean sendMessageDelayed (Message msg,
long delayMillis)
This method is similar to the method in C. The only difference is that the operation of method 1 is delayed and the delay time is the second parameter, long delayMillis.

Posted by jfugate on Tue, 02 Apr 2019 12:30:29 -0700