1. 程式人生 > >Android多執行緒-----非同步(AsyncTask)

Android多執行緒-----非同步(AsyncTask)

一、總述

在Android當中,提供了非同步訊息處理機制的兩種方式來解決執行緒之間的通訊問題,一種是通過Handler的機制(這種方式在後面的部落格中將詳細介紹),還有一種就是今天要詳細講解的 AsyncTask 機制。

Android中的工作者執行緒主要有AsyncTask、IntentService、HandlerThread,它們本質上都是Handler與執行緒池的封裝。關於執行緒和執行緒池相關知識的介紹,請參考這兩篇博文:Java核心技術點之多執行緒    深入理解Java之執行緒池

使用AsyncTask的方便之處在於能夠更新使用者介面,當然這裡更新使用者介面的操作還是在主執行緒中完成的,但是由於AsyncTask內部包含一個Handler,所以可以傳送訊息給主執行緒讓它更新UI。另外,AsyncTask內還包含了一個執行緒池。使用執行緒池的主要原因是避免不必要的建立及銷燬執行緒的開銷。

二、掌握AsyncTask

我們就必須要一個概念,總結起來就是: 3個泛型,4個步驟。

1、3個泛型引數

我們來看看AsyncTask這個抽象類的定義,當我們定義一個類來繼承AsyncTask這個類的時候,需要為其指定3個泛型引數:AsyncTask <Params, Progress, Result>

  • Params: 指定的是我們傳遞給非同步任務執行時的引數的型別
  • Progress: 指定的是我們的非同步任務在執行的時候將執行的進度返回給UI執行緒的引數的型別
  • Result: 指定的是非同步任務執行完後返回給UI執行緒的結果的型別

 我們在定義一個類繼承AsyncTask類的時候,必須指定好這三個泛型的型別,如果都不指定的話,則都將其寫成Void,例如:AsyncTask <Void, Void, Void>


2、4個步驟

當我們執行一個非同步任務時,需要按照下面的4個步驟分別執行:

  • onPreExecute(): 這個方法是在執行非同步任務之前的時候執行,並且是在UI Thread當中執行的,通常我們在這個方法裡做一些UI控制元件的初始化的操作,例如彈出ProgressDialog
  • doInBackground(Params... params): 在onPreExecute()方法執行完後,會馬上執行這個方法,這個方法就是來處理非同步任務的方法,Android作業系統會在後臺的執行緒池當中開啟一個worker thread來執行這個方法(即在worker thread當中執行),執行完後將執行結果傳送給最後一個 onPostExecute 方法,在這個方法裡,我們可以從網路當中獲取資料等一些耗時的操作
  • onProgressUpdate(Progess... values): 這個方法也是在UI Thread當中執行的,在非同步任務執行的時候,有時需要將執行的進度返回給UI介面,例如下載一張網路圖片,我們需要時刻顯示其下載的進度,就可以使用這個方法來更新進度。這個方法在呼叫之前,我們需要在 doInBackground 方法中呼叫一個 publishProgress(Progress) 的方法來將進度時時刻刻傳遞給 onProgressUpdate 方法來更新
  • onPostExecute(Result... result): 當非同步任務執行完之後,就會將結果返回給這個方法,這個方法也是在UI Thread當中呼叫的,我們可以將返回的結果顯示在UI控制元件上

 為什麼AsyncTask抽象類只有一個 doInBackground 的抽象方法呢??原因是,我們如果要做一個非同步任務,我們必須要為其開闢一個新的Thread,讓其完成一些操作,而在完成這個非同步任務時,我可能並不需要彈出ProgressDialog,並不需要隨時更新ProgressDialog的進度條,也並不需要將結果更新給UI介面,所以除了 doInBackground 方法之外的三個方法,都不是必須有的,因此必須要實現的方法是 doInBackground 方法。

4個步驟簡潔版描述如下:
  第一步:表示任務執行前的操作
  第二步:主要完成耗時操作
  第三步:主要是更新UI操作
  第四步:產生最終結果
以下例項中代表的含義為:
  第一步:顯示進度條
  第二步:(此任務必不可少)在後臺執行任務,將進度值傳給第三步,將結果傳給第四步;
  第三步:進度值更新
  第四步:產生最終結果

