Android foundation is solid - how much do you know about Handler?

Welcome everyone who likes blogging to visit my blog, and the blog post will also be updated on it: Ryane's Blog

Overview

For students who are just getting started, they are often confused about Handler, what kind of thing is Handler. Of course, for some experienced engineers, they may not be able to describe it accurately. Let's take a look at the introduction of API.

Handler is a tool used to send and process "Message objects" and "Runnable objects" in conjunction with the thread's message queue. Each Handler instance is then associated with a thread and the thread's message queue. When you create a Handler, from then on, it will be automatically associated with the thread/message queue where it is located, and then it will successively distribute Message/Runnalbe to the message queue and process them when they are dequeued.

From the official documents, it is not difficult for us to find the key word, which is " thread ". We all know that an Android application involving network operations, time-consuming operations, etc. is inseparable from multi-threaded operations. However, if we allow concurrent UI updates at this time, the state of the controls will eventually be undetermined. Therefore, we can lock the control and unlock it when it is not needed. This is one of the solutions, but in the end it is easy to block the thread and the efficiency will be very poor. Therefore, Google has adopted only allowing the UI to be updated on the main thread, so the Handler as a thread communication bridge came into being.

The relationship between Looper, MessageQueue, Message, and Handler

When it comes to Handler, it is definitely inseparable from the relationship between Looper, MessageQueue, Message and Handler. The following is a brief introduction. In detail, you can check the relevant information or view the source code, which is more convenient for everyone to study in depth.

Looper

Each thread has only one Looper. After each thread initializes the Looper, the Looper will maintain the message queue of the thread to store the Message sent by the Handler and process the Message that is dequeued from the message queue. Its characteristic is that it is bound to its thread, and the processing of messages is also processed in the thread where the Looper is located, so when we create a Handler in the main thread, it will be bound to the only Looper of the main thread, so we use When the Handler sends a message in the sub-thread, it is finally processed in the main thread, which achieves an asynchronous effect.

Then someone will ask, why do we never need to create a Looper when we use Handler? This is because in the main thread, ActivityThread will initialize the Looper by default. After prepare, the current thread will become a Looper thread.

MessageQueue

MessageQueue is a message queue used to store messages sent by Handler. There is at most one MessageQueue per thread. MessageQueue is usually managed by Looper, and when the main thread is created, a default Looper object will be created, and the creation of Looper object will automatically create a MessageQueue. Other non-main threads do not automatically create Loopers.

Message

The message object is the object stored in the MessageQueue. A MessageQueue can include multiple Messages. When we need to send a Message, we generally do not recommend creating it in the form of new Message(). It is more recommended to use Message.obtain() to obtain a Message instance, because a message pool is defined in the Message class. When there is an unused message, it will return. If there is no unused message, it will be created and returned by new. Therefore, using Message.obtain() to obtain an instance can greatly reduce the garbage collection generated when there are a large number of Message objects. question.

The overall relationship between the four is as follows (if there is something wrong, thank you for pointing it out)image

The main purpose of Handler

  1. Push the Message or Runnable to be executed at a certain point in the future to the message queue.
  2. The child thread adds operations that need to be performed in another thread to the message queue.

Without further ado, let's illustrate the two main uses of Handler with examples.

1. Push the Message or Runnable to be executed at a certain point in the future to the message queue

Example: Countdown is realized through Handler with Message or Runnable

  • First look at the renderings

image

  • The first method is to implement the countdown by means of Handler + Message. code show as below:
public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;

    private Handler mHandler ;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //设置监听事件
        mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //通过Handler + Message的方式实现倒计时
                for (int i = 1; i <= 10; i++) {
                    Message message = Message.obtain(mHandler);
                    message.what = 10 - i;
                    mHandler.sendMessageDelayed(message, 1000 * i); //通过延迟发送消息,每隔一秒发送一条消息
                }
            }
        });

        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                mBinding.time.setText(msg.what + "");   //在handleMessage中处理消息队列中的消息
            }
        };
    }
}

In fact, the code does not need to be explained, it is relatively easy to understand, but DataBiding is used here. It may look a bit strange for students who have not used it, but in fact, the code is simplified. , so don't explain too much. Through this small program, the author hopes that everyone can understand that one of the functions of Handler is that in the main thread, some sequential operations can be processed by Handler, so that they can be executed at a fixed point in time.

  • The second method is to implement the countdown by means of Handler + Runnable. code show as below:
