看到這個問題,相信很多小夥伴認爲,android開啓子線程更新UI,是不對的,爲什麼呢?
因爲只有主線程(UI線程)纔可以進行UI的修改,如果在子線程進行UI更新,會拋出異常:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views
是的,上面的表述是對的,但是,爲什麼說子線程又可以更新UI呢?這種說法可靠嗎?
現在,我們用事實(代碼)說話,來一段,
場景1:在MainActivity中開啓子線程,設置TextView的內容,佈局文件中有個TextView,比較簡單,這裏忽略,如下:
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.v("MainActivity", "onCreate執行...");
initView();
new Thread(new Runnable() {
@Override
public void run() {
Log.v("MainActivity", "準備UI更新...");
tv.setText("測試子線程更新UI");
Log.v("MainActivity", "完成UI更新...");
}
}).start();
}
private void initView() {
tv = (TextView) this.findViewById(R.id.tv);
}
@Override
protected void onResume() {
super.onResume();
Log.v("MainActivity", "onResume執行...");
}
}
可以在日誌看到:
onCreate執行...
onResume執行...
準備UI更新...
完成UI更新...
I/ActivityManager:Displayed com.rockykou.debug/.MainActivity: +868ms
運行結果:正常,界面顯示“測試子線程更新UI”
場景2:在子線程的更新UI代碼前,加上Thread.sleep(),其他與上面場景一致,如下:
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.v("MainActivity", "onCreate執行...");
initView();
new Thread(new Runnable() {
@Override
public void run() {
//添加部分
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.v("MainActivity", "準備UI更新...");
tv.setText("測試子線程更新UI");
Log.v("MainActivity", "完成UI更新...");
}
}).start();
}
private void initView() {
tv = (TextView) this.findViewById(R.id.tv);
}
@Override
protected void onResume() {
super.onResume();
Log.v("MainActivity", "onResume執行...");
}
}
可以在日誌看到:
onCreate執行...
onResume執行...
I/ActivityManager: Displayed com.rockykou.debug/.MainActivity: +1s33ms
準備UI更新...
AndroidRuntime: FATAL EXCEPTION: Thread-161
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4607)
...
運行結果:異常,app崩潰
比較下,場景1情況,子線程更新UI正常,場景2情況,加上Thread.sleep(5000),子線程更新UI異常,而且注意場景2中有個ViewRootImpl.checkThread方法,
怎麼解釋上述問題呢?
首先,這是兩個線程,
場景1,主線程執行onCreate,開啓子線程,主線程繼續執行onResume,接下來,子線程執行,這裏明確一點,ViewRootImpl類l對象沒有被創建,checkThread沒有被執行,直到子線程執行完,UI更新正常完成
場景2,主線程執行onCreate, 開啓子線程,主線程繼續執行onResume,子線程處於睡眠5秒狀態,當繼續執行子線程代碼時,ViewRootImpl對象已經被創建,checkThead方法被調用,檢查出當前線程(也就是子線程)與原來的線程(主線程)不一致,拋出異常
實際上,子線程執行更新UI代碼時,如果ViewRootImpl對象如果已經創建,UI更新異常,未被創建,UI更新正常,
最終,根本在cpu對時間片的分配上,
當主線程有足夠的時間執行時,ViewRootImpl對象被創建,子線程更新UI異常
當主線程沒有足夠時間(此時子線程在佔有時間片)執行時,ViewRootImpl對象未創建,子線程更新UI正常