安卓AsyncTack詳解

我們知道安卓中的UI線程不是線程安全的,即不能在UI線程中進行耗時操作,所以我們通常的做法是開啓一個子線程來進行耗時操作,然後將處理後的結果運用Handler機制傳遞給UI線程,在UI線程中根據處理後的結果更新界面。如從網絡上獲取一張圖片顯示到界面上的一個ImageView控件上,我們會開啓一個子線程來進行網絡請求獲取圖片,然後運用Handler告訴主線程圖片已經獲取到,可以刷新界面顯示圖片。即運用Handler+Thread來處理這種請求,事實上這種情況在安卓開發中使用非常頻繁,因此谷歌也爲了簡化開發步驟,提供了AsyncTask這個類,即異步任務類,它是爲了在子線程中更新UI界面而存在的。

一AsyncTask基本用法

我們首先來看一下其類的定義:

 public abstract class AsyncTask<Params, Progress, Result> 
可以看到AsyncTask是一個抽象類,這說明它至少存在一種抽象方法,這個抽象方法就是我們必須重寫的doInBackground(Params... params),另外它包含三個泛型參數Params, Progress, Result。

Params: 顧名思義,就是參數的意思,這個泛型指定的是我們傳遞給異步任務執行時的參數的類型
Progress:顧名思義,就是進度的意思,這個泛型指定的是我們的異步任務在執行的時候將執行的進度返回給UI線程的參數的類型
Result: 顧名思義,就是結果的意思,這個泛型指定的異步任務執行完後返回給UI線程的結果的類型

下面我們來看一下AsyncTask中的重要方法:

protected void onPreExecute() { }
protected abstract Result doInBackground(Params... params);
protected void onProgressUpdate(Progress... values) { }
protected void onPostExecute(Result result) { }
可以看到在這四個最重要的方法中doInBackground(Params... params)是唯一一個抽象方法。下面我們一一介紹:

1onPreExecute(): 這個方法是在執行異步任務之前的時候執行,是在UI Thread當中執行的,通常我們在這個方法裏做一些UI控件的初始化的操作,如彈出ProgressDialog

2doInBackground(Params... params):在onPreExecute()方法執行完之後,會馬上執行這個方法,這個方法就是來處理異步任務的方法,Android操作系統會在後臺的線程池當中開啓一個worker thread來執行我們的這個方法,所以這個方法是在worker thread當中執行的,我們應該在此處理耗時操作,但是注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress...)方法來完成。

3onProgressUpdate(Progess... values): 這個方法也是在UI Thread當中執行的,我們在異步任務執行的時候,可能需要將執行的進度返回給我們的UI界面,例如下載一張網絡圖片,我們可能需要時刻顯示其下載的進度,就可以使用這個方法來更新我們的進度。當我們在在 doInBackground 方法中調用 publishProgress(Progress) 的方法來後, onProgressUpdate 方法將會被調用。

4onPostExecute(Result... result): 當我們的異步任務執行完之後通過return語句進行返回時,就會將結果作爲參數傳遞到此方法中,這個方法也是在UI Thread當中調用的,我們可以在此方法中將返回的結果顯示在UI控件上。

瞭解了上述四個重要的方法後,我們就可以實現自己的AsyncTask,主要邏輯就是重寫上述四個方法,在這些方法中根據業務邏輯進行相應的操作,如一個從網絡上獲取圖片的異步任務代碼如下:

public class MainActivity extends Activity
{
    private Button button;
    private ImageView imageView;
    private ProgressDialog progressDialog;
    private final String IMAGE_PATH = "http://developer.android.com/images/home/kk-hero.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        button = (Button)findViewById(R.id.button);
        imageView = (ImageView)findViewById(R.id.imageView);
      
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setTitle("提示信息");
        progressDialog.setMessage("正在下載中,請稍後......");
        //    設置setCancelable(false); 表示我們不能取消這個彈出框,等下載完成之後再讓彈出框消失
        progressDialog.setCancelable(false);
        //    設置ProgressDialog樣式爲水平的樣式
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        
        button.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                new MyAsyncTask().execute(IMAGE_PATH);
            }
        });
    }
    
    /**
     * 定義一個類,讓其繼承AsyncTask這個類
     * Params: String類型,表示傳遞給異步任務的參數類型是String,因爲此異步任務是從網絡上獲取圖片,所以通常指定的是URL路徑
     * Progress: Integer類型,進度條的單位通常都是Integer類型
     * Result:byte[]類型,因爲我們要存儲從網絡上獲取的圖片,然後將其以字節數組形式返回
     *
     */
    public class MyAsyncTask extends AsyncTask<String, Integer, byte[]>
    {
        @Override
        protected void onPreExecute()
        {
            super.onPreExecute();
            //    在onPreExecute()中我們讓ProgressDialog顯示出來
            progressDialog.show();
        }
        @Override
        protected byte[] doInBackground(String... params)
        {
            //    通過Apache的HttpClient來訪問請求網絡中的一張圖片
            HttpClient httpClient = new DefaultHttpClient();
            HttpGet httpGet = new HttpGet(params[0]);
            byte[] image = new byte[]{};
            try
            {
                HttpResponse httpResponse = httpClient.execute(httpGet);
                HttpEntity httpEntity = httpResponse.getEntity();
                InputStream inputStream = null;
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                if(httpEntity != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
                {
                    //    得到文件的總長度
                    long file_length = httpEntity.getContentLength();
                    //    每次讀取後累加的長度
                    long total_length = 0;
                    int length = 0;
                    //    每次讀取1024個字節
                    byte[] data = new byte[1024];
                    inputStream = httpEntity.getContent();
                    while(-1 != (length = inputStream.read(data)))
                    {
                        //    每讀一次,就將total_length累加起來
                        total_length += length;
                      
                        byteArrayOutputStream.write(data, 0, length);
                        //    得到當前圖片下載的進度
                        int progress = ((int)(total_length/(float)file_length) * 100);
                        //    調用<span style="font-family: Arial, Helvetica, sans-serif;">publishProgress(progress);</span>將當前進度傳給給onProgressUpdate方法
                        publishProgress(progress);
                    }
                }
                image = byteArrayOutputStream.toByteArray();
                inputStream.close();
                byteArrayOutputStream.close();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            finally
            {
                httpClient.getConnectionManager().shutdown();
            }
            return image;
        }
        @Override
        protected void onProgressUpdate(Integer... values)
        {
            super.onProgressUpdate(values);
            //    更新ProgressDialog的進度條
            progressDialog.setProgress(values[0]);
        }
        @Override
        protected void onPostExecute(byte[] result)
        {
            super.onPostExecute(result);
            //    將doInBackground方法返回的byte[]解碼成要給Bitmap
            Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length);
            //    更新我們的ImageView控件
            imageView.setImageBitmap(bitmap);
            //    使ProgressDialog框消失
            progressDialog.dismiss();
        }
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}


二總結

首先我們來梳理一下AsyncTask的處理過程,然後講一下在使用AsyncTask時應該注意的地方

當我們在UI線程中調用execute(Params... params)開啓異步任務後,execute方法會調用onPreExecute()方法,在該方法中doInBackground(Params... params)將被調用,且execute(Params... params)中的params參數將會被傳遞給doInBackground(Params... params)中的params參數,在該方法調用完成後,會自動調用onPostExecute(Result result)方法,且會將在doInBackground中return返回的結果傳遞給onPostExecute(Result result)中的result參數。


如果在doInBackground(Params... params)中

調用了publishProgress(Progress... values)方法,則在該方法中會調用onProgressUpdate(Progress... values)方法將被調用且會將publishProgress(Progress... values)中的

Progress... values參數傳遞給onProgressUpdate(Progress... values)中的values。

上述文字敘述可能不太直觀,下面是它們運行調用的順序直觀表示:


使用AsyncTask時應該注意的地方:

1AsyncTask的對象必須在UI Thread當中實例化
2execute方法必須在UI Thread當中調用

3不能在doInBackground(Params... params)中更改UI組件,UI的更新必須在onProgressUpdate中完成。
4不要手動的去調用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute方法,這些都是由Android系統自動調用的
5AsyncTask任務只能被執行一次,如果執行第二次將會拋出異常。


好了以上就是本人理解的關於AsyncTask的相關知識,看官如果覺得不錯請不要吝嗇點擊一下下方的“頂”按鈕給我一點鼓勵哦!微笑

發佈了98 篇原創文章 · 獲贊 330 · 訪問量 118萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章