1. 程式人生 > >Android開發之多執行緒環境下更新介面

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的時候遇到的一個具體問題,寫在這裡總結一下。

有用的資料