Analysis of common opening postures and stepping on pits of Handler

Posted by sell-traffic on Fri, 21 Jan 2022 22:59:39 +0100

Analysis of common opening postures and stepping on pits of Handler

1. Citation

Basic definition of handler: first take a direct look at the most authoritative official definition

  • A Handler allows you to send and process {@link Message} and Runnable
  • objects associated with a thread's {@link MessageQueue}. Each Handler
  • instance is associated with a single thread and that thread's message
  • queue. When you create a new Handler it is bound to a {@link Looper}.
  • It will deliver messages and runnables to that Looper's message
  • queue and execute them on that Looper's thread.
  • There are two main uses for a Handler: (1) to schedule messages and

  • runnables to be executed at some point in the future; and (2) to enqueue
  • an action to be performed on a different thread than your own.

To the effect that:
Handler allows binding with a unique thread. Its main functions are:

  1. Allow deferred execution of tasks
  2. Allow switching threads to perform tasks

Main API:
post(Runnable),
postAtTime(Runnable,long),
postDelayed(Runnable,long),
sendEmptyMessage(int),
sendMessage(msg),
sendMessageAtTime(msg,long)
sendMessageDelayed(msg, long) method.

**A simple demo example will be given at the end of the article**

2. First look at common errors

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = findViewById(R.id.tv);
        Log.d(TAG, "1-->" + Thread.currentThread().getId());

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    Log.e(TAG, e.toString());
                }
                tv.setText("oncreate Method, no time-consuming, change Tv text");

            }
        }).start();
    }

If you write the above code with shaking hands, congratulations and the following error reports:

 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6891)
        at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:1083)
        at android.view.ViewGroup.invalidateChild(ViewGroup.java:5205)
        at android.view.View.invalidateInternal(View.java:13656)
        at android.view.View.invalidate(View.java:13620)

This is relatively simple. Sub threads are not allowed to update Ui (really?)

3. Normal open position

The gestures of android updating UI are:
runOnUiThread();
handler.post();
handler.sendMessage();
view.post();
Simple demo interface

3.1 runOnUiThread

The API in Activity is simple and rough to use, but it must be used in the context of Activity

 public void runOnUIBtnOnclick(View view) {
     runOnUiThread(new Runnable() {  // Activity method, which can only be used in activity
         @Override
         public void run() {
             tv.setText("runOnUiThread change Tv text"); 
         }
     });
    }

3.2 handler.post()

It is easy to use. It is mainly used to switch threads.
Basic use

 Handler handler = new Handler(Looper.myLooper());
 handler.post(new Runnable() {
                    @Override
                    public void run() {
                        tv.setText("Switch threads, change Tv text"); 
                    }
                });

Let's see how threads switch

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = findViewById(R.id.tv);
        Log.d(TAG, "1-->" + Thread.currentThread().getId());
...
 public void changeThreadBtnOnclick(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "2--->" + Thread.currentThread().getId());
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.d(TAG, "3--->" + Thread.currentThread().getId());
                        tv.setText("Switch threads, change Tv text"); 
                    }
                });
            }
        }).start();
    }

The test results are:

2020-12-31 20:40:19.544 8170-8170/com.gavin.handlerdemo D/MainActivity: 1-->1
2020-12-31 20:42:15.357 8170-8208/com.gavin.handlerdemo D/MainActivity: 2--->346
2020-12-31 20:42:15.365 8170-8170/com.gavin.handlerdemo D/MainActivity: 3--->1

It can be seen that the thread at 2 is indeed a child thread and the thread at 3 is the main thread
To sum up:
Note that it is either written in the sub thread method or executed by the sub thread

3.3 handler.sendMessage()

This is mainly used to deliver messages. The common method of messages is that the sub thread sends a notification to the main thread to update the UI. However, the main thread can also send messages to the sub thread.
Next, create an endless loop. The main thread sends a message to the sub thread. After the sub thread receives it, it returns it to the main thread, and then the loop continues. The purpose of this demonstration is to warn that the consequences of not de registering this callback may be very serious when exiting.

 private static String TAG = "SecActivity";
    TextView tv;
    Handler threadHandler;

    Handler mainHandler = new Handler(Looper.myLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            if (msg.obj != null) {
                Log.e(TAG,  "The main thread received a call, \"" + msg.obj + "\"" );
            } else {
                Log.e(TAG,  "The main thread received a call");
            }
            Message message = new Message();
            message.obj = "This is a call from the main thread!!!";
            if (threadHandler != null) {
                threadHandler.sendMessageDelayed(message, 1000);
            }
        }
      };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sec);
        tv = findViewById(R.id.tv);
        HandlerThread thread = new HandlerThread("HandlerThread"); // HandlerThread is mainly used to facilitate the acquisition of loopers
        thread.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadHandler = new Handler(thread.getLooper()) {
                    @Override
                    public void handleMessage(@NonNull Message msg) {
                        if (msg.obj != null) {
                            Log.e(TAG,  "The child thread received the call, \"" + msg.obj + "\"" );
                        } else {
                            Log.e(TAG,  "The child thread received the call");
                        }
                        Message message = new Message();
                        message.obj = "This is a call from a child thread!!!";
                        mainHandler.sendMessageDelayed(message,1000);

                    }
                };
            }
        }).start();
    }

    public void testMsgTransform(View view) {
        mainHandler.sendEmptyMessageDelayed(0, 100); // Send an empty message, which is captured by its own handleMessage. Who sends and who receives.
    }