3、4條準則

為了正確的使用AsyncTask類,必須遵守以下幾條準則(侷限性):

  • AsyncTask的例項必須在UI執行緒中建立
  • execute方法必須在UI執行緒中呼叫
  • 不要手動去呼叫onPreExecute() doInBaground() onProgressUpdate() onPostExecute()
  • AsyncTask的例項只能被執行一次 多次呼叫的時候將會出現異常

三、例項

public class MyAsyncTask extends AsyncTask<String , Void , Bitmap>{
    //上下文物件
    private Context context;
    private ProgressDialog dialog;
    private ImageView imageView;
    
    //構造
    public MyAsyncTask(Context context , ImageView imageView){
        super();
        this.context = context;
        this.imageView = imageView;
    }
    
    /**
     * 實現四個系統回撥的方法
     */
     
    /**
     * 1.onPreExecute()方法:在後臺執行前的準備工作,主執行緒呼叫
     * 準備工作:做什麼?>建立進度對話方塊
     */
    @Override
    protected void onPreExecute(){
        super.onPreExecute();
        dialog = new ProgressDialog();
        dialog.setIcon(R.drawable.xxx);
        dialog.setTitle("友情提示!");
        dialog.setMessage("正在玩命為您載入中");
        //下面show()方法不要忘了,否則不顯示進度對話方塊
        dialog.show();
    }
    
    /**
     *  2.doInBackground()方法:非主執行緒呼叫,後臺的主要工作執行緒,
     *  如耗時的操作(網路下載圖片,下載JSON等)都放在這裡執行。
     *  它是子執行緒,不可以在這裡做UI操作(重要)
     *  引數是String型別字元陣列,通常為傳入的url
     */
    @Override
    public void doInBackground(String...params){
        //網路請求:
        /*建立HttpClient的例項*/
        HttpClient httpClient = new DefaultHttpClient();
        /*建立連線方法的例項,HttpGet()的構造中傳入url地址*/
        HttpGet httpGet = new HttpGet(params[0]);
        try{
            /*呼叫建立好的HttpClient的例項的execute方法來發送建立好的HttpGet或HttpPost請求,並返回HttpResponse物件*/
            HttpResponse httpResponse = httpClient.execute(httpGet);
            if(httpResponse.getStatusLine().getStatusCode() == 200){
                /*返回實體物件*/
                HttpEntity entity = httpResponse.getEntity();
                byte [] data = EntityUtils.toByteArray(entity);
                Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
                return bitmap;
            }
        }
        catch(Exception e){
            e.printStackTrace();
        }
        finally{
            httpClient.getConnectionManager().shutdown();
        }
    }
    
    /**
     *  更新顯示進度,主執行緒呼叫
     */
    @Override
    public void onProgressUpdate(Void...values){
        super.onProgressUpdate(values);
    }
    
    /**
     *  在doInBackground執行完成之後呼叫,後臺的計算結果將通過該方法傳遞到UI執行緒,回到主執行緒(主執行緒呼叫的)
     *  可以實現主執行緒和子執行緒之間的資料互動
     */
    public void onPostExcute(Bitmap bitmap){
        super.onPostExecute(bitmap);
        if(bitmap != null){
            imageView.setImageBitmap(bitmap);
        }
        else{
            Toast.makeText(context,"下載圖片失敗",Toast.LENGTH.LONG).show();
        }
        //關閉進度對話方塊
        dialog.dismiss();
    }
}

public class MainActivity extends Activity{
    private ImageView imageView;
    private MyAsyncTask myAsyncTask;
    private String url = "";http://p5.qhimg.com/dmt/490_350_/t01405cf23f986e5ef6.jpg
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);
        
        imageView = (ImageView)findViewById(R.id.iv_imageview);
        //建立AsyncTask的例項
        myAsyncTask = new MyAsyncTask(this,imageView);
    }
    
    //點選按鈕,開始非同步下載圖片
    public void downloadPic(View view){
        /*AsyncTask的例項只能被執行一次*/
        myAsyncTask.execute(url);
    }
}