Chapter 10 of exploring the art of Android Development - reading notes on Android's message mechanism

1. Overview of Android message mechanism

1.1 what is the message mechanism of Android?

The message mechanism of Android is to switch a task to the thread where the Handler is located for execution through the running mechanism of the Handler.

However, the handler class alone cannot switch a task to the thread where the handler is located; In fact, the operation of handler needs the support of MessageQueue and Looper. Handler is just the upper interface of Android message mechanism.

In other words, Android defines a Handler, which is directly oriented to developers and shields MessageQueue and Looper (not completely shielding Looper). Developers only need to deal with the Handler to use Android's message mechanism.

1.2 the handler is specially used to update the UI. Is this right? Why?

incorrect.

In the development process, we perform some time-consuming operations in the sub thread, such as reading files, reading databases, accessing the network, etc., get the data we need, and then display these data on the UI. At this time, directly operate the UI control in the sub thread to display the data. Android is not allowed and will throw an exception to us; The correct approach is to create a Handler object in the UI thread, use this Handler object in the child thread to switch the data to be displayed to the UI thread where the Handler is located, and then operate the UI control to display the data. This is the scenario where the Handler is used to update the UI.

Let's look at the actual code:

// Create a Handler object in the main thread
private Handler mainThreadHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (msg.what == 3) {
            Log.d(TAG, "handleMessage: msg.what=" + msg.what + ",msg.obj=" +
                    msg.obj + ",threadName=" + Thread.currentThread().getName());
            // Here is the main thread. You can rest assured to update the UI.
        }
    }
};
// Click the button to send a message from the child thread to the main thread
public void sendMessage2UIThread(View view) {
	// Start a child thread
    new Thread(new Runnable() {
        @Override
        public void run() {
            int what = 3;
            String obj = "hello, ui thread!";
            Log.d(TAG, "sendMessage2UIThread: what="+ what +",obj=" +
                    obj + ",threadName=" + Thread.currentThread().getName());
            mainThreadHandler.obtainMessage(what, obj).sendToTarget();
        }
    }, "work-thread").start();
}

Print the log as follows:

D/MainActivity: sendMessage2UIThread: what=3,obj=hello, ui thread!,threadName=work-thread
D/MainActivity: handleMessage: msg.what=3,msg.obj=hello, ui thread!,threadName=main

However, we can also switch the data from the main thread to the sub thread for execution. Here, a practical example is used to illustrate:

private Handler workThreadHandler;
private void startWorkThread() {
	// Start a child thread
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            // Create Handler object in child thread
            workThreadHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    if (msg.what == 2) {
                        Log.d(TAG, "handleMessage: msg.what=" + msg.what + ",msg.obj=" + 
                                msg.obj + ",threadName=" + Thread.currentThread().getName());
                    }
                }
            };
            Looper.loop();
        }
    }, "work-thread").start();
}
// Click the button to send a message from the main thread to the child thread
public void sendMessage2WorkThread(View view) {
    int what = 2;
    String obj = "hello, work thread!";
    Log.d(TAG, "sendMessage2WorkThread: what="+ what +",obj=" + 
            obj + ",threadName=" + Thread.currentThread().getName());
    workThreadHandler.sendMessage(workThreadHandler.obtainMessage(what, obj));
}

Click the button to print the log as follows:

D/MainActivity: sendMessage2WorkThread: what=2,obj=hello, work thread!,threadName=main
D/MainActivity: handleMessage: msg.what=2,msg.obj=hello, work thread!,threadName=work-thread

You can see that the data is indeed switched from the main thread to the child thread.

Therefore, we say that the Handler is not specifically used to update the UI, but is often used by developers to update the UI.

1.3 is it true that the UI cannot be updated in the child thread?

As we know, Android stipulates that accessing the UI should be carried out in the main thread. If the UI is updated in the sub thread, the program will throw an exception. This is because the UI will be verified in the ViewRootImpl class. Specifically, it is completed by the checkThread method.

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

Now let's set the text for the TextView control in the sub thread:

private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_check_thread_not_working);
    tv = (TextView) findViewById(R.id.tv);
    new Thread(() -> {
        SystemClock.sleep(1000L);
        tv.setText("I am text set in " + Thread.currentThread().getName());
    },"work-thread").start();
}

When running the program, an error will be reported:

2022-01-08 05:47:15.391 9225-9252/com.wzc.chapter_10 E/AndroidRuntime: FATAL EXCEPTION: work-thread
    Process: com.wzc.chapter_10, PID: 9225
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)
        at android.view.View.requestLayout(View.java:17476)
        at android.view.View.requestLayout(View.java:17476)
        at android.view.View.requestLayout(View.java:17476)
        at android.view.View.requestLayout(View.java:17476)
        at android.view.View.requestLayout(View.java:17476)
        at android.widget.TextView.checkForRelayout(TextView.java:6871)
        at android.widget.TextView.setText(TextView.java:4057)
        at android.widget.TextView.setText(TextView.java:3915)
        at android.widget.TextView.setText(TextView.java:3890)
        at com.wzc.chapter_10.CheckThreadNotWorkingActivity.lambda$onCreate$0$CheckThreadNotWorkingActivity(CheckThreadNotWorkingActivity.java:19)
        at com.wzc.chapter_10.-$$Lambda$CheckThreadNotWorkingActivity$Thy_KGiEr_duYPMycxt-0lYIEGo.run(lambda)
        at java.lang.Thread.run(Thread.java:818)

You can see that the exception is thrown in the checkThread method of ViewRootImpl class:

Only the original thread that created a view hierarchy can touch its views.

mThread in the checkThread method is the UI thread. Now we call the checkThread method in the child thread, then thread Currentthread () is a child thread, so mThread= Thread. If currentthread() determines to be true, it will enter the if branch and throw a CalledFromWrongThreadException exception.

But if I put systemclock sleep(1000L); What happens if this line of code is commented out?

Run the program, and the effect is as follows:

Yes, this is not an illusion. Updating the UI in the child thread succeeded.

Then, the question comes again. Why is there an error when the sub thread updates the UI when sleeping, but the sub thread updates the UI successfully when not sleeping?

This is because when there is sleep, when updating the UI, the ViewRootImpl object has been created successfully, and the checkThread method will be executed; When there is no sleep, the ViewRootImpl object is not created when the UI update operation is performed, and the checkThread method is not executed.

In fact, without hibernation, we just don't use the checkThread method when setting the text in the sub thread. When the text is really drawn to the screen, it is still carried out in the UI thread.

Look at this method again,

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

As long as mThread and thread If currentthread() is the same, no exception will be reported, and the Chinese meaning of the exception: only the thread that originally created the View system can operate its View. This doesn't mean that you don't let child threads update the UI at all. What I really want to explain here is that the thread that created the View system should operate its View; In other words, if a thread operates a View created by another thread, it is not allowed.

So, if we just add the view in the child thread, will this be a problem?

We add a Window in the sub thread. The code is as follows:

public void createUIInWorkThread(View view) {
    new Thread(() -> {
        // The Looper object is used here, because the ViewRootHandler object will be created in ViewRootImpl.
        Looper.prepare();
        TextView tv = new TextView(MainActivity.this);
        tv.setBackgroundColor(Color.GRAY);
        tv.setTextColor(Color.RED);
        tv.setTextSize(40);
        tv.setText("i am text created in " + Thread.currentThread().getName());
        WindowManager windowManager = MainActivity.this.getWindowManager();
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
                0, 0, PixelFormat.TRANSPARENT);
        layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
        layoutParams.gravity = Gravity.CENTER;
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        windowManager.addView(tv, layoutParams);
        Looper.loop();
    }, "work-thread").start();
}

Click the button to view the effect:

You can see that it is possible to operate the UI in the sub thread.

Here we summarize:

  • Generally, the UI of thread B cannot be operated in thread A; However, if the ViewRootImpl of thread B has not been created, the checkThread method will not be used and exceptions will not be thrown. Finally, thread B completes the UI operation.

  • It is possible to operate the view system created by the thread in a thread. In other words, a thread can only operate its own UI.

