AsyncTask非同步任務,或稱異步任務,是一個相當常用的類別,是專門用來處理背景任務與UI的類別。
Android 4.0 之後,有明文規定所有的網路行為都不能在主執行緒(Main Thread)執行,
主執行緒又稱UI執行緒(UI Thread),任何有關UI的東西都在主執行緒中執行,若是你的程式佔據主執行緒很久,使用者體驗會非常的差。
想像一下,按了一個按鈕後,整個App停住五秒會是怎樣的感覺,因此許多耗時的程式建議寫在背景執行,而其中最常見的就是網路的功能。
在此先介紹一下有關ANR(Application Not Responding)的問題,也就是應用程式沒有回應。
你可以試著加入一個Button,在onClick事件裡面做一件很花時間的事情。
- button = (Button)findViewById(R.id.button);
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- for(int i = 0 ; i < 10000000 ; i++){
- Log.d("Tag = " , "HI");
- }
- }
- });
複製代碼
跑了一個很大的迴圈,過一下子你的手機會跑出這個警告。
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裡面寫了以下的程式碼。
- try {
- URL url = new URL("http://i.imgur.com/Uki7N9T.jpg");
- //取得圖片的URL
- Bitmap bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
- //透過BitmapFactory來下載URL的圖片
- imageView.setImageBitmap(bitmap);
- //設置圖片到ImageView之中
- } catch (IOException e) {
- e.printStackTrace();
- }
複製代碼
透過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)
- private class GetImage extends AsyncTask<String , Integer , Bitmap>{
- @Override
- protected void onPreExecute() {
- //執行前 設定可以在這邊設定
- super.onPreExecute();
- }
- @Override
- protected Bitmap doInBackground(String... params) {
- //執行中 在背景做事情
- return null;
- }
- @Override
- protected void onProgressUpdate(Integer... values) {
- //執行中 可以在這邊告知使用者進度
- super.onProgressUpdate(values);
- }
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- //執行後 完成背景任務
- super.onPostExecute(bitmap);
- }
- }
複製代碼
這邊你可能不明白String...是什麼意思,這東西的意思是你可以傳單一一個String,
或者是一個String陣列都可以。
假如你丟一個String進去,你只要取得第一個元素即可。
String urlStr = params[0];
接著在把剛才下載圖片的程式改寫到doInBackground之中。
- protected Bitmap doInBackground(String... params) {
- //執行中 在背景做事情
- String urlStr = params[0];
- try {
- URL url = new URL(urlStr);
- Bitmap bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
- return bitmap;
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
複製代碼
如此一來,你就會在背景下載圖片,當沒有例外的時候就會回傳。
此時,你可能會想,那我不要等回傳,我直接在doInBackground去改我的圖片就好了。
- protected Bitmap doInBackground(String... params) {
- //執行中 在背景做事情
- String urlStr = params[0];
- try {
- URL url = new URL(urlStr);
- Bitmap bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
- imageView.setImageBitmap(bitmap);
- return bitmap;
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
複製代碼
這時候會跳出一個例外:
Only the original thread that created a view hierarchy can touch its views.
意思是,你只能在UI Thread去修改UI,因為你現在是在背景,因此你必須回到UI Thread才能對UI做事情,很勤勞的跑去Google找解,最後你的程式碼可能變成這樣。
- private Bitmap bitmap;
- @Override
- protected Bitmap doInBackground(String... params) {
- //執行中 在背景做事情
- String urlStr = params[0];
- try {
- URL url = new URL(urlStr);
- bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- imageView.setImageBitmap(bitmap);
- }
- });
- return bitmap;
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
複製代碼
事實上,這樣是可以運作沒錯,但是有點太多此一舉。
主執行緒沒辦法用網路 -> 用背景執行 -> 背景執行沒辦法改UI -> 在回去主執行緒。
我們有提到,AsyncTask有四個步驟,我們試著將這四個步驟的執行緒ID都印出來。
- private class GetImage extends AsyncTask<String , Integer , Bitmap>{
- @Override
- protected void onPreExecute() {
- //執行前 設定可以在這邊設定
- super.onPreExecute();
- Log.d("Tag onPreExecute" , String.valueOf(Thread.currentThread().getId()));
- }
- @Override
- protected Bitmap doInBackground(String... params) {
- //執行中 在背景做事情
- Log.d("Tag doInBackground" , String.valueOf(Thread.currentThread().getId()));
- publishProgress(100);
- return null;
- }
- @Override
- protected void onProgressUpdate(Integer... values) {
- //執行中 可以在這邊告知使用者進度
- super.onProgressUpdate(values);
- Log.d("Tag onProgressUpdate", String.valueOf(Thread.currentThread().getId()));
- }
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- //執行後 完成背景任務
- super.onPostExecute(bitmap);
- Log.d("Tag onPostExecute", String.valueOf(Thread.currentThread().getId()));
- }
- }
複製代碼
Android AsyncTask 非同步 任務 Java
除了背景任務以外都回到主執行緒了,因此你可以在結果的部分在對UI做修改,不用特定在背景那邊在呼叫回主執行緒,這樣太多此一舉了。
如此一來基本的認識應該有了,這裏提供兩個範例。
1. 傳入一個網址,下載網路圖片後顯示在ImageView之中
- private class GetImage extends AsyncTask<String , Integer , Bitmap>{
- @Override
- protected void onPreExecute() {
- //執行前 設定可以在這邊設定
- super.onPreExecute();
- }
- @Override
- protected Bitmap doInBackground(String... params) {
- //執行中 在背景做事情
- String urlStr = params[0];
- try {
- URL url = new URL(urlStr);
- return BitmapFactory.decodeStream(url.openConnection().getInputStream());
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- @Override
- protected void onProgressUpdate(Integer... values) {
- //執行中 可以在這邊告知使用者進度
- super.onProgressUpdate(values);
- }
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- //執行後 完成背景任務
- super.onPostExecute(bitmap);
- imageView.setImageBitmap(bitmap);
- }
- }
複製代碼
執行背景程式的方法
- new GetImage().execute("http://i.imgur.com/Uki7N9T.jpg");
複製代碼
2. 實作進度條的功能。
模擬下載三張圖,可是下載完後沒有回傳圖片回去,這個例子是說明怎麼使用進度條。
- private class GetImage extends AsyncTask<String , Integer , Bitmap>{
- private ProgressDialog progressBar;
- //進度條元件
- @Override
- protected void onPreExecute() {
- //執行前 設定可以在這邊設定
- super.onPreExecute();
- progressBar = new ProgressDialog(MainActivity.this);
- progressBar.setMessage("Loading...");
- progressBar.setCancelable(false);
- progressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- progressBar.show();
- //初始化進度條並設定樣式及顯示的資訊。
- }
- @Override
- protected Bitmap doInBackground(String... params) {
- //執行中 在背景做事情
- int progress = 0;
- for (String urlStr : params) {
- try {
- URL url = new URL(urlStr);
- Bitmap bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
- } catch (Exception e) {
- e.printStackTrace();
- }
- publishProgress(progress+=33);
- //有三張圖 每張圖33%
- }
- publishProgress(100);
- //最後達到100%
- return null;
- }
- @Override
- protected void onProgressUpdate(Integer... values) {
- //執行中 可以在這邊告知使用者進度
- super.onProgressUpdate(values);
- progressBar.setProgress(values[0]);
- //取得更新的進度
- }
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- //執行後 完成背景任務
- super.onPostExecute(bitmap);
- progressBar.dismiss();
- //當完成的時候,把進度條消失
- imageView.setImageBitmap(bitmap);
- }
- }
複製代碼
當你呼叫publishProgress時,丟入一個值,會到onProgressUpdate之中,在這邊更新進度條的進度。
當完成之時,在呼叫dismiss將進度條除去。
看完這篇文章你應該對AsyncTask有一些基本的認識了。
文章出處
來源http://www.netyea.com
#網頁設計 #網站架設 #關鍵字優化 #網頁優化 #App程式設計 #AIOT物聯網
|