public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //设置监听事件
        mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for (int i = 1; i <= 10; i++) {
                    final int fadedSecond = i;
                    //每延迟一秒,发送一个Runnable对象
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mBinding.time.setText((10 - fadedSecond) + "");
                        }
                    }, 1000 * i);
                }
            }
        });
    }
}

The second method is to let you deepen the use of Handler to process ordered events through code. The reason why Runnable and Message are separated is because many people do not know why Handler can push Runnable and Message objects. In fact, no matter whether Handler adds Runnable or Message to MessageQueue, it only adds Message to MessageQueue in the end . As long as you look at the source code, you can know that the method of Handler's post Runnable object just encapsulates the post Message, so in the end we all push a Message through the Handler. As for why the two methods are separated, the following will give Let's talk about it in detail. Let's take a look at the second main use of Handler.

2. In the child thread, add the operation that needs to be performed in another thread to the message queue

Example: Load pictures in sub-threads through Handler + Message, and display pictures in UI thread

  • The effect diagram is as follows

image

  • The code is as follows (the layout code is not released)

public class ThreadActivity extends AppCompatActivity implements View.OnClickListener {
    private ActivityThreadBinding mBinding = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);
        // 设置点击事件
        mBinding.clickBtn.setOnClickListener(this);
        mBinding.resetBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            // 响应load按钮
            case R.id.clickBtn:
                // 开启一个线程
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        // 在Runnable中进行网络读取操作,返回bitmap
                        final Bitmap bitmap = loadPicFromInternet();
                        // 在子线程中实例化Handler同样是可以的,只要在构造函数的参数中传入主线程的Looper即可
                        Handler handler = new Handler(Looper.getMainLooper());
                        // 通过Handler的post Runnable到UI线程的MessageQueue中去即可
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                // 在MessageQueue出队该Runnable时进行的操作
                                mBinding.photo.setImageBitmap(bitmap);
                            }
                        });
                    }
                }).start();
                break;
            case R.id.resetBtn:
                mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic));
                break;
        }
    }

    /***
     * HttpUrlConnection加载图片,不多说
     * @return
     */
    public Bitmap loadPicFromInternet() {
        Bitmap bitmap = null;
        int respondCode = 0;
        InputStream is = null;
        try {
            URL url = new URL("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1421494343,3838991329&fm=23&gp=0.jpg");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(10 * 1000);
            connection.setReadTimeout(5 * 1000);
            connection.connect();
            respondCode = connection.getResponseCode();
            if (respondCode == 200) {
                is = connection.getInputStream();
                bitmap = BitmapFactory.decodeStream(is);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(), "访问失败", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }
}

The difference between Handler push Message and Runnable

In the above, we achieved the same countdown effect by pushing Message and Runnable with Handler. Here we will talk about the difference between Post(Runnable) and SendMessage(Message).

First, let's look at the source code of the post method and the sendMessage method:

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

It can be seen that both methods are implemented by calling the sendMessageDelayed method, so you can know that their underlying logic is the same.

However, when the bottom layer of the post method calls sendMessageDelayed, it converts the Runnable object into a Message through getPostMessage(r). We can see that by clicking on the getPostMessage() method:

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

In fact, the runnable is finally converted into a Message, and this Message has only one assigned member variable, which is the callback function of Runnable. That is to say, after this Message enters the MessageQueue, it is just an "action", that is, our Runnbale's The operations in the run method.

You know, our Message class has many parameters, so you can understand that it is a very rich JavaBean, you can look at its member variables:

  • public int what;
  • public int arg1;
  • public int arg2;
  • public Object obj;
  • ...

So when it comes to this, everyone should understand why Google engineers encapsulate these two methods. I summarize it as follows: In order to make it easier for developers to call according to different needs . When we need to transmit a lot of data, we can use sendMessage to achieve it, because by assigning values ​​to different member variables of Message, it can be encapsulated into objects with very rich data for transmission; when we only need to perform one action, use Runnable directly, The action content can be implemented in the run method. Of course, we can also pass in the callback interface through Message.obtain(Handler h, Runnable callback), but this does not seem as intuitive as post(Ruannaable callback).

API

API is the best document for us to study, so I will briefly learn it from you. In fact, everyone can read the API of Handler carefully by reading my introduction above and practicing by yourself.

Constructor

  • Handler()
  • Handler(Handler.Callback callback): Pass in an implemented Handler.Callback interface. The interface only needs to implement the handleMessage method.
  • Handler (Looper looper): A Looper that associates a Handler with any thread, which can be used to implement communication between sub-threads.
  • Handler(Looper looper, Handler.Callback callback)

main method

  • void dispatchMessage (Message msg)

Under normal circumstances, it will not be used, because its underlying implementation is actually a method for processing system messages. If it is really used, the effect is the same as that of sendMessage(Message m).

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 如果有Runnbale,则直接执行它的run方法
            handleCallback(msg);
        } else {
            //如果有实现自己的callback接口
            if (mCallback != null) {
                //执行callback的handleMessage方法
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //否则执行自身的handleMessage方法
            handleMessage(msg);
        }
    }
    
    private static void handleCallback(Message message) {
        message.callback.run();
    }
  • void dump (Printer pw, String prefix)

