美文网首页程序员
子线程可以更新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线程是否可以更新UI

    可以,在onCreate函数子线程是可以更新UI的因为通常的子线程更新UI的报错是ViewRootImpl类的ch...

  • 子线程可以更新UI吗

    尝试直接在子线程中更新text 可以看到界面正常展示,textView的内容被更新且没有crash.那我们就能得出...

  • 封装工具类无法使用runOnUiThread解决办法

    由于Android中不能在子线程中更新ui,所以平时在子线程中需要更新ui时可以使用Android提供的RunOn...

  • iOS多线程之3.NSThread的线程间通信

      我们把一些耗时操作放在子线程,例如下载图片,但是下载完毕我们不能在子线程更新UI,因为只有主线程才可以更新UI...

  • 如何做到在子线程更新 UI?

    一般来讲,子线程是不能更新 UI 的,如果在子线程更新 UI,会报错。 但在某种情况下直接开启线程更新 UI 是不...

  • Android下的消息机制

    Android下的消息机制 子线程不可以修改UI 只有主线程才可以修改UI 如果子线程想要更新UI就必须利用消息机...

  • iOS子线程操作UI

    首先声明一点:子线程里面是可以更新UI的。 之所以说子线程不能操作UI是因为UIKit不是线程安全的。UI操作涉及...

  • Android消息机制

    一:子线程更新UI 安卓是单线程模型,那么子线程中是否能更新UI呢,答案是可以。 我们可以自己给它一个ViewRo...

  • Android在线程中更新UI和在协程中更新UI

    1、在子线程里面更新UI 我们都知道Android只能在主线程里面对UI更新,所以谷歌提供了很多在子线程里面更新U...

  • 线程通讯详解

    关于子线程能否更新UI的思考线程通讯详解线程池-多线程的高效使用姿势 上文我们说到了关于子线程中能否更新UI的问题...

网友评论

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

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