TShopping

 找回密碼
 註冊
搜索
查看: 418|回復: 0

[教學] [Android] AsyncTask - 非同步任務

[複製鏈接]
發表於 2020-8-10 18:39:17 | 顯示全部樓層 |閱讀模式
 
Push to Facebook Push to Plurk  
AsyncTask非同步任務,或稱異步任務,是一個相當常用的類別,是專門用來處理背景任務與UI的類別。

Android 4.0 之後,有明文規定所有的網路行為都不能在主執行緒(Main Thread)執行,

主執行緒又稱UI執行緒(UI Thread),任何有關UI的東西都在主執行緒中執行,若是你的程式佔據主執行緒很久,使用者體驗會非常的差。

想像一下,按了一個按鈕後,整個App停住五秒會是怎樣的感覺,因此許多耗時的程式建議寫在背景執行,而其中最常見的就是網路的功能。

在此先介紹一下有關ANR(Application Not Responding)的問題,也就是應用程式沒有回應。

你可以試著加入一個Button,在onClick事件裡面做一件很花時間的事情。

        
  1. button = (Button)findViewById(R.id.button);
  2.         button.setOnClickListener(new View.OnClickListener() {
  3.             @Override
  4.             public void onClick(View view) {
  5.                 for(int i = 0 ; i < 10000000 ; i++){
  6.                     Log.d("Tag = " , "HI");
  7.                 }
  8.             }
  9.         });
複製代碼

跑了一個很大的迴圈,過一下子你的手機會跑出這個警告。

Android AsyncTask 非同步 任務 Java

Android  AsyncTask 非同步 任務 Java



這就是典型的ANR,因為onClick事件是在主執行緒,你佔據主執行緒太久的時間,因此跳出了這個警告,若是按下確定就會關閉你的程式。

要怎麼知道我是不是在主執行緒呢,你可以用以下的程式碼來判斷。

Thread.currentThread().getId()

像是你在onClick裡面加入這個Log,

Log.d("onClick = " , String.valueOf(Thread.currentThread().getId()));

然後試著印出來,你會發現他會寫1,1就是主執行緒的ID,也是UI Thread。

這樣你大概了解其中一種會產生ANR的問題。

接下來來談談AsyncTask的用法吧,這次舉的例子是從網路下載圖片。

你可能會想,只是下載一張小圖片應該不會花費太久時間,那我寫在主執行緒就好了。

然後就在onClick裡面寫了以下的程式碼。

  1. try {
  2.    URL url = new URL("http://i.imgur.com/Uki7N9T.jpg");
  3.     //取得圖片的URL
  4.    Bitmap bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
  5.     //透過BitmapFactory來下載URL的圖片
  6.    imageView.setImageBitmap(bitmap);
  7.    //設置圖片到ImageView之中
  8. } catch (IOException e) {
  9.     e.printStackTrace();
  10. }
複製代碼


透過BitmapFactory來下載圖片,在使用Try Catch來捕捉一些可能的例外,看起來滿正確的,但是實際執行會發現有一個例外。

android.os.NetworkOnMainThreadException

意思是,你不能在主執行緒做網路的事情,還是乖乖用AsyncTask吧XD


AsyncTask<Params, Progress, Result>,這是基本的架構,使用泛型來定義參數,

泛型意思是,你可以定義任意的資料型態給他。

Params : 參數,你要餵什麼樣的參數給它。

Progress : 進度條,進度條的資料型態要用哪種

Result : 結果,你希望這個背景任務最後會有什麼樣的結果回傳給你。

此外,AsyncTask會有四個步驟。

onPreExecute : 執行前,一些基本設定可以在這邊做。

doInBackground : 執行中,在背景做任務。

onProgressUpdate : 執行中,當你呼叫publishProgress的時候會到這邊,可以告知使用者進度。

onPostExecute : 執行後,最後的結果會在這邊。

拿下載圖片的例子來寫,繼承AsyncTask,並實作四個步驟,

參數說明 : 丟入網址(String),進度條用整數(Integer),拿到圖片(Bitmap)

  1. private class GetImage extends AsyncTask<String , Integer , Bitmap>{

  2.         @Override
  3.         protected void onPreExecute() {
  4.             //執行前 設定可以在這邊設定
  5.             super.onPreExecute();
  6.         }

  7.         @Override
  8.         protected Bitmap doInBackground(String... params) {
  9.             //執行中 在背景做事情
  10.             return null;
  11.         }

  12.         @Override
  13.         protected void onProgressUpdate(Integer... values) {
  14.             //執行中 可以在這邊告知使用者進度
  15.             super.onProgressUpdate(values);
  16.         }

  17.         @Override
  18.         protected void onPostExecute(Bitmap bitmap) {
  19.             //執行後 完成背景任務
  20.             super.onPostExecute(bitmap);
  21.         }
  22.     }