A method used in the main Debug, the dump function just uses the Printer object to print, and prints out some information in the Handler, Looper and Queue. The source code is as follows:

    public final void dump(Printer pw, String prefix) {
        pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());
        // 如果Looper为空,输出Looper没有初始化
        if (mLooper == null) {
            pw.println(prefix + "looper uninitialized");
        } else {
            // 否则调用Looper的dump方法,Looper的dump方法也是
            mLooper.dump(pw, prefix + "  ");
        }
    }

Through the test cases, you will understand more clearly:

        //测试代码
        Printer pw = new LogPrinter(Log.ERROR, "MyTag");
        mHandler.dump(pw, "prefix");

result:image

  • Looper getLooper ()

Get the Looper associated with the Handler

  • String getMessageName (Message message)

Get the name of the Message, the default name is the value of message.what.

  • void handleMessage (Message msg)

Process the message.

  • boolean hasMessages (int what)

Determine whether there is a what value of Message is the parameter what.

  • boolean hasMessages (int what, Object object)

Determine whether there is a message whose what value is the parameter what, and the obj value is the parameter object.

  • Message obtainMessage (int what, Object obj)

Get a message from the message pool and assign what and obj. The same is true for other overloaded functions.

  • boolean post (Runnable r)

Add the Runnable object to the MessageQueue.

  • boolean post (Runnable r)

Add Runnbale to the head of the message queue. However, this is not officially recommended, because it is easy to disrupt the queue order.

  • boolean postAtTime (Runnable r, Object token, long uptimeMillis)

Execute Runnable r at some point in time.

  • boolean postDelayed (Runnable r, long delayMillis)

Runnable r is executed after the current time delay delayMillis milliseconds.

  • void removeCallbacks (Runnable r, Object token)

Removes all Runnable objects in the MessageQueue.

  • void removeCallbacksAndMessages (Object token)

Removes all Runnable and Message objects from the MessageQueue.

  • void removeMessages (int what)

Removes all what-worthy Message objects.

  • boolean sendEmptyMessage (int what)

Get an empty message directly, assign what, and send it to MessageQueue.

  • boolean sendMessageDelayed (Message msg, long delayMillis)

Send a Message to the MessageQueue after a delay of delayMillis milliseconds.

Memory leak caused by Handler

In the above example, for the sake of convenience, I have not considered the memory leak situation, but in actual development, if the security of the code is not considered, especially when a project reaches a certain scale, then the maintenance of the code And system debugging is very difficult. And the memory leak of Handler is also a very classic case in Android.

For details, please refer to: How to Leak a Context: Handlers & Inner Classes

Or refer to the translated text: Memory leak caused by Handler in Android

Usually we define an inner class of Handler inside an Activity:

public class MainActivity extends AppCompatActivity {

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                 ...
            }
        }
    };
    
        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);


        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                ...
            }
        }, 1000000);

    }
}

(1) A non-static inner class Handler is defined in the outer class Activity, and the non-static inner class holds a reference to the outer class by default. If the external Activity is suddenly closed, but the messages in the MessageQueue have not been processed, the Handler will always hold a reference to the external Activity, and the garbage collector cannot recycle the Activity, resulting in a memory leak.

