Let’s start with an exception:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8820)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1530)
        at android.view.View.requestLayout(View.java:24648)
        at android.widget.TextView.checkForRelayout(TextView.java:9752)
        at android.widget.TextView.setText(TextView.java:6326)
        at android.widget.TextView.setText(TextView.java:6154)
        at android.widget.TextView.setText(TextView.java:6106)
        at com.hfy.demo01.MainActivity$9.run(MainActivity.java:414)
        at android.os.Handler.handleCallback(Handler.java:888)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:213)
        at android.app.ActivityThread.main(ActivityThread.java:8147)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101)
Copy the code

Normally, we would report this error if we were working directly on the UI in the child thread and didn’t cut to the main thread with handler.

Would you believe me if I said, well, I have this error right here on the main thread? = =

Here’s the code. HandleAddWindow () is executed by pressing onCreate on the MainActivity.

    private void handleAddWindow(a) {

        // The child thread creates the window. Only this child thread can access the window's view
        Button button = new Button(MainActivity.this);
        button.setText("Button added to window");
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                MyToast.showMsg(MainActivity.this."Ordered the button"); }});new Thread(new Runnable() {
            @Override
            public void run(a) {
				// Since adding a window is an IPC operation, the callback requires handler to switch threads, so Looper is required
                Looper.prepare();

                addWindow(button);

                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run(a) {
                        button.setText("The words have changed!!"); }},3000);
                
				// Start looper to loop through messages.
                Looper.loop();
            }
        }).start();

        Only the original thread that created a view hierarchy can touch its views.
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run(a) {
                button.setText("The word you has changed!!"); }},4000);
    }

    private void addWindow(Button view) {
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                0.0,
                PixelFormat.TRANSPARENT
        );
        // flag Sets the Window attribute
        layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        // type Sets the Window category (hierarchy)
        layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        }

        layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        layoutParams.x = 100;
        layoutParams.y = 100;

        WindowManager windowManager = getWindowManager();
        windowManager.addView(view, layoutParams);
    }
Copy the code

Basically: open child thread, then add a system window, window only has a button. Then, 3 seconds later, change the Button text directly in the child thread, and a second later, change the Button text again in the main thread.

(Handler and window are involved. You can click to see related knowledge)

The execution effect is as follows. It can be seen that after opening the App, the Button in the upper left corner changed 3 seconds later, and then crashed one second later.

Why does the child thread update the UI without an error and the main thread update the UI without an error?

Only the original thread that created a view hierarchy can touch its views. Only the thread that created the view tree can access its child views. It does not say that child threads must not access the UI. As you can guess, the button’s child thread is actually added to the window, the child thread can be accessed directly, and the main thread does throw an exception. That seems to explain the mistake. The following specific analysis.

The error occurred in the ViewRootImpl checkThread method, and UI updates will go to this 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

(View wrootimPL related knowledge can poke here View works)

Windowmanager. addView creates a ViewRootImpl instance for the window:

    public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {... ViewRootImpl root; View panelParentView =null; . root =newViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); . }}Copy the code

And then the ViewRootImpl constructor will get the current thread,

    public ViewRootImpl(Context context, Display display) { mContext = context; . mThread = Thread.currentThread(); . }Copy the code

So the checkThread() in the ViewRootImpl does compare the current thread that wants to update the UI to the thread that added the window. Different threads will report errors.

An Activity is also a window, and the window is added to the handleResumeActivity() of the ActivityThread. The ActivityThread is the main thread, so the Activity’s view can only be accessed in the main thread.

In general, the UI refers to the Activity’s view, which is why we usually call the main thread the UI thread, when the proper name is the Activity’s UI thread. In our case, this child thread could also be called Button’s UI thread.

Why do you need a checkThread? According to handler:

Because UI controls are not thread-safe. Then why not lock it? One is that locking complicates UI access. Second, locking reduces UI access efficiency and blocks some threads from accessing the UI. Instead, use the single-threaded model to handle UI operations, switching them with handlers.

Let’s look at another question, Toast can be in the child thread show? The answer is yes

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                // Since adding a window is an IPC operation, the callback requires handler to switch threads, so Looper is required
                Looper.prepare();

                addWindow(button);

                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run(a) {
                        button.setText("The words have changed!!"); }},3000);

                Toast.makeText(MainActivity.this."Child thread showToast", Toast.LENGTH_SHORT).show();

                // Start looper to loop through messages.
                Looper.loop();
            }
        }).start();
Copy the code

In the above example, showToast in the thread, the run discovery does work. According to the knowledge about Windows, Toast is also a window, and the process of show is the process of adding a window.

Also note 1, this thread has looper.prepare () and looper.loop (), which are necessary. Because adding Windows is IPC with WindowManagerService, IPC is executed with binder thread pools, and ViewRootImpl has Handler instances by default. This handler is used to switch messages from the binder thread pool to the current thread. In addition Toast with NotificationMamagerService IPC, also is the need to Handler instance. Since handler is required, looper is required for all threads. The Activity also interacts IPC with ActivityManagerService, and the main thread has Looper by default. Set looper.prepare () and looper.loop () to show Toast, Dialog, popupWindow, and custom window on the child threads.

Also note 2 that it is ok to create child threads to update the UI in between the activity’s onCreate and its first onResume. Doesn’t that contradict the above conclusion? The Activity window is added after the initial onResume, and the ViewRootImpl is created after that, so it cannot checkThread. In fact, there is no checkThread at this time, because the View is not displayed at all.

The execution in onCreate() is OK:new Thread(new Runnable() {
    @Override
    public void run(a) {
        tv.setText("text");
    }
}).start();
Copy the code

.

If you like this article, please help to like, bookmark and share it. Thanks!

Welcome to my public account: