The purpose of the Handler

Handler is mainly used for asynchronous message processing, mainly to solve the problem that the child thread cannot access the UI.

Initialization of the Handler

There are two common ways to use it: first, initialize a handler directly and override its handleMessage method, or pass a Handler.Callback object and override the handleMessage method in the handler. Callback class.

private Handler handler1 = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg); }};private Handler handler2 = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
        return false; }});Copy the code

In the Handler method:

public void dispatchMessage(@NonNull Message msg) {
    if(msg.callback ! =null) {
        handleCallback(msg);
    } else {
        if(mCallback ! =null) {
            if (mCallback.handleMessage(msg)) {
                return; } } handleMessage(msg); }}Copy the code

Send a message

  • handler.sendMessage(Message msg)
  • handler.post(Runnable r)The runnable object is eventually wrapped in a Message.

Create Handler in child thread

Calling new Handler() directly from a child thread will result in an error:

2021-05-31 16:15:52.447 28913-29688/com.fred.app E/AndroidRuntime: FATAL EXCEPTION: Thread3 Process: com.fred.app, PID: 28913 java.lang.RuntimeException: Can't create handler inside thread Thread[thread-3,5,main] that has not called Looper. Prepare () at android.os.Handler.<init>(Handler.java:207) at android.os.Handler.<init>(Handler.java:119) at com.fred.app.MainActivity.lambda$sendMsg$3$MainActivity(MainActivity.java:95) at com.fred.app.-$$Lambda$MainActivity$wS6JjKtF31xh-2WedxDQY0yoNJA.run(Unknown Source:2) at java.lang.Thread.run(Thread.java:919)Copy the code

The error message is also clear: Handler could not be created in the child thread because the looper.prepare () method was not called. The exception message is thrown in the Handler constructor. So, if we call looper.prepare () once in the child thread, new Handler() should not be a problem.

Handler, Looper, MessageQueue

Binding of the main thread to Looper

sequenceDiagram
ActivityThread->>Looper: Looper.prepareLooper()
Looper-->>Looper: prepare()
Looper-->> ThreadLocal: set(new Looper(quitAllowed);

Looper.preparemainlooper () is called in the main method of ActivityThread; This method calls prepare() in Looper, which calls set(new Looper(quitAllowed) in ThreadLocal. Bind the current thread to the Looper to be created. When new Looper(quitAllowed) is created, a MessageQueue is initialized, and the corresponding Looper and MessageQueue in the main thread are created.

Looper and MessageQueue

Looper will have an instance of MessageQueue,

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
Copy the code

The Handler and stars

Let’s look at the Handler constructor:

public Handler(@Nullable Callback callback, boolean async) {... mLooper = Looper.myLooper();if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Copy the code

In the stars

public static @Nullable Looper myLooper(a) {
    return sThreadLocal.get();
}
Copy the code

We can see from the Handler constructor that:

Set Looper in Handler

When Handler initializes, it calls looper.mylooper () to fetch a Looper object from the ThreadLocal. From the previous analysis, if this method is called from the main thread, the Looper must already be initialized. So the return value must not be null. MyLooper () must be null, and an exception will be thrown. The solution is to call looper.prepareLooper () once in the child thread.

Handler initialization cannot directly new a Looper, because if you new a Looper, you cannot guarantee the uniqueness of the Looper. In the prepare method for Looper, a Looper is fetched from ThreadLocal and the resulting Looper is bound to the current thread.

Set MessageQueue in Handler

mQueue = mLooper.mQueue;

Sending of messages

Calling sendMessage or a similar method in the Handler will eventually put the message on the message queue

Consumption of messages

Using the main thread as an example, looper.prepareMainLooper () is called in the main method of the main thread. Looper.loop() is called after the method; Method, which has an infinite loop, constantly to messages from the message queue, take to the message after it performs MSG. Target. DispatchMessage (MSG); We know that msg.target corresponds to a Handler object, so this method ends up calling a Handler’s dispatchMessage method. The dispatchMessage method eventually calls the Handler’s handleMessage method to complete the message distribution.

The Handler and Message

When sendMessage is in Handler, the enqueueMessage method is called, which binds Message to Handler.

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
    // The MSG target attribute points to the current handler
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code

Handler memory leaks. Procedure

public class MainActivity extends AppCompatActivity {

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

    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            startActivity(new Intent(MainActivity.this, OrderActivity.class));
            System.out.println("[App Log]-------------------------------->handleMessage"); }};private void initView(a) {
        findViewById(R.id.tv_id).setOnClickListener(v -> sendMsg());
    }

    private void sendMsg(a) {
        new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Message msg = new Message();
            msg.what = 1;
            handler.sendMessage(msg);
        }).start();
    }

    @Override
    protected void onDestroy(a) {
        super.onDestroy();
        handler.removeMessages(1); }}Copy the code

As shown in the code above, clicking on the button triggers the sendMsg method, which will sleep(5000) and then send a message. The handler will jump to another activity after receiving the message. We removed the message that was sent in onDestory. If we press the system’s back key after pressing the button, the activity’s onDestory will execute, and handler.removemessage (1) will execute, since the handler message in sendMsg has not been sent. So the removeMessage method doesn’t actually delete the message. Eventually the methods in the handleMessage will execute anyway, causing a memory leak.

The solution is simple, as long as we’re hereonDestoryI’m going to set handler tonull“And then make a non-null judgment when sending a message.

Or we could do it this way, if we write it in sendMsg

private void sendMsg() {
    new Thread(() -> {
        Message msg = new Message();
        msg.what = 1;
        handler.sendMessageDelayed(msg, 5000);
    }).start();
}
Copy the code