1.4 why does Android system use single thread model to access UI?

The UI control of Android is not thread safe. If it is accessed concurrently in multiple threads, the UI control may be in an unexpected state; If the access to UI controls is locked, the logic of UI access will become complex and the efficiency of UI access will be reduced.

Therefore, Android uses a single thread model to handle UI operations.

1.5 why is the Handler class the upper interface of Android message mechanism?

From the perspective of construction method

It can be divided into two categories: those that can pass Looper objects and those that cannot pass Looper objects.

We focus on the handler (callback, Boolean async) method that does not pass the Looper object, because this method is very representative.

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

The main functions of this method are:

  • Through looper The mylooper () method to get and hold the looper object through mLooper. Looper. The myloop () method will fetch the looper object corresponding to the current thread from the thread local variable ThreadLocal.
  • If the mloop object is still null, an exception will be thrown: "Can't create handler inside thread that has not called Looper.prepare()", which tells the current thread that looper is not called Prepare(), so the Handler cannot be created.
  • Get the MessageQueue object through the Looper object and assign it to the mQueue member variable.

When constructing the Handler object, you must hold the Looper object and MessageQueue object. That is, the Handler class combines the Looper object and MessageQueue object.

From the method of sending messages

The methods of sending messages are divided into two categories: postXXX method and sendXXX method.

The postXXX method is used to send a Runnable object, which will be wrapped into a Message object and sent to the Message queue;

The sendXXX method is used to send a Message object to the Message queue.

As can be seen from the call graph, both the postXXX method and the sendXXX method ultimately call the enqueuemessage (messagequeue, message MSG, long uptimemillis) method:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

The main functions of this method are:

  • Assign the Handler object to the target field of the Message object;
  • Call the enqueueMessage method of the MessageQueue object to add the message to the message queue.

In terms of message processing methods

When a message is fetched from the message queue, the dispatchMessage method of the Handler object is called to distribute the message

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
public interface Callback {
    public boolean handleMessage(Message msg);
}
private static void handleCallback(Message message) {
    message.callback.run();
}
public void handleMessage(Message msg) {
}

The main function of this method

For Message, it is the function of the callback interface: the Message object holds the Handler object and callbacks the Message object to the Handler by calling the dispatchMessage method of the Handler object.

For the Handler, it is used to distribute messages:

  • If the callback field of the Message object is not empty, a Runnable object is held inside the Message, and the handleCallback method is called to run the code encapsulated by the Runnable object;
  • If the callback field of the Message object is empty and the mCallback object is not null, the handleMessage method of the callback class is used to process the Message; This mCallback completes the assignment in the constructor of the Handler. The advantage of using this callback is that it is not necessary to subclass the Handler in order to override the handleMessage method of the Handler class;
  • If the callback field of the Message object is empty and the mCallback object is null, the handleMessage method of the Handler class is used to process the Message.

From the information obtained

Handler encapsulates a series of obtainMessage tools and methods to facilitate us to get the Message object.

From the removal message

Handler encapsulates the removeXXX method and internally delegates to the MessageQueue object to do the real work.

public final void removeMessages(int what) {
    mQueue.removeMessages(this, what, null);
}

To sum up: using Handler, you can combine MessageQueue object and Looper object, send messages, process messages, obtain message objects, and remove messages. Therefore, Handler is the upper interface of Android message mechanism.

1.6 what is the overall process of Android message mechanism?

Illustration:

  • Create the handler object handler in the main thread. By default, the Looper object of the main thread and the corresponding MessageQueue object are used;
  • The worker thread sends the message through the sending message method of the handler object handler, and finally adds the message to the message queue through the enqueueMessage method of the MessageQueue object;
  • Looper. The loop () method runs on the thread in the Handler. Here, it runs on the main thread, loop The loop () method continuously obtains qualified Message objects from the Message queue;
  • After obtaining the qualified Message object, call back the Message to the Handler sending the Message through the dispatchMessage method of the target field held by the Message object (actually the Handler object sending the Message), so that the Message is received by the main thread.

