子线程可以更新UI吗

  • 尝试直接在子线程中更新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.MainActivity2.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吗)