複製代碼



這邊你可能不明白String...是什麼意思,這東西的意思是你可以傳單一一個String,
或者是一個String陣列都可以。

假如你丟一個String進去,你只要取得第一個元素即可。

String urlStr = params[0];

接著在把剛才下載圖片的程式改寫到doInBackground之中。

        
  1. protected Bitmap doInBackground(String... params) {
  2.             //執行中 在背景做事情
  3.             String urlStr = params[0];
  4.             try {
  5.                 URL url = new URL(urlStr);
  6.                 Bitmap bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
  7.                 return bitmap;
  8.             } catch (Exception e) {
  9.                 e.printStackTrace();
  10.                 return null;
  11.             }
  12.         }
複製代碼


如此一來,你就會在背景下載圖片,當沒有例外的時候就會回傳。

此時,你可能會想,那我不要等回傳,我直接在doInBackground去改我的圖片就好了。

        
  1. protected Bitmap doInBackground(String... params) {
  2.             //執行中 在背景做事情
  3.             String urlStr = params[0];
  4.             try {
  5.                 URL url = new URL(urlStr);
  6.                 Bitmap bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
  7.                 imageView.setImageBitmap(bitmap);
  8.                 return bitmap;
  9.             } catch (Exception e) {
  10.                 e.printStackTrace();
  11.                 return null;
  12.             }
  13.         }
複製代碼

這時候會跳出一個例外:

Only the original thread that created a view hierarchy can touch its views.

意思是,你只能在UI Thread去修改UI,因為你現在是在背景,因此你必須回到UI Thread才能對UI做事情,很勤勞的跑去Google找解,最後你的程式碼可能變成這樣。

        
  1. private Bitmap bitmap;

  2.         @Override
  3.         protected Bitmap doInBackground(String... params) {
  4.             //執行中 在背景做事情
  5.             String urlStr = params[0];
  6.             try {
  7.                 URL url = new URL(urlStr);
  8.                 bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
  9.                 runOnUiThread(new Runnable() {
  10.                     @Override
  11.                     public void run() {
  12.                         imageView.setImageBitmap(bitmap);
  13.                     }
  14.                 });
  15.                 return bitmap;
  16.             } catch (Exception e) {
  17.                 e.printStackTrace();
  18.                 return null;
  19.             }
  20.         }
複製代碼

事實上,這樣是可以運作沒錯,但是有點太多此一舉。

主執行緒沒辦法用網路 -> 用背景執行 -> 背景執行沒辦法改UI -> 在回去主執行緒。

我們有提到,AsyncTask有四個步驟,我們試著將這四個步驟的執行緒ID都印出來。

  1. private class GetImage extends AsyncTask<String , Integer , Bitmap>{

  2.         @Override
  3.         protected void onPreExecute() {
  4.             //執行前 設定可以在這邊設定
  5.             super.onPreExecute();
  6.             Log.d("Tag onPreExecute" , String.valueOf(Thread.currentThread().getId()));
  7.         }

  8.         @Override
  9.         protected Bitmap doInBackground(String... params) {
  10.             //執行中 在背景做事情
  11.             Log.d("Tag doInBackground" , String.valueOf(Thread.currentThread().getId()));
  12.             publishProgress(100);
  13.             return null;
  14.         }

  15.         @Override
  16.         protected void onProgressUpdate(Integer... values) {
  17.             //執行中 可以在這邊告知使用者進度
  18.             super.onProgressUpdate(values);
  19.             Log.d("Tag onProgressUpdate", String.valueOf(Thread.currentThread().getId()));

  20.         }

  21.         @Override
  22.         protected void onPostExecute(Bitmap bitmap) {
  23.             //執行後 完成背景任務
  24.             super.onPostExecute(bitmap);
  25.             Log.d("Tag onPostExecute", String.valueOf(Thread.currentThread().getId()));
  26.         }
  27.     }
複製代碼

Android AsyncTask 非同步 任務 Java

Android  AsyncTask 非同步 任務 Java

