美文网首页程序员
子线程可以更新UI吗

子线程可以更新UI吗

作者: 小虫虫奇遇记 | 来源:发表于2020-08-10 10:22 被阅读0次
    • 尝试直接在子线程中更新text
     override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            var text: TextView = findViewById(R.id.text)
            Thread(Runnable {
                text.text = "can I change you?"
            }).start()
    }
    
    image.png

    可以看到界面正常展示,textView的内容被更新且没有crash.
    那我们就能得出可以在子线程中随意更新UI的结论了吗?

    • 在thread中加上延时呢?
     Thread(Runnable {
                Thread.sleep(300)
                text.text = "can I change you?"
            }).start()
    

    运行,竟然崩溃了。。

    2020-08-09 10:55:51.895 26802-26858/com.drinkwater.meng.myapplication E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.drinkwater.meng.myapplication, PID: 26802
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
    at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
    at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
    at android.view.View.requestLayout(View.java:23093)
    at android.view.View.requestLayout(View.java:23093)
    at android.view.View.requestLayout(View.java:23093)
    at android.view.View.requestLayout(View.java:23093)
    at android.view.View.requestLayout(View.java:23093)
    at android.view.View.requestLayout(View.java:23093)
    at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
    at android.view.View.requestLayout(View.java:23093)
    at android.widget.TextView.checkForRelayout(TextView.java:8908)
    at android.widget.TextView.setText(TextView.java:5730)
    at android.widget.TextView.setText(TextView.java:5571)
    at android.widget.TextView.setText(TextView.java:5528)
    at com.drinkwater.meng.myapplication.MainActivityonCreate2.run(MainActivity.kt:43)

    异常翻译:只有创建这个view的线程才能操作这个view!

    注意此时我们的子线程都在oncreate中,那如果放在onresume中呢?

      override fun onResume() {
            super.onResume()
            Thread(Runnable {
                 // Thread.sleep(300)  放在onResume中后,不加延迟不会崩溃,加延时的话,延时短的情况下偶尔崩溃,长的话必崩
                text.text = "can I change you?"
            }).start()
            Log.d("TAG", "onResume()")
        }
    

    为什么加了长延迟,就必崩呢?
    看下崩溃的堆栈,发现是在ViewRootImpl.requestLayout的时候checkThread 方法中检查线程

        @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }
        void checkThread() {
            if (mThread != Thread.currentThread()) {
                throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its views.");
            }
        }
    //那这个mThread又是什么时候赋值的呢?
      public ViewRootImpl(Context context, Display display) {
          ...
            mThread = Thread.currentThread();
               ....
    }
    //可以看出mThread是在ViewRootImpl初始化的时候赋值的,那ViewRootImpl初始化是什么时候呢?其实在onResume时,最终会调用到WindowManagerGlobal.addView()之中。而这里也就可以看ViewRootImpl的“管理逻辑”:
    
    public final class ActivityThread {
      @Override
      public void handleResumeActivity(...){
        //...
        windowManager.addView(decorView, windowManagerLayoutParams);
      }
    }
    
    //WindowManagerImpl.java
    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow){
    
    ...
    //初始化ViewRootImpl的地方
    ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
    
    view.setLayoutParams(wparams);
    
    mViews.add(view);
    
    mRoots.add(root);
    
    mParams.add(wparams);
    //最终调用ViewRootImpl.setView开始刷新绘制View
    root.setView(view, wparams, panelParentView);
    ...
    }
    

    由于ViewRootImpl初始化是在onResume 中调用的,也就是在主线程调用,因此ViewRootImpl的mThread在onResume后才被赋值,因此之后子线程中更新text,调用到requestLayout的时候检查线程就会异常崩溃。

    在onCreate中子线程不加延时不崩溃是因为此时ViewRootImpl还没完成初始化,还没开始绘制,绘制是在onresume中调用了ViewRootImpl.setView之后开始的,就会把绘制之前对view设置的属性进行绘制。

    进阶

    • ViewPropertyAnimator Android 5.0之后通过RenderThread实现异步layout,
      measure 从而实现异步动画。只能通过反射使用
    ViewPropertyAnimator animator = clickTest.animate().scaleX(4).setDuration(2000);
            setViewPropertyAnimatorRT(animator,createViewPropertyAnimatorRT(clickTest));
            animator.start();
    
     private static Object createViewPropertyAnimatorRT(View view) {
            try {
                final Class<?> animRtClazz = Class.forName("android.view.ViewPropertyAnimatorRT");
                final Constructor<?> animRtConstructor = animRtClazz.getDeclaredConstructor(View.class);
                animRtConstructor.setAccessible(true);
                return animRtConstructor.newInstance(view);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private static void setViewPropertyAnimatorRT(ViewPropertyAnimator animator, Object rt) {
            try {
                final Class<?> animRtClazz = Class.forName("android.view.ViewPropertyAnimatorRT");
                final Field animRtField = animRtClazz.getDeclaredField("mRTBackend");
                animRtField.setAccessible(true);
                animRtField.set(animator,rt);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    缺点是 使用限制较多,不能使用监听器,不能开启硬件加速等。

    相关文章

      网友评论

        本文标题:子线程可以更新UI吗

        本文链接:https://www.haomeiwen.com/subject/jkrldktx.html