2. Message mechanism analysis of Android

2.1 what are the usage scenarios of ThreadLocal?

Scenario 1: when some data is thread scoped and different threads have different thread copies, consider using ThreadLocal.

For the Handler, it needs to get the Looper of the current thread (the scope of the Looper is the thread, and different threads have different loopers). At this time, using ThreadLocal can easily access the Looper in the thread.

For SimpleDateFormat, it is not thread safe, that is, an exception will be thrown when multiple threads operate concurrently. See the demo code as follows:

public class SimpleDateFormatDemo1 {
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                5, 50, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100));

        List<String> data = Arrays.asList(
                "2021-03-01 00:00:00",
                "2020-01-01 12:11:40",
                "2019-07-02 23:11:23",
                "2010-12-03 08:22:33",
                "2013-11-29 10:10:10",
                "2017-09-01 14:14:14",
                "2021-04-01 15:15:15"
        );

        for (String date : data) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(sdf.parse(date));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        threadPool.shutdown();

    }
}

When running this program, such an exception will occur:

java.lang.NumberFormatException: For input string: ".103E2103E2"
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.java.advanced.features.concurrent.threadlocal.SimpleDateFormatDemo1$1.run(SimpleDateFormatDemo1.java:45)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

In order to solve the unsafe problem of SimpleDateFormat thread, we can use a synchronized modified method to encapsulate its parse method, but it will compete for locks under multithreading, which is not efficient. ThreadLocal is used to create an exclusive SimpleDateFormat object copy for each thread. When a thread needs to obtain the SimpleDateFormat object for operation, it obtains its own copy, which has no impact on the SimpleDateFormat object copies of other threads, so that thread insecurity will not occur.

public class SimpleDateFormatDemo2 {
    private static ThreadLocal<SimpleDateFormat> threadLocal = ThreadLocal.withInitial(new Supplier<SimpleDateFormat>() {
        @Override
        public SimpleDateFormat get() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    });

    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                5, 50, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100));

        List<String> data = Arrays.asList(
                "2021-03-01 00:00:00",
                "2020-01-01 12:11:40",
                "2019-07-02 23:11:23",
                "2010-12-03 08:22:33",
                "2013-11-29 10:10:10",
                "2017-09-01 14:14:14",
                "2021-04-01 15:15:15"
        );

        for (String date : data) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(threadLocal.get().parse(date));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        threadPool.shutdown();
    }
}

Run the program many times and it can be parsed normally.

In fact, jdk1 8 provides a thread safe DateTimeFormatter to replace the thread unsafe SimpleDateFormat. I won't elaborate here.

Scenario 2: use ThreadLocal to transfer objects under complex logic.

For example, for a task in a thread, its function call stack is deep, or the call chain has a third-party library that cannot be modified. At this time, we want to pass a listener parameter in. What should we do?

If it is your own code, you can modify it and add listener parameters to each function in the call chain, but there are many changes, which are easy to make mistakes and troublesome;

If the call chain has a third-party library that cannot be changed, the listener can be accessed by the thread as a static variable, but in multithreading, each thread must have its own listener object, so we need to manage these listeners with a collection (with the thread name as the key and the listener as the value). In this way, when obtaining the specified listener under multithreading, there will still be collection competition. So it's not good.

This problem can be solved by using ThreadLocal. The code is as follows:

private static ThreadLocal<Runnable> runnableThreadLocal = new ThreadLocal<>();
public void threadLocalargs(View view) {
   new Thread("thread1") {
       @Override
       public void run() {
           task();
       }
   }.start();
   new Thread("thread2") {
       @Override
       public void run() {
           task();
       }
   }.start();
}
private void task() {
    Runnable runnable = () -> Log.d(TAG, "run: " + Thread.currentThread().getName());
    runnableThreadLocal.set(runnable);
    method1();
}
private void method1() {
    method2();
}
private void method2() {
    method3();
}
private void method3() {
    runnableThreadLocal.get().run();
}

