Android開發之多執行緒環境下更新介面
Android應用程式的介面運行於獨立的執行緒裡。但有時候軟體需要單獨的執行緒來處理資料,然後再更新介面。這樣能夠保證介面執行的流暢又不至於影響使用者體驗。這裡的問題在於,UI只能被介面執行緒更新,在多執行緒環境下回出錯。本文會展示這種典型的錯誤,以及解決方案。
下面以計時器為例。在這個應用場景中,計時是在另一個執行緒裡面完成的,然後再由UI顯示出來。
多執行緒更新介面的常見問題
下面這段程式碼使用了一個Timer執行緒來嘗試更新UI,應用會意外終止。
final TextView dashboard = (TextView) findViewById(R.id.dashboard); stopwatch = new Stopwatch(); dashboard.setText(stopwatch.toString()); timer = new Timer("timer", true); timer.schedule(new TimerTask() { @Override public void run() { stopwatch.next(); dashboard.setText(stopwatch.toString()); } }, 1000, 1000);
檢視LogCat會發現如下資訊:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
這是因為android中,只有UI執行緒才能更新使用者介面,其他執行緒要更新介面只能通過傳送訊息來實現。
UI訊息佇列
與經典的MFC類似,Android的UI框架也是基於訊息佇列的。使用者的點選、動作等等,甚至後臺對介面的更新都是通過傳送訊息來實現的。
以點選事件為例,該事件會被UI框架加入到訊息佇列中。而UI執行緒則會依次從佇列中取出訊息,根據訊息的型別、內容,在UI介面樹中依次傳送。目標控制元件會相應該事件,從而完成介面的繪製和更新。
傳送訊息
要從別的執行緒向UI執行緒傳送訊息,執行動作,需要使用到android.os.Handler這個類。如果你線上程A中聲明瞭Hanlder的一個例項,執行緒B就可以使用這個例項向執行緒A的訊息佇列傳送訊息。下面將使用Handler類的postAtFrontOfQueue(Runnable)方法來實現計時程式。
timer = new Timer("timer", true); timer.schedule(new TimerTask() { @Override public void run() { stopwatch.next(); handler.postAtFrontOfQueue(new Runnable() { public void run() { dashboard.setText(stopwatch.toString()); } }); } }, 1000, 1000);
Handler例項會在UI執行緒中執行給定的Runnable例項,完成更新時間的動作。
這篇文章是我在寫X-Points的時候遇到的一個具體問題,寫在這裡總結一下。