Android 實現聯網(三)——在線程中聯網

在前面的關於Java I/O的學習中,有一個我們需要牢記的是:對數據流的操作都是阻塞的,在一般情況下,我們是不需要考慮這個問題的,但是在Android 實現聯網的時候,我們必須考慮到這個問題。比如:從網絡上下載一張圖片:

Java代碼 
  1. public Bitmap returnBitmap(String url)  
  2. {  
  3. URL myFileUrl = null;  
  4. Bitmap bitmap = null;  
  5. try{  
  6. myFileUrl = new URL(url);  
  7. }catch(MalformedURLException e){  
  8. e.printStackTrace();  
  9. return null;  
  10. };  
  11. try{  
  12. HttpURLConnection conn = (HttpURLConnection)myFileUrl.openConnection();  
  13. conn.setDoInput(true);  
  14. conn.connect();  
  15. InputStream is = conn.getInputStream();  
  16. bitmap = BitmapFactroy.decodeStream(is);  
  17. is.close();  
  18. }catch(IOException e){  
  19. e.printStackTrace();  
  20. }  
  21. return bitmap;  
  22. }  
 


由於網絡連接需要很長的時間,需要3-5秒,甚至更長的時間才能返回頁面的內容。如果此連接動作直接在主線程,也就是UI線程中處理,會發生什麼情況呢? 整個程序處於等待狀態,界面似乎是“死”掉了。爲了解決這個問題,必須把這個任務放置到單獨線程中運行,避免阻塞UI線程,這樣就不會對主線程有任何影 響。舉個例子如下:

Java代碼 
  1. private void connect(String strURL){  
  2. new Thread() {  
  3. public void run() {  
  4. try {  
  5. HttpClient client = new DefaultHttpClient();  
  6. // params[0]代表連接的url  
  7. HttpGet get = new HttpGet(url.getText().toString());  
  8. HttpResponse response = client.execute(get);  
  9. HttpEntity entity = response.getEntity();  
  10. long length = entity.getContentLength();  
  11. InputStream is = entity.getContent();  
  12. String s = null;  
  13. if (is != null) {  
  14. ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  15. byte[] buf = new byte[128];  
  16. int ch = -1;  
  17. int count = 0;  
  18. while ((ch = is.read(buf)) != -1) {  
  19. baos.write(buf, 0, ch);  
  20. count += ch;  
  21. }  
  22. s = new String(baos.toByteArray());  
  23. Log.V(“moandroid sample”,s);  
  24. }  
  25. catch (Exception e) {  
  26. e.printStackTrace();  
  27. }  
  28. }  
  29. }.start();  
  30. }  
 

 

使用Handler更新界面

如何將下載的信息顯示在界面上了,比如說下載的進度。Android SDK平臺只允許在主線程中調用相關View的方法來更新界面。如果返回結果在新線程中獲得,那麼必須藉助Handler來更新界面。爲此,在界面 Activity中創建一個Handler對象,並在handleMessage()中更新UI。

Java代碼 
  1. //Task在另外的線程執行,不能直接在Task中更新UI,因此創建了Handler  
  2. private Handler handler = new Handler() {  
  3. @Override  
  4. public void handleMessage(Message msg) {  
  5. String m = (String) msg.obj;  
  6. message.setText(m);  
  7. }  
  8. };  
 


只需要將上面的

Java代碼 
  1. Log.V(“moandroid sample”,s);  
 


替換爲:

Java代碼 
  1. s = new String(baos.toByteArray());  
  2. Message mg = Message.obtain();  
  3. mg.obj = s;  
  4. handler.sendMessage(mg);  
 

 

AsyncTask

看上去修改後的connect()方法已經可用了,但是這種匿名程的方式是存在缺陷的:

  • 線程的開銷較大,如果每個任務都要創建一個線程,那麼應用程 序的效率要低很多;
  • 線程無法管理,匿名線程創建並啓動後就不受程序的控制了,如果有很多個請求發送,那麼就會啓動非常多的線程,系統將不堪重負。
  • 另外,前面已經看到,在新線程中更新UI還必須要引入handler,這讓代碼看上去非常臃腫。

爲了解決這一問題,Android在1.5版本引入了AsyncTask。AsyncTask的特點是任務在主線程之外運行,而回調方法是在主線程 中執行,這就有效地避免了使用Handler帶來的麻煩。閱讀AsyncTask的源碼可知,AsyncTask是使用 java.util.concurrent 框架來管理線程以及任務的執行的,concurrent框架是一個非常成熟,高效的框架,經過了嚴格的測試。這說明AsyncTask的設計很好的解決了 匿名線程存在的問題。
AsyncTask是抽象類,其結構圖如下圖所示:
AsyncTask-class 
AsyncTask定義了三種泛型類型 Params,Progress和Result。

  • Params 啓動任務執行的輸入參數,比如HTTP請求的URL。
  • Progress 後臺任務執行的百分比。
  • Result 後臺執行任務最終返回的結果,比如String。

子類必須實現抽象方法doInBackground(Params… p) ,在此方法中實現任務的執行工作,比如連接網絡獲取數據等。通常還應該實現onPostExecute(Result r)方法,因爲應用程序關心的結果在此方法中返回。需要注意的是AsyncTask一定要在主線程中創建實例。
AsyncTask的執行分爲四個步驟,每一步都對應一個回調方法,需要注意的是這些方法不應該由應用程序調用,開發者需要做的就是實現這些方法。在任務 的執行過程中,這些方法被自動調用,運行過程,如下圖所示:

AsyncTask-Usage

  • onPreExecute() 當任務執行之前開始調用此方法,可以在這裏顯示進度對話框。
  • doInBackground(Params…) 此方法在後臺線程執行,完成任務的主要工作,通常需要較長的時間。在執行過程中可以調用publicProgress(Progress…)來更新任務的 進度。
  • onProgressUpdate(Progress…) 此方法在主線程執行,用於顯示任務執行的進度。
  • onPostExecute(Result) 此方法在主線程執行,任務執行的結果作爲此方法的參數返回。

舉個簡單的例子如下:

Java代碼 
  1. // 設置三種類型參數分別爲String,Integer,String  
  2. class PageTask extends AsyncTask {  
  3. // 可變長的輸入參數,與AsyncTask.exucute()對應  
  4. @Override  
  5. protected String doInBackground(String… params) {  
  6. try {  
  7. HttpClient client = new DefaultHttpClient();  
  8. // params[0]代表連接的url  
  9. HttpGet get = new HttpGet(params[0]);  
  10. HttpResponse response = client.execute(get);  
  11. HttpEntity entity = response.getEntity();  
  12. long length = entity.getContentLength();  
  13. InputStream is = entity.getContent();  
  14. String s = null;  
  15. if (is != null) {  
  16. ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  17. byte[] buf = new byte[128];  
  18. int ch = -1;  
  19. int count = 0;  
  20. while ((ch = is.read(buf)) != -1) {  
  21. baos.write(buf, 0, ch);  
  22. count += ch;  
  23. if (length > 0) {  
  24. // 如果知道響應的長度,調用publishProgress()更新進度  
  25. publishProgress((int) ((count / (float) length) * 100));  
  26. }  
  27. // 爲了在模擬器中清楚地看到進度,讓線程休眠100ms  
  28. Thread.sleep(100);  
  29. }  
  30. s = new String(baos.toByteArray()); }  
  31. // 返回結果  
  32. return s;  
  33. catch (Exception e) {  
  34. e.printStackTrace();  
  35. }  
  36. return null;  
  37. }  
  38. @Override  
  39. protected void onCancelled() {  
  40. super.onCancelled();  
  41. }  
  42. @Override  
  43. protected void onPostExecute(String result) {  
  44. // 返回HTML頁面的內容  
  45. message.setText(result);  
  46. }  
  47. @Override  
  48. protected void onPreExecute() {  
  49. // 任務啓動,可以在這裏顯示一個對話框,這裏簡單處理  
  50. message.setText(R.string.task_started);  
  51. }  
  52. @Override  
  53. protected void onProgressUpdate(Integer… values) {  
  54. // 更新進度  
  55. message.setText(values[0]);  
  56. }  
  57. }  
  58. 執行PageTask非常簡單,只需要調用如下代碼。  
  59. PageTask task = new PageTask();  
  60. task.execute(url.getText().toString());  
 

 

總結說明

Handler在前面我們已經學習過,今天突然看到AsyncTask,以及學習其他人的博客基礎上,做出了上面的總結,感覺自己收穫很多,在這裏 與大家分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章