Print the log as follows:

D/MainActivity: run: thread1
D/MainActivity: run: thread2

2.2 why can ThreadLocal store and modify data in multiple threads without interference?

Let's take a look at the basic use of ThreadLocal:

private static ThreadLocal<Boolean> sBooleanThreadLocal = new ThreadLocal<>();
private static ThreadLocal<String> sStringThreadLocal = new ThreadLocal<>();
public void threadLocal_basic(View view) {
    sBooleanThreadLocal.set(true);
    log();
    new Thread("Thread#1") {
        @Override
        public void run() {
            super.run();
            sBooleanThreadLocal.set(false);
            sStringThreadLocal.set(Thread.currentThread().getName());
            log();
        }
    }.start();
    new Thread("Thread#2"){
        @Override
        public void run() {
            super.run();
            sStringThreadLocal.set(Thread.currentThread().getName());
            log();
        }
    }.start();
}
private void log() {
    Log.d(TAG, "["+ Thread.currentThread().getName() +"]"+ "sBooleanThreadLocal.get()=" + sBooleanThreadLocal.get());
    Log.d(TAG, "["+ Thread.currentThread().getName() +"]"+ "sStringThreadLocal.get()=" + sStringThreadLocal.get());
}

Print the log as follows:

D/MainActivity: [main]sBooleanThreadLocal.get()=true
D/MainActivity: [main]sStringThreadLocal.get()=null
D/MainActivity: [Thread#1]sBooleanThreadLocal.get()=false
D/MainActivity: [Thread#1]sStringThreadLocal.get()=Thread#1
D/MainActivity: [Thread#2]sBooleanThreadLocal.get()=null
D/MainActivity: [Thread#2]sStringThreadLocal.get()=Thread#2

It can be seen that although we access the same ThreadLocal object in different threads, the value they get from the ThreadLocal object is exactly the set value or the default value.

Here we are looking at jdk1 8 source code.

Let's take a look at the related class relationships first:

  • Each Thread object holds a ThreadLocal Threadlocalmap object threadLocals;

  • ThreadLocalMap is the static internal class of ThreadLocal. ThreadLocalMap maintains an Entry array table `;

  • Entry is the static internal class of ThreadLocal, which encapsulates a key value pair. Key is the threadload object, and value is the value corresponding to the generic type of ThreadLocal.

Draw the class diagram as follows:

The core code is as follows:

public class ThreadLocal<T> {
    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private Entry[] table;
    }
}
public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
}

Next, let's look at the get() and set() methods of ThreadLocal:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

The main functions of this method are:

  • Get the ThreadLocalMap variable held by the current thread;
  • Take the current ThreadLocal object as the key and the value corresponding to the generic type as the value, and store it in ThreadLocalMap.
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

The main functions of this method are:

  • Get the ThreadLocalMap variable held by the current thread;
  • From the ThreadLocalMap variable, take the ThreadLocal object as the key to find the corresponding Entry object;
  • Get the value part of the Entry object and return.

It can be seen from the set and get methods of ThreadLocal that the ThreadLocalMap object operated by ThreadLocal object is the ThreadLocalMap object of the current thread; Each ThreadLocalMap will hold an Entry array, so the Entry array of ThreadLocal object operation is the Entry array of the current thread, that is, read / write operations are the Entry array of the current thread, which does not involve other threads and will not affect other threads. Therefore, ThreadLocal can store and modify data in multiple threads without interference.

In order to better understand the thread isolation of ThreadLocal, draw a small example demonstrating ThreadLocal into a memory diagram, as follows:

2.3 how does message queue complete message insertion and reading operations?

Message queue is MessageQueue. It uses the boolean enqueueMessage(Message msg, long when) method to insert messages and Message next() to read messages (the message will be removed from the message queue when it is obtained).

MessageQueue maintains the message list through a single linked list data structure based on time sorting.

Let's take a look at its two main methods.

public final class MessageQueue {
	// Head node of linked list
	Message mMessages;
	boolean enqueueMessage(Message msg, long when) {
		...
		synchronized (this) {
			...
			msg.markInUse();
			msg.when = when;
			Message p = mMessages;
			boolean needWake;
			if (p == null || when == 0 || when < p.when) {
				// When there is no Message in the queue or the trigger time of the new Message is the earliest, the new Message should be used as the header node
				msg.next = p;
				mMessages = msg;
				needWake = mBlocked;
			} else {
				// The new Message should be inserted into the queue
				needWake = mBlocked && p.target == null && msg.isAsynchronous();
				Message prev;
				// Traverse the linked list to find the position where it should be inserted
				for (;;) {
					prev = p;
					p = p.next;
					if (p == null || when < p.when) {
						break;
					}
					if (needWake && p.isAsynchronous()) {
						needWake = false;
					}
				}
				// Insert a new Message
				msg.next = p; // invariant: p == prev.next
				prev.next = msg;
			}
			if (needWake) {
				nativeWake(mPtr);
			}
		}
		return true;
	}	
}
public final class MessageQueue {
	// Head node of linked list
	Message mMessages;
	Message next() {
		final long ptr = mPtr;
		if (ptr == 0) {
			return null;
		}
		int pendingIdleHandlerCount = -1; // -1 only during first iteration
		int nextPollTimeoutMillis = 0;
		for (;;) {
			if (nextPollTimeoutMillis != 0) {
				Binder.flushPendingCommands();
			}
			nativePollOnce(ptr, nextPollTimeoutMillis);
			synchronized (this) {
				final long now = SystemClock.uptimeMillis();
				Message prevMsg = null;
				Message msg = mMessages;
				// This branch is entered when there is a synchronization barrier message
				if (msg != null && msg.target == null) {
					// Get the next asynchronous message through the do while loop.
					// If no asynchronous message is obtained, the do while loop will not end.
					do {
						prevMsg = msg;
						msg = msg.next;
					} while (msg != null && !msg.isAsynchronous());
				}
				if (msg != null) {
					if (now < msg.when) {
						// If the trigger time of the next message is not up yet, set a timeout to wake up the queue when the trigger time arrives
						nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
					} else {
						// Get a message
						mBlocked = false;
						// Remove the obtained message from the linked list
						if (prevMsg != null) {
							prevMsg.next = msg.next;
						} else {
							mMessages = msg.next;
						}
						msg.next = null;
						// Returns the obtained message
						return msg;
					}
				} else {
					// No more messages.
					nextPollTimeoutMillis = -1;
				}
				if (mQuitting) {
					dispose();
					return null;
				}
				...// The IdleHandler section is omitted
			}
			...// The IdleHandler section is omitted
		}
	}
}

2.4 what is the role of looper in the message mechanism?

Loop plays the role of message loop in Android's message mechanism. It constantly checks whether a message is obtained from the MessageQueue through the loop() method: if a message is obtained, it will be immediately handed over to the Handler for processing. If a message is not obtained, it will be blocked all the time. If it is null, it will exit the message loop.

2.5 Looper.prepare() method and looper What is the difference between the preparemainlooper () method?

  • The Looper message loop created by the prepare() method can exit, but the message loop created by the prepareMainLooper() method cannot exit.

    The parameter passed by the internal call to prepare(boolean quitAllowed) in the prepare() method is true, while the parameter passed by the internal call to prepare(boolean quitAllowed) in the preparemainloop() method is false,

    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));
    }
    

    The function of this method:

    • Check whether the Looper object obtained from the thread local variable sThreadLocal is null. If it is not null, an exception will be thrown: each thread can only create one Looper object;

    • Call the Looper constructor to create a Looper object: the constructor creates a MessageQueue object and holds the current thread that creates the Looper

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

      quitAllowed is passed into the message queue. If this value is true, it means that it is allowed to exit the message loop; On the contrary, it is not allowed. The message loop of the main thread cannot exit.

    • Save the Looper object in the thread local variable sThreadLocal.

  • The prepare() method is used by the child thread to create the Looper object, and prepareMainLooper() is used by the main thread to create the Looper object.

2.6 loop's quit() method and quitsafe () method are both used to exit the message loop. What is the difference between them?

quit() will set an exit flag and directly exit the message loop;

Quitsafe() only sets an exit flag, and Looper will still exit after processing the non delayed messages in the message queue.

public final class Looper {
    final MessageQueue mQueue;
    public void quit() {
        mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }
}
public final class MessageQueue {
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            // Set exit flag to true
            mQuitting = true;
            if (safe) {
                // Remove all messages that need to be delayed, and keep the messages that need to be triggered for the message queue to continue processing
                removeAllFutureMessagesLocked();
            } else {
                // Remove all messages
                removeAllMessagesLocked();
            }
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
}

Whether the quit() or quitsafe () method is called, false will be returned if a message is added to the message queue.

public final class MessageQueue {
    boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            if (mQuitting) { // Exit is marked as true, enter the branch and return false.
                ...
                return false;
            }
            ...
        }
        return true;
    }
}

It should be noted that the loop is manually created in the sub thread and the loop() method of the loop is called. If the quit method is not actively called to exit the message loop, the sub thread will always be in a waiting state. Specifically, the message loop waits for the next message. Therefore, when you do not need a message loop, you must actively terminate the Looper.

2.7 Looper. Is it true that the loop () method exits when there is no message in the message queue?

That's not true. We use the method of counter evidence to illustrate, assuming looper The statement that the loop () method will exit when there is no message in the message queue is correct. Now we write the code in a sub thread as follows:

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();
    }
}

Then there must be no messages in the message queue now, so looper The loop () method will exit.

However, when we use mHandler to send messages in the main thread, we can still send them successfully and receive them in the child thread. It contradicts the hypothesis, so the hypothesis is not tenable. This statement is wrong.

Let's go and see where the loop() method return s:

public static void loop() {
    final Looper me = myLooper();
    
    final MessageQueue queue = me.mQueue;
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // If the obtained Message object is null, exit the Message loop.
            return;
        } 
        msg.target.dispatchMessage(msg);
        final long newIdent = Binder.clearCallingIdentity();      
        msg.recycleUnchecked();
    }
}

So when will the next method of MessageQueue return null?

When the loop's quit method is called, the message queue is marked to exit, and its next method will return null;

When loop's quitsafe method is called, the message queue is marked as exited. After processing the non delayed message, its next method will return null.

In fact, when there is no message in the message queue, it will not return when calling the next method. It will always block, so the loop method will also block there.

3. Message loop of main thread

3.1 how is the message loop model of the main thread established?

To build the main thread message loop, you need to call Looper Preparemainlooper () method to create MessageQueue object and Looper object. Let's see where this method is called:

You can see that this method is called in three places: activitythread java,Bridge.java and systemserver java,

The main function of ActivityThread starts the main thread message loop of App process, and the run method of SystemServer starts system_ The main thread message loop of the server process.

The main thread message loop of the App process is used to illustrate:

ActivityThread class is the initial class of App process, and its main function is the entry of App process. Looper. The prepareMainLooper () method is invoked in its main function.

We take scheduleLaunchActivity as an example to illustrate the message loop of the main thread. The key codes are as follows:

public final class ActivityThread {
	final ApplicationThread mAppThread = new ApplicationThread();
	final H mH = new H();
	
	private class ApplicationThread extends ApplicationThreadNative {
		
		public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
				ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
				IVoiceInteractor voiceInteractor, int procState, Bundle state,
				PersistableBundle persistentState, List<ResultInfo> pendingResults,
				List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
				ProfilerInfo profilerInfo) {
			updateProcessState(procState, false);
			ActivityClientRecord r = new ActivityClientRecord();
			... // Omit a series of assignment operations
			sendMessage(H.LAUNCH_ACTIVITY, r);
		}
		...// Other cross process calling methods are omitted, and only scheduleLaunchActivity method is retained
	}	
	
	private class H extends Handler {
		public static final int LAUNCH_ACTIVITY         = 100;
		... // Other constant definitions are omitted and only launch is retained_ ACTIVITY
	   
		public void handleMessage(Message msg) {
			switch (msg.what) {
				case LAUNCH_ACTIVITY: {
					Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
					final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
					r.packageInfo = getPackageInfoNoCheck(
							r.activityInfo.applicationInfo, r.compatInfo);
					handleLaunchActivity(r, null);
					Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
				} break;
				... // Other case branches are omitted and only launch is retained_ Activity branch
			}
		}
		
	}	
	
	private void sendMessage(int what, Object obj) {
		sendMessage(what, obj, 0, 0, false);
	}

	private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
		Message msg = Message.obtain();
		msg.what = what;
		msg.obj = obj;
		msg.arg1 = arg1;
		msg.arg2 = arg2;
		if (async) {
			msg.setAsynchronous(true);
		}
		mH.sendMessage(msg);
	}
	
	private void attach(boolean system) {
		if (!system) {
			final IActivityManager mgr = ActivityManagerNative.getDefault();
				mgr.attachApplication(mAppThread);     
		} else {
			...
		}
	}

	public static void main(String[] args) {
		...
		Looper.prepareMainLooper();
		ActivityThread thread = new ActivityThread();
		thread.attach(false);
		if (sMainThreadHandler == null) {
			sMainThreadHandler = thread.getHandler();
		}
		AsyncTask.init();
		
		Looper.loop();
		throw new RuntimeException("Main thread loop unexpectedly exited");
	}
}

The key codes are described below:

  1. The main method is the entrance to the App process, where:

    • Through Looper Preparemainlooper() creates the Looper and MessageQueue of the main thread;

    • The ActivityThread object is instantiated, which initializes the member variables mAppThread and mH of the ActivityThread object in the main thread.

      final ApplicationThread mAppThread = new ApplicationThread();
      final H mH = new H();
      

      ApplicationThread and H are internal classes of ActivityThread. ApplicationThread exists as the server object of iaapplicationthread binder interface, and iaapplicationthread is system_ The server process is a bridge to initiate communication with the App process; H is a subclass of the Handler class.

    • Call the attach(false) method of the ActivityThread object and internally set mAppThread to AMS, which is equivalent to the App process setting a callback interface object to AMS, so that AMS can communicate with the App process through mAppThread.

    • Call looper Loop () method to start the message loop of the main thread.

  2. When AMS needs to initiate a scheduleLaunchActivity request to the client process, it will initiate the request through the iaapplicationthread client object it holds. After being driven by the binder, AMS will call the scheduleLaunchActivity method of ApplicationThread. It should be noted that because it is cross process communication, At this time, the scheduleLaunchActivity method of ApplicationThread runs in the binder thread pool of the client process.

  3. Then call the sendMessage method of ActivityThread to encapsulate the data to be sent into a Message object, and then call the sendMessage method of mH.

  4. The message is received in the hanldeMessage method of class H, which completes the data switching from the binder thread pool to the main thread.

reference resources

  1. Can Android sub threads really not update the UI?
    It explains the reason why the sub thread cannot operate the main process UI, as well as the example of demonstrating that the sub thread can operate the main process UI, mainly whether the checkThread method is used.
  2. Can Android sub threads really not update UI
    It shows that it is possible to update the UI in the child thread, as long as the View system is created by itself.
  3. What is the practical use of ThreadLocal in Java- Ao Bing
  4. ThreadLocal usage and principle - Ao Bing
  5. Interviewer: "your resume says you are familiar with the Handler mechanism. Let's talk about IdleHandler?"
  6. Have you heard of IdleHandler in Handler?
  7. Temporary cramming: Principle Analysis and wonderful use of IdleHandler
  8. Android pit avoidance Guide: let's talk about the pit of IdleHandler from practical experience
  9. You know android's messagequeue Idlehandler?

Added by izlik on Mon, 10 Jan 2022 01:34:19 +0200