Results obtained:

2020-12-31 20:48:29.521 8170-8170/com.gavin.handlerdemo E/SecActivity: The main thread received a call
2020-12-31 20:48:30.523 8170-8221/com.gavin.handlerdemo E/SecActivity: The child thread received the call, "This is a call from the main thread!!!"
2020-12-31 20:48:31.526 8170-8170/com.gavin.handlerdemo E/SecActivity: The main thread received a call, "This is a call from a child thread!!!"
2020-12-31 20:48:32.527 8170-8221/com.gavin.handlerdemo E/SecActivity: The child thread received the call, "This is a call from the main thread!!!"
2020-12-31 20:48:33.529 8170-8170/com.gavin.handlerdemo E/SecActivity: The main thread received a call, "This is a call from a child thread!!!"
2020-12-31 20:48:34.531 8170-8221/com.gavin.handlerdemo E/SecActivity: The child thread received the call, "This is a call from the main thread!!!"
2020-12-31 20:48:35.533 8170-8170/com.gavin.handlerdemo E/SecActivity: The main thread received a call, "This is a call from a child thread!!!"
2020-12-31 20:48:36.537 8170-8221/com.gavin.handlerdemo E/SecActivity: The child thread received the call, "This is a call from the main thread!!!"
2020-12-31 20:48:37.537 8170-8170/com.gavin.handlerdemo E/SecActivity: The main thread received a call, "This is a call from a child thread!!!"

To sum up:

  1. There can be many handlers, but each Handler can only bind one thread and only have one Looper. The messages sent by the Handler are ultimately consumed by its own handleMessage, which is a real fertile water and does not flow to outsiders.

  2. Even if you close the page, you will find that this dead cycle is still there. Its life cycle is the life cycle of the app. If you don't close it, the consequences are really serious.
    Closing method:

  @Override
    protected void onDestroy() {
        super.onDestroy();
        //If the callback is not removed here, the callback will always exist, which is easy to cause memory leakage
        mainHandler.removeCallbacksAndMessages(null);
        threadHandler.removeCallbacksAndMessages(null);
    }

3.4 view.post()

  public void viewpostTest(View view) {
        tv.post(new Runnable() {
            @Override
            public void run() {
                tv.setText("view.post Method, change Tv text");
            }
        });
    }

view.post and handler Post is basically similar, but it is also different. We won't continue here.

4. Can the sub thread really update Ui?

The answer is generally not updated, but during the interview, you have to answer that you can update...
You can verify the effect first.

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = findViewById(R.id.tv);
        Log.d(TAG, "1-->" + Thread.currentThread().getId());

         new Thread(new Runnable() {
            @Override
            public void run() {
                tv.setText("oncreate Method, no time-consuming, change Tv text");
            }
        }).start();
    }

It can be seen that the page does not report an error and is displayed normally.
But if you add delay, that's it

   new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                tv.setText("oncreate Method, no time-consuming, change Tv text");
            }
        }).start();

Would like to mention the collapse mentioned above.
Why?

Error reading prompt:

 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6891)

This error message is given by ViewRootImpl.
The process is as follows:
The oncreate method does not give this validation, so the program executes normally and Ui is indeed updated.
However, in the onresume phase of the Activity, the ViewRootImpl method will be called, and then the checkThread will check whether the thread updating Ui is the main thread.
The specific code is relatively complex. In brief, you can see the onresume annotation

When the View becomes visible, the ViewRootImpl performTraversals method is called.

Therefore, if the sub thread does not take time, and the method in onresume has not been executed before the initial entry, there will be no problem if the UI update has been completed before the execution of ViewRootImpl checkThread. However, once the time-consuming method is executed, the checkThread mechanism will be triggered.

5. Memory leakage

There is no problem with the above code only as a test demo. But if you go online, there will be a memory leak.
(it is assumed that there is no removecallbacksandmessages when ondestroy is used.)
The main reasons are:
The Handler class object instantiated by the anonymous inner class implicitly holds the external Activity object.
When an Activity exits, the life cycle of the Handler is the same as that of the APP. Because it holds an Activity instance, the Activity cannot be recycled by the GC, causing a memory leak.

Solution:
By creating a static inner class that inherits the Handler and using weak references, it is used to avoid the Handler object holding strong references to external class objects.

 
    public MyHandler mHandler = new MyHandler(this);
 
    //Static inner class
    static class MyHandler extends Handler {
 
        private WeakReference<Context> weakReference;
 
        MyHandler(Context context) {
            weakReference = new WeakReference<>(context);
        }
 
     @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity mainActivity = mWeakReference.get();
            if (mainActivity != null) {
                tv.setText(msg.obj + "");
        }

6. demo address

https://github.com/jjbheda/handlerDemo

Topics: Java Android Apache