(2) As in the above code, in postDelayed, we pass in a non-static inner class Runnable in the parameter, which will also cause memory leaks. If the Activity is closed at this time, the garbage collector will not be able to recycle it in the next 1000000ms Activity, causing a memory leak.

solution:

(1) Convert non-static inner classes Handler and Runnable to static inner classes, because non-static inner classes (anonymous inner classes) will hold strong references to outer classes by default.

(2) After changing to a static inner class, the reference to the outer class is set as a weak reference, because during garbage collection, the weakly referenced object will be automatically recycled.

Examples of avoiding memory leaks:

public class HandlerActivity extends AppCompatActivity {

    private final MyHandler mHandler = new MyHandler(this);

    private static final Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            // 操作
        }
    };


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

        mHandler.postDelayed(mRunnable, 1000*10);
        
        finish();   
    }


    private static class MyHandler extends Handler {
        WeakReference<HandlerActivity> mWeakActivity;

        public MyHandler(HandlerActivity activity) {
            this.mWeakActivity = new WeakReference<HandlerActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            final HandlerActivity mActivity = mWeakActivity.get();
            if (mActivity != null) {
                // 处理消息
            }
        }
    }

}

HandlerThread

Think about it, if we need to download A and B at the same time, it takes 6s to download A and 5s to download B. After they are downloaded, the Toast information can come out. At this time, HandlerThread is one of the solutions. So what exactly is HandlerThread?

  • HandlerThread is a kind of thread.
  • The difference between HandlerThread and ordinary Thread is that HandlerThread will provide its own Looper object of the thread when it is created.

Therefore, if you understand the relationship between Looper, Message, Handler, and MessageQueue that I mentioned earlier, it is very clear what HandlerThread is. As we all know, when we create Actvity, the system will automatically help us initialize the Looper of the main thread, and then this Looper will manage the message queue of the main thread. But when we create a child thread, the system will not help us to create the Looper of the child thread, we need to create it manually, as follows:

    new Thread(){
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            Handler mHandler = new Handler(Looper.myLooper());
            Looper.loop();
        }
    }.start();

So HandlerThread encapsulates the creation process of Looper internally. As you can see from the source code, HandlerThread is integrated into Thread, and then overrides the run method to create Looper, thereby exposing the Looper object of the thread through the getLooper method

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    
    ...
    
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
    
    ...
}    

So with HandlerThread, we can easily create a child thread that contains Looper:

final HandlerThread mHandlerThread = new HandlerThread("HandlerThread");

mHandlerThread.start();

Handler mHandler = new Handler(mHandlerThread.getLooper());

Use HandlerThread to download Demo of A and B at the same time:

image

Code:

public class HandlerThreadActivity extends AppCompatActivity {
    private TextView tv_A, tv_B;

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

        tv_A = (TextView) findViewById(R.id.txt_dlA);
        tv_B = (TextView) findViewById(R.id.txt_dlB);

        final Handler mainHandler = new Handler();

        final HandlerThread downloadAThread = new HandlerThread("downloadAThread");
        downloadAThread.start();
        Handler downloadAHandler = new Handler(downloadAThread.getLooper());

        // 通过postDelayed模拟耗时操作
        downloadAHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), "下载A完成", Toast.LENGTH_SHORT).show();
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        tv_A.setText("A任务已经下载完成");
                    }
                });
            }
        }, 1000 * 5);


        final HandlerThread downloadBThread = new HandlerThread("downloadBThread");
        downloadBThread.start();
        Handler downloadBHandler = new Handler(downloadBThread.getLooper());

        // 通过postDelayed模拟耗时操作
        downloadBHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), "下载B完成", Toast.LENGTH_SHORT).show();
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        tv_B.setText("B任务已经下载完成");
                    }
                });

            }
        }, 1000 * 7);
    }
}

Summarize

Since Android's UI update can only be done on the main thread, Handler is a very important set of UI thread update mechanisms in Android. Although we can reduce the code writing of a lot of Handlers with the help of many frameworks, in fact, the bottom layer of many frameworks The implementation is to update the UI through Handler, so it can be seen how important it is for us to master the Handler, so this is also one of the high-frequency test points for many interviewers in the interview. Although Handler is a very convenient existence for developers, we cannot deny that it also has shortcomings. If it is not handled properly, the memory leak caused by Handler is also a very headache for developers.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325849489&siteId=291194637