The MSG will be pushed to the messgeQueue corresponding to the handler, but will be sent after 5 seconds, but since the onDestory has removed the message from the message queue, it can be avoided in this way.

Three message types

A synchronous message

We usually use, sent messages are synchronous messages.

Asynchronous messaging

A message set to asynchronous via msg.setasynchronous (true) was introduced in API Level 22(after Android 5.0).

Barrier message

Also known as synchronous barrier messages, which have no corresponding target, i.e. no corresponding handler, and need to use reflection to call, belong to the system’s hidden API. Android has become increasingly restrictive in this regard, disallowing reflection calls on older versions.

Insert barrier message

MessageQueue queue = mHandler.getLooper().getQueue();
Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
method.setAccessible(true);
token = (int)method.invoke(queue);
Copy the code

Remove barrier messages

MessageQueue queue = mHandler.getLooper().getQueue();
Method method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier".int.class);
method.setAccessible(true);
method.invoke(queue, token);
Copy the code

When a postSyncBarrier is called to issue a barrier message, the synchronous messages in the message queue are suspended, the asynchronous messages execute normally, and the synchronous messages continue after removeSyncBarrier is called. It is important to note that after we insert the barrier message, we must remember to remove it, otherwise the page will be in a hanging state. For example, click events on a page cannot be listened to.

HandlerThread

Thread is a Thread that can use a Handler. It has a Looper inside, and its run method starts the Looper message loop. Its use:

Thread t = new Thread(new Runnable() {
    @Override
    public void run(a) {
        Log.i(TAG, "thread start"); }}); HandlerThread handlerThread =new HandlerThread("Handler Thread");
Handler handler = new Handler(handlerThread.getLooper());
handler.post(t);
Copy the code

IntentService

IntentService inherits from Service and is an abstract class. IntentService can be used to execute time-consuming tasks in the background and will stop automatically. Since it is a service, its priority is much higher than that of a simple thread, which means it is not easy to be killed by the system. IntentService encapsulates the Handler and HandlerThread

  • IntentService: Most services don’t need to process multiple requests at the same time. In this case, we can use IntentService.
  • Working mechanism:
    • Create a default worker thread and put allintentsPassed to theonStartCommand
    • Create a queue, one at a timeintentPassed to theonHandleIntentSo we don’t have to worry about multithreading
    • If all requests are executed, they will be executedstopSelfmethods
  • IntentService encapsulatesHandlerThreadandHandler. In itsonCreateOne is created in theHandlerThreadObject, and then use that object’sLooperTo construct aHandlerBy thisHandlerTo process messages because of thisHandlerIs made up ofHandlerThreadtheLooperStructural, whileHandlerThreadIntentService is essentially a child thread, so it can perform background tasks.
  • Usage: Inherit from IntentService and implement itonHandleIntentmethods

A Demo

button2.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action"."ACTION_1"));
            startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action"."ACTION_2"));
            startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action"."ACTION_3"));
            startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action"."ACTION_4")); }}});Copy the code
public class MyIntentService extends IntentService {
    private final String TAG = "MyIntentService";


    public MyIntentService(a) {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getStringExtra("action");
        Log.i(TAG, "task:" + action + " Thread:" + Thread.currentThread());
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.i(TAG, "task:" + action + " handled");
    }

    @Override
    public void onDestroy(a) {
        super.onDestroy();
        Log.i(TAG, "service destory"); }}Copy the code

The execution result

10 to 22 09:51:10. 540, 15243-15470 / com. Fred I/MyIntentService: Task :ACTION_1 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:11.542 15243-15470/com.fred task:ACTION_1 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:11.542 15243-15470/com.fred I/MyIntentService: task:ACTION_1 handled 10-22 09:51:11.544 15243-15470/com. I/MyIntentService: task:ACTION_1 handled 10-22 09:51:11.544 Task :ACTION_2 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:12.544 15243-15470/com.fred task:ACTION_2 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:12.544 15243-15470/com.fred I/MyIntentService: task:ACTION_2 handled 10-22 09:51:12.545 15243-1547 /com. I/MyIntentService: task:ACTION_2 handled 10-22 09:51:12.545 Task :ACTION_3 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:13.545 15243-15470/com.fred task:ACTION_3 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:13.545 15243-15470/com.fred I/MyIntentService: task:ACTION_3 handled 10-22 09:51:13.546 15243-15470/com. I/MyIntentService: task:ACTION_3 handled 10-22 09:51:13.546 Task :ACTION_4 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:14.546 15243-15470/com.fred task:ACTION_4 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:14.546 15243-15470/com.fred I/MyIntentService: task:ACTION_4 handled 10-22 09:51:14.546 15243-15243/com. Fred I/MyIntentService: service destoryCopy the code

You can see that the tasks in IntentService are queued.

Frequently asked Questions

Child thread can’t manipulate UI?

Since the ViewRootImpl contains the checkThread operation, it is possible to operate the UI if the corresponding UI operation is completed before the checkThread, for example, in the activity’s onCreate method.

void checkThread(a) {
    if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views."); }}Copy the code

whylooper.loop()Will not cause the main thread to freeze?

  • Called in the main threadlooper.loop()Method, which has an infinite loop in it to ensure that the main thread will always execute and not exit.
  • Block in the loop when there is no messagequeue.next()In thenativePollOnce()Method, the thread frees CPU resources and goes to sleep until the next message arrives or a transaction occurs. So an infinite loop is not a huge drain on CPU resources.
  • The only operations that really get stuck are those that take too long to process a message, resulting in frame drops, ANR, and not the loop method itself.

reference

  • www.jianshu.com/p/4a5856866…