子線程可以更新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的內(nèi)容被更新且沒有crash.
那我們就能得出可以在子線程中隨意更新UI的結(jié)論了嗎?

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

運(yùn)行,竟然崩潰了。。

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)

異常翻譯:只有創(chuàng)建這個(gè)view的線程才能操作這個(gè)view!

注意此時(shí)我們的子線程都在oncreate中,那如果放在onresume中呢?

  override fun onResume() {
        super.onResume()
        Thread(Runnable {
             // Thread.sleep(300)  放在onResume中后,不加延遲不會(huì)崩潰,加延時(shí)的話,延時(shí)短的情況下偶爾崩潰,長的話必崩
            text.text = "can I change you?"
        }).start()
        Log.d("TAG", "onResume()")
    }

為什么加了長延遲,就必崩呢?
看下崩潰的堆棧,發(fā)現(xiàn)是在ViewRootImpl.requestLayout的時(shí)候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.");
        }
    }
//那這個(gè)mThread又是什么時(shí)候賦值的呢?
  public ViewRootImpl(Context context, Display display) {
      ...
        mThread = Thread.currentThread();
           ....
}
//可以看出mThread是在ViewRootImpl初始化的時(shí)候賦值的,那ViewRootImpl初始化是什么時(shí)候呢?其實(shí)在onResume時(shí),最終會(huì)調(diào)用到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);
//最終調(diào)用ViewRootImpl.setView開始刷新繪制View
root.setView(view, wparams, panelParentView);
...
}

由于ViewRootImpl初始化是在onResume 中調(diào)用的,也就是在主線程調(diào)用,因此ViewRootImpl的mThread在onResume后才被賦值,因此之后子線程中更新text,調(diào)用到requestLayout的時(shí)候檢查線程就會(huì)異常崩潰。

在onCreate中子線程不加延時(shí)不崩潰是因?yàn)榇藭r(shí)ViewRootImpl還沒完成初始化,還沒開始繪制,繪制是在onresume中調(diào)用了ViewRootImpl.setView之后開始的,就會(huì)把繪制之前對(duì)view設(shè)置的屬性進(jìn)行繪制。

進(jìn)階

  • ViewPropertyAnimator Android 5.0之后通過RenderThread實(shí)現(xiàn)異步layout,
    measure 從而實(shí)現(xiàn)異步動(dòng)畫。只能通過反射使用
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();
        }
    }

缺點(diǎn)是 使用限制較多,不能使用監(jiān)聽器,不能開啟硬件加速等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容