Interviewer: child thread really can not update the UI?

We talk about from 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)

Generally, we operate directly in the sub-thread UI, with no cut to the main thread handler, this error will be reported.

What if I say that I am here this error occurs in the main thread, you believe it?

The following is a specific code, handleAddWindow () is executed in the MainActivity in onCreate.

    private void handleAddWindow() {

        //子线程创建window,只能由这个子线程访问 window的view
        Button button = new Button(MainActivity.this);
        button.setText("添加到window中的button");
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                MyToast.showMsg(MainActivity.this, "点了button");
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
				//因为添加window是IPC操作,回调回来时,需要handler切换线程,所以需要Looper
                Looper.prepare();

                addWindow(button);

                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        button.setText("文字变了!!!");
                    }
                },3000);
                
				//开启looper,循环取消息。
                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() {
                button.setText("文字 you 变了!!!");
            }
        },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 设置 Window 属性
        layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        // type 设置 Window 类别(层级)
        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);
    }

Mainly: open sub-thread, and then add a system window, window only one button. 3 seconds and then directly change the Button Text child thread, then over one second, then changing the text button in the main thread.

(Which involves knowledge of Handler , window . Click here for the available knowledge)

Performing the following effects, seen after the App open, the upper left corner of the Button, changed after three seconds, and then after a one second crash.
Here Insert Picture Description

That did not update the UI thread for the wisdom error, the main thread being given it?

First, we look at the cause of the error description: Only the original thread that created a view hierarchy can touch its views translation that is. Only thread that creates a view of the tree in order to access its sub-view . He did not say the child thread must not access UI. That can guess, button is indeed to be added to the window, the child thread does have direct access to the child thread, the main thread to access does throw an exception. It seems this may explain the cause of the error.
Here's a detailed analysis below.

Mistakes in checkThread method of ViewRootImpl, and the UI update will come to this method:

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

(ViewRootImpl knowledge can poke here works View, )

By window -related knowledge, we know that this will create a window ViewRootImpl instance when called windowManager.addView add window:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...

        	ViewRootImpl root;
        	View panelParentView = null;
        ...
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
...
        }
    }

Then ViewRootImpl constructor will get the current thread,

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

So checkThread ViewRootImpl of (), the thread is really to take the current when you want to update the UI thread and add window for comparison, a thread is not being given the same opportunity.

Through the window of knowledge, we also know, Activity is also a window, the window is added handleResumeActivity in ActivityThread of (). ActivityThread is the main thread, so the view can only access the Activity in the main thread .

Generally, UI refers Activity of view, that is what we usually call the main thread to the UI thread of reason, in fact, should be the name for rigorous activity UI thread. And in our case, this sub-thread can also be referred to as button UI thread.

Why would that necessarily checkThread it ? According handler knowledge of:

Since UI controls are not thread safe in. Then why not lock it? First, lock UI will complicate access; the second is to lock access will reduce the UI efficiency, some threads will block access UI. So simply use a single-threaded process model UI operation, can be switched using a Handler.

We look at a problem, Toast can show the child thread do ? The answer is yes

        new Thread(new Runnable() {
            @Override
            public void run() {
                //因为添加window是IPC操作,回调回来时,需要handler切换线程,所以需要Looper
                Looper.prepare();

                addWindow(button);

                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        button.setText("文字变了!!!");
                    }
                },3000);

                Toast.makeText(MainActivity.this, "子线程showToast", Toast.LENGTH_SHORT).show();

                //开启looper,循环取消息。
                Looper.loop();
            }
        }).start();

In the above example, thread showToast, run discovery indeed possible. Because, according to window -related knowledge, but also know Toast window, show the process is the process of adding a Window.

Also note 1, this thread Looper.prepare () and Looper.loop (), which is necessary .
Since the addition of the process is a process window for the IPC and WindowManagerService, is performed when the back IPC binder thread pool, and there is the default ViewRootImpl Handler instance, the handler binder thread pool is used to switch to the current message thread. Further Toast also with IPC NotificationMamagerService, Handler instance is needed. Since the required handler, so that the looper thread is required. Another addition Activity IPC also interact with ActivityManagerService, while the main thread is the default there Looper's.
Spread out, like in the child thread show Toast, Dialog, popupWindow, custom window, just before and after the transfer Looper.prepare () and Looper.loop () can be.

Also note 2, the activity of the first onCreate onResume period in which to create a child thread to update the UI is also possible. This is not contrary to the above conclusion yet? Actually no, it says, because the Activity window Add performed after the first onResume, create that ViewRootImpl also after that, it will not be able checkThread up. In fact this period is not checkThread, simply because View not show up.

onCreate()中执行是OK的:

new Thread(new Runnable() {
    @Override
    public void run() {
        tv.setText("text");
    }
}).start();
Published 53 original articles · won praise 9 · views 10000 +

Guess you like

Origin blog.csdn.net/hfy8971613/article/details/103939085