android的UI操作不是安全執行緒的,同時也只有主線程才能夠操作UI,同時主線程對於UI操作有一定的時間限制(最長5秒)。為了能夠做一些比較耗時的操作(比如下載、開啟大檔案等),android提供了一些列機制。《android基礎知識02——安全執行緒》系列文章就是參考了網上許多網友的文章後,整理出來的一個系列,介紹了主要的方法。分別如下:
android基礎知識02——安全執行緒1:定義及例子
android基礎知識02——安全執行緒2:handler、message、runnable
android基礎知識02——安全執行緒3:Message,MessageQueue,Handler,Looper
android基礎知識02——安全執行緒4:HandlerThread
android基礎知識02——安全執行緒5: AsyncTask
在上文Timer的例子中,我們提到安全執行緒這個問題,下面我們來詳細介紹一下。
一、安全執行緒
首先來看一下安全執行緒的定義:
安全執行緒:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程啟動並執行結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是安全執行緒的,或者說:一個類或者程式所提供的介面對於線程來說是原子操作或者多個線程之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題 。
當一個程式第一次啟動的時候,Android會啟動一個LINUX進程和一個主線程。預設的情況下,所有該程式的組件都將在該進程和線程中運行 。主線程(Main Thread)主要負責處理與UI相關的事件,如:使用者的按鍵事件,使用者接觸螢幕的事件以及螢幕繪圖事 件,並把相關的事件分發到對應的組件進行處理。所以主線程通常又被叫做UI線 程。UI線程才能與Android UI工具包中的組件進行互動,在開發Android應用時必須遵守單執行緒模式的原則:
Android UI操作並不是安全執行緒的並且這些操作必須在UI線程中執行。
當主線程正在做一些比較耗時的操作的時候,如正從網路上下載一個大圖片,或者訪問資料庫,由於主線程被這些耗時的操作阻塞住,無法及時的響 應使用者的事件,從使用者的角度看會覺得程式已經死掉。如果程式長時間不響應,使用者還可能得重啟系統。為了避免這樣的情況,Android設 置了一個5秒 的逾時時間,一旦使用者的事件由於主線程阻塞而超過5秒 鐘沒有響應,Android會彈出一個應用程式沒有響應的對話方塊。
二、例子
下面將通過一個案例來示範這種情況:
本程式將設計和實現查看指定城市的當天天氣情況的功能,
1. 首先,需要選擇一個天氣查詢的 服務介面,目前可供選擇的介面很多,諸如YAHOO的 天氣API和Google提 供的天氣API。 本文將選擇GOOGLE 的 天氣查詢API。 該介面提供了多種查詢方式,可以通過指定具體城市的經緯度進行查詢,也可以通過城市名稱進行查詢。
2. 使用者在輸入框內輸入需要查詢的 城市名稱,然後點擊查詢按鈕
3. 當使用者點擊查詢按鈕後,使用已 經內建在Android SDK中的HttpClient API來調用GOOGLE 的 天氣查詢API, 然後解析返回的指定城市的天氣資訊,並把該天氣資訊顯示在Title上
主要代碼如下:
public class WeatherReport extends Activity implements OnClickListener { private static final String GOOGLE_API_URL = "http://www.google.com/ig/api?weather="; private static final String NETWORK_ERROR = "網路異常"; private EditText editText; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); editText = (EditText) findViewById(R.id.weather_city_edit); Button button = (Button) findViewById(R.id.goQuery); button.setOnClickListener(this); }
@Override public void onClick(View v) { //獲得使用者輸入的城市名稱 String city = editText.getText().toString(); //調用Google 天氣API查詢指定城市的當日天氣 情況 String weather = getWetherByCity(city); //把天氣資訊顯示在title上 setTitle(weather); }
public String getWetherByCity(String city) { HttpClient httpClient = new DefaultHttpClient(); HttpContext localContext = new BasicHttpContext(); HttpGet httpGet = new HttpGet(GOOGLE_API_URL + city); try { HttpResponse response = httpClient.execute(httpGet, localContext); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { httpGet.abort(); } else { HttpEntity httpEntity = response.getEntity(); return parseWeather(httpEntity.getContent()); } } catch (Exception e) { Log.e("WeatherReport", "Failed to get weather", e); } finally { httpClient.getConnectionManager().shutdown(); } return NETWORK_ERROR; }}
當使用者輸入城市名稱,然後單擊按鈕進行查詢後,程式會調用Google API的介面獲得指定城市的當日天氣情況。由於需要訪問網路,所以當網路出現異常或者服務繁忙的時候都會使訪問網路的動作很耗時。為了要示範逾時的現象,只需要製造一種網路異常的狀況,最簡單的方式就是斷開網路連接,然後啟動該程式,同時觸發一個使用者事件,比如按一下MENU鍵,由於主線程因為網路異常而被長時間阻塞,所以使用者的按鍵事件在5秒 鐘內得不到響應,Android會 提示一個程式無法響應的異常。
三、子線程
Android的UI是單線程(Single-threaded)的。為了避免拖住GUI,一些較費時的對象應該交給獨立的線程去執行。但幕後的線程來執行UI對象,Android就會發出錯誤訊息 CalledFromWrongThreadException。
像上例一樣由主線程來負責執行 該操作是錯誤的。所以我們需要在onClick方 法中建立一個新的子線程來負責調用GOOGLE API來獲得天氣資料。剛接觸Android的 開發人員最容易想到的方式就是如下:
public void onClick(View v) { //建立一個子線程執行耗時的從網路上擷取天氣資訊的操作 new Thread() { @Override public void run() { //獲得使用者輸入的城市名稱 String city = editText.getText().toString(); //調用Google 天氣API查詢指定城市的當日天氣 情況 String weather = getWetherByCity(city); //把天氣資訊顯示在title上 setTitle(weather); } }.start(); }
你會發 現Android會 提示程式由於異常而終止。為什麼在其他平台上看起來很簡單的代碼在Android上啟動並執行時候依然會出錯呢?如果你觀察LogCat中列印的日誌資訊就會發現這樣的錯誤記錄檔:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
從錯誤資訊不難看出Android禁 止其他子線程來更新由UI thread建立的試圖。本例中顯示天氣資訊的title實際是就是一個由UI thread所建立的TextView,所以參試在一個子線程中去更改TextView的時候就出錯了。這顯示違背了單執行緒模式的原則:Android UI操作並不是安全執行緒的並且這些操作必須在UI線 程中執行