剛剛有個羣友問我,爲何個人子線程更新了 UI 沒報錯?藉此,糾正一些Android 程序員的一個知識誤區

開門見山:

這個誤區是:子線程不能更新 UI ,其應該分類討論,而不是絕對的。java

半小時前,個人 XRecyclerView 羣裏面,一位羣友私聊我,問題是:面試

爲何個人子線程更新了 UI 沒報錯?異步

我叫他發下代碼我看,以下,十分簡單的代碼。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    title = (TextView) findViewById(R.id.title_tips);
    doGet("http;//www.baidu.com", new Callback() {
        @Override
        public void onFailure(Request request, IOException e) {

        }
        @Override
        public void onResponse(Response response) throws IOException {
            title.setText(response.body().string()); // 這裏在子線程更新了 text
        }
    });
}

private void doGet(String url,Callback callback) {
    OkHttpClient client = new OkHttpClient();

    Request.Builder builder = new Request.Builder();
    Request request = builder.url(url).get().build();

    client.newCall(request).enqueue(callback);
}複製代碼

簡單解析下。他用了 OkHttp 的異步 enqueue 的請求,並在成功後更新了 textView 的 text。ide

明確一點:

  • okhttp 的同步異步的回調都是在子線程裏面的。

那麼這樣來講,按照咱們被一直灌輸的原理: 子線程不能刷新UI,上面這段代碼妥妥地爆錯啊。源碼分析

而我要說的是:

上面的代碼不必定爆錯,它還會穩穩的順利執行。ui

你十分懷疑了?

你能夠嘗試下。嫌麻煩,你能夠運行下下面這段通透的子線程更新UI代碼url

public class TestActivity extends Activity {
    private TextView title;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        title = (TextView) findViewById(R.id.title_tips);
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        // 子線程更新UI
                        title.setText("我 tm 妥妥地執行完畢");
                    }
                }
        ).start();
    }
}複製代碼

試了的都知道,真 tm 執行了沒爆錯。spa

顛覆了嗎?

緣由

在看到他發給個人代碼,onCreate 裏面的部分,一切已經明瞭,這也是我以前面試幾年經驗的人設過的坑。下面我直接講緣由,源碼分析那些大家本身去看吧,你應該去看線程

  • 子線程不能更新 UI 的限制是 viewRootImpl.java 內部限制了
    void checkThread() {
      // 該方法是 viewRootImpl.java 內部代碼
      if (mThread != Thread.currentThread()) {
          throw new CalledFromWrongThreadException(
                  "Only the original thread that created a view hierarchy can touch its views.");
      }
    }複製代碼
  • 對組件 Activity 而言,viewRootImpl 的初始化在 onCreate 以後,onResume 以後。
  • 若是你的子線程更新代碼在知足下面的條件下,那麼它能夠順利運行:
    • 修改應用層的 viewRootImpl.java 源碼,解除限制
    • 把你更新代碼寫在 onResume 以前,例如 onCreate 裏面,且,更新之際要趕在 viewRootImpl 初始化以前。

修改驗證 --- 拋出錯誤

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    title = (TextView) findViewById(R.id.title_tips);
    new Thread(
            new Runnable() {
                @Override
                public void run() {
                    try {
                        // 等待 onResume 執行完,讓 viewRootImpl 初始化完成
                        Thread.sleep(3000); // ---------- 這裏,看這裏
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    title.setText("我執行不了");
                }
            }
    ).start();
}複製代碼

相關文章
相關標籤/搜索