除了背景任務以外都回到主執行緒了,因此你可以在結果的部分在對UI做修改,不用特定在背景那邊在呼叫回主執行緒,這樣太多此一舉了。

如此一來基本的認識應該有了,這裏提供兩個範例。

1. 傳入一個網址,下載網路圖片後顯示在ImageView之中
   
  1. private class GetImage extends AsyncTask<String , Integer , Bitmap>{

  2.         @Override
  3.         protected void onPreExecute() {
  4.             //執行前 設定可以在這邊設定
  5.             super.onPreExecute();
  6.         }

  7.         @Override
  8.         protected Bitmap doInBackground(String... params) {
  9.             //執行中 在背景做事情

  10.             String urlStr = params[0];
  11.             try {
  12.                 URL url = new URL(urlStr);
  13.                 return BitmapFactory.decodeStream(url.openConnection().getInputStream());
  14.             } catch (Exception e) {
  15.                 e.printStackTrace();
  16.                 return null;
  17.             }
  18.         }

  19.         @Override
  20.         protected void onProgressUpdate(Integer... values) {
  21.             //執行中 可以在這邊告知使用者進度
  22.             super.onProgressUpdate(values);

  23.         }

  24.         @Override
  25.         protected void onPostExecute(Bitmap bitmap) {
  26.             //執行後 完成背景任務
  27.             super.onPostExecute(bitmap);
  28.             imageView.setImageBitmap(bitmap);
  29.         }
  30.     }
複製代碼

執行背景程式的方法

               
  1. new GetImage().execute("http://i.imgur.com/Uki7N9T.jpg");
複製代碼

2. 實作進度條的功能。

模擬下載三張圖,可是下載完後沒有回傳圖片回去,這個例子是說明怎麼使用進度條。
  1. private class GetImage extends AsyncTask<String , Integer , Bitmap>{

  2.         private ProgressDialog progressBar;
  3.         //進度條元件

  4.         @Override
  5.         protected void onPreExecute() {
  6.             //執行前 設定可以在這邊設定
  7.             super.onPreExecute();

  8.             progressBar = new ProgressDialog(MainActivity.this);
  9.             progressBar.setMessage("Loading...");
  10.             progressBar.setCancelable(false);
  11.             progressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
  12.             progressBar.show();
  13.             //初始化進度條並設定樣式及顯示的資訊。
  14.         }

  15.         @Override
  16.         protected Bitmap doInBackground(String... params) {
  17.             //執行中 在背景做事情
  18.             int progress = 0;
  19.             for (String urlStr : params) {
  20.                 try {
  21.                     URL url = new URL(urlStr);
  22.                     Bitmap bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
  23.                 } catch (Exception  e) {
  24.                     e.printStackTrace();
  25.                 }
  26.                 publishProgress(progress+=33);
  27.                 //有三張圖 每張圖33%
  28.             }
  29.             publishProgress(100);
  30.             //最後達到100%
  31.             return null;
  32.         }

  33.         @Override
  34.         protected void onProgressUpdate(Integer... values) {
  35.             //執行中 可以在這邊告知使用者進度
  36.             super.onProgressUpdate(values);
  37.             progressBar.setProgress(values[0]);
  38.             //取得更新的進度
  39.         }

  40.         @Override
  41.         protected void onPostExecute(Bitmap bitmap) {
  42.             //執行後 完成背景任務
  43.             super.onPostExecute(bitmap);

  44.             progressBar.dismiss();
  45.             //當完成的時候,把進度條消失
  46.             imageView.setImageBitmap(bitmap);
  47.         }
  48.     }
複製代碼

當你呼叫publishProgress時,丟入一個值,會到onProgressUpdate之中,在這邊更新進度條的進度。
3.png

當完成之時,在呼叫dismiss將進度條除去。

看完這篇文章你應該對AsyncTask有一些基本的認識了。


文章出處

來源http://www.netyea.com
#網頁設計 #網站架設 #關鍵字優化 #網頁優化 #App程式設計 #AIOT物聯網

 

臉書網友討論
您需要登錄後才可以回帖 登錄 | 註冊 |

本版積分規則



Archiver|手機版|小黑屋|免責聲明|TShopping

GMT+8, 2020-10-27 19:21 , Processed in 0.059440 second(s), 24 queries .

本論壇言論純屬發表者個人意見,與 TShopping綜合論壇 立場無關 如有意見侵犯了您的權益 請寫信聯絡我們。

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回復 返回頂部 返回列表