Android的異步(Thread、Handler、AsyncTask)

很多初入Android或Java開發的新手(我也在內)對Thread、Looper、Handler和Message仍然比較迷惑,衍生的有HandlerThread、java.util.concurrent、Task、AsyncTask由於目前市面上的書籍等資料都沒有談到這些問題,今天就這一問題做更系統性的總結。我們創建的Service、Activity以及Broadcast均是一個主線程處理,這裏我們可以理解爲UI線程。但是在操作一些耗時操作時,比如I/O讀寫的大文件讀寫,數據庫操作以及網絡下載需要很長時間,爲了不阻塞用戶界面,出現ANR的響應提示窗口,這個時候我們可以考慮使用Thread線程來解決。
首先使用一個實例來解釋幾個異步之間的關係:
實例內容:從網絡上下載圖片
此實例由Sundy講解Android視頻提供的,地址:http://www.verycd.com/topics/2900036/
XML代碼:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent" android:layout_height="fill_parent"
        android:orientation="vertical">
        <ScrollView android:layout_width="fill_parent"
                android:layout_height="fill_parent">
                <LinearLayout android:layout_width="fill_parent"
                        android:layout_height="fill_parent" android:orientation="vertical">
                        <ImageView android:id="@+id/imageThreadConcept"
                                android:layout_width="wrap_content" android:layout_height="wrap_content">      </ImageView>
                        <Button android:text="Work Threads" android:id="@+id/buttonWorkThread"
                                android:layout_width="fill_parent" android:layout_height="wrap_content"></Button>
                        <Button android:text="Work Threads2" android:id="@+id/buttonWorkThread2"
                                android:layout_width="fill_parent" android:layout_height="wrap_content"></Button>
                        <Button android:text="Work Threads3" android:id="@+id/buttonWorkThread3"
                                android:layout_width="fill_parent" android:layout_height="wrap_content"></Button>
                        <Button android:text="Work AsyncTask" android:id="@+id/buttonWorkThread4"
                                android:layout_width="fill_parent" android:layout_height="wrap_content"></Button>
                </LinearLayout>
        </ScrollView>
</LinearLayout>


 


 

Java代碼:

       public class LoadImageTest extends Activity{

        

        private static final String TAG = "LoadImageTest";

        private ImageView mImageView = null ;

        //從網絡上下載圖片 .

        private final String IMAGE1_URL = "http://image.91rb.com/200905/27/9/34b5b080ac80661657946eaa51566d03.jpg" ;        

        private final String IMAGE3_URL = "http://m.ztwan.com/wallpaper/UploadPic/2010/8/28/201082811538894.jpg" ;

        private final String IMAGE4_URL = "http://m.ztwan.com/wallpaper/UploadPic/2010/8/28/20108280578236.jpg" ;

        

        protected void onCreate(Bundle savedInstanceState) {

                super.onCreate(savedInstanceState);

                this.setContentView(R.layout.load_imagetest)  ;

                

                mImageView = (ImageView)this.findViewById(R.id.imageThreadConcept) ;

                

                /方法1、直接在UI線程中加載網絡圖片

                findViewById(R.id.buttonWorkThread).setOnClickListener(new OnClickListener(){

                        

                        

                        public void onClick(View v) {

                                // 這隻方式直接在網絡中得到一張圖片,因數據量比較小,在UI線程中執行,並不會造成用戶視覺上的等待,如果數據量龐大,不採用這中方式

                                Drawable drawable = loadImageFromNetwork(IMAGE1_URL);

                    mImageView.setImageDrawable(drawable) ;

                        }})  ;

                

                /方法2、java習慣,在android不推薦使用,使得使用線程不安全

                findViewById(R.id.buttonWorkThread2).setOnClickListener(new OnClickListener(){

                        

                        

                        public void onClick(View v) {

                                // 在Android中是灰常不建議這樣做的,這樣做極易出現異常

                                new Thread(new Runnable(){

                                

                                        public void run() {

                                                Drawable drawable = loadImageFromNetwork(IMAGE1_URL);

                                                mImageView.setImageDrawable(drawable) ;

                                        }

                                        

                                }).start()  ;

                                

                        }})  ;

                

                //3. load image in new thread , but set imageview by View.post(Runnable) 

                //方法3、創建一個新的線程,( Runnable + Handler.postDelayed(runnable,time) )

                findViewById(R.id.buttonWorkThread3).setOnClickListener(new OnClickListener(){

                        

                        

                        public void onClick(View v) {

                                new Thread(new Runnable(){

                                        Drawable drawable = loadImageFromNetwork(IMAGE3_URL);

                                        

                                        public void run() {

                                                

                                    mImageView.post(new Runnable(){

                                            

                                                        public void run() {

                                                                

                                                                mImageView.setImageDrawable(drawable) ;

                                                        }}) ;

                                        }

                                        

                                }).start()  ;

                                

                        }})  ;

                

                //4. load image in new thread , but set imageview by AsyncTask 

               //方法4、使用AsyncTask,AsyncTask是在Android 1.5後引入的,能夠更安全的使用線程,在下面,將會再用一個實例來分析AsyncTask



                findViewById(R.id.buttonWorkThread4).setOnClickListener(new OnClickListener(){

                        

                        

                        public void onClick(View v) {

                                // 不可缺少的異步,當數據量龐大時,耗時的操作時就使用這種方式吧。

                                //IMAGE4_URL 是執行傳入的參數

                                new DownloadImageTask().execute(IMAGE4_URL) ;

                                

                        }})  ;

        }

        //Async private class 

        private class DownloadImageTask extends AsyncTask<String, Void, Drawable> {

            /** The system calls this to perform work in a worker thread and

              * delivers it the parameters given to AsyncTask.execute() */

            protected Drawable doInBackground(String... urls) {

                return loadImageFromNetwork(urls[0]);

            }

            

            /** The system calls this to perform work in the UI thread and delivers

              * the result from doInBackground() */

            protected void onPostExecute(Drawable result) {

                mImageView.setImageDrawable(result);

            }

        }

        

        // the Drawable loadImage main function 

        private Drawable loadImageFromNetwork(String imageUrl)

        {

                Drawable drawable = null;

                try {

                        drawable = Drawable.createFromStream(

                                        new URL(imageUrl).openStream(), "image.gif");

                } catch (IOException e) {

                        Log.d(TAG, e.getMessage());

                }

                if (drawable == null) {

                        Log.d(TAG, "null drawable");

                } else {

                        Log.d(TAG, "not null drawable");

                }

                

                return drawable ;

        }

}



在獨立線程中進行地理位置編碼:本實例來自於Pro Android2 精通Android 2
XML代碼:
<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

        android:layout_width="fill_parent" android:layout_height="fill_parent">

        <com.google.android.maps.MapView

                android:id="@+id/geoMap" android:clickable="true"

                android:layout_width="fill_parent" android:layout_height="450px"

                android:apiKey="PUT YOUR MAP API KEY" />

        <LinearLayout android:layout_width="fill_parent"

                android:layout_alignParentBottom="true" android:layout_height="wrap_content"

                android:orientation="vertical">

                <EditText android:layout_width="fill_parent" android:id="@+id/location"

                        android:layout_height="wrap_content" android:text="tian an meng" />

                <Button android:id="@+id/geocodeBtn" android:layout_width="wrap_content"

                        android:layout_height="wrap_content" android:text="Find Location" />

        </LinearLayout>

</RelativeLayout>
 

Java代碼:

public class GeocodingDemoActivity extends MapActivity {

        

        public static final String TAG = "GeocodingDemoActivity";

        Geocoder geocoder = null;

        MapController mMapController;

        public MapView mapView;

        ProgressDialog progDialog = null;

        List<Address> addressList = null;



        protected void onCreate(Bundle savedInstanceState) {

                super.onCreate(savedInstanceState);

                setContentView(R.layout.geocode);



                int lat = (int) (31.83659536 * 1000000);

                int lng = (int) (117.1912658 * 1000000);

                mapView = (MapView) findViewById(R.id.geoMap);

                mMapController = mapView.getController();

                GeoPoint curpt = new GeoPoint(lat, lng);

                mMapController.animateTo(curpt);

                mapView.setBuiltInZoomControls(true);

                mMapController.setZoom(15);



                geocoder = new Geocoder(this);

                Button geoBtn = (Button) findViewById(R.id.geocodeBtn);

                

                geoBtn.setOnClickListener(new OnClickListener() {



                        public void onClick(View view) {

                                EditText loc = (EditText) findViewById(R.id.location);

                                String locationName = loc.getText().toString();



                                progDialog = ProgressDialog.show(GeocodingDemoActivity.this,

                                                "Processing...", "Finding Location...", true, false);

                                findLocation(locationName);

                        }

                });



        }

        

        protected boolean isLocationDisplayed() {

                return false;

        }

        protected boolean isRouteDisplayed() {

                return false;

        }



        private void findLocation(final String locationName) {

                //使用一個新的線程來實現地理位置的編碼

                Thread thrd = new Thread() {

                        public void run() {

                                try {

                                        // do backgrond work

                                        addressList = geocoder.getFromLocationName(locationName, 5);

                                        // send message to handler to process results

                                        uiCallback.sendEmptyMessage(0);



                                } catch (IOException e) {

                                        e.printStackTrace();

                                }

                        }

                };

                thrd.start();

        }



        // ui thread callback handler

        private Handler uiCallback = new Handler() {

                @Override

                public void handleMessage(Message msg) {

                        progDialog.dismiss();

                        //打印出本線程的ID

                        Log.i(TAG, "Handler Thread :" + Thread.currentThread().getId());

                        if (addressList != null && addressList.size() > 0) {

                                int lat = (int) (addressList.get(0).getLatitude() * 1000000);

                                int lng = (int) (addressList.get(0).getLongitude() * 1000000);

                                GeoPoint pt = new GeoPoint(lat, lng);

                                mapView.getController().setZoom(15);

                                mapView.getController().setCenter(pt);

                        } else {

                                Dialog foundNothingDlg = new AlertDialog.Builder(

                                                GeocodingDemoActivity.this).setIcon(0)

                                                .setTitle("Failed to Find Location")

                                                .setPositiveButton("Ok", null)

                                                .setMessage("Location Not Found...").create();

                                foundNothingDlg.show();

                        }

                }

        };

}
 

下面詳解  AsyncTask的使用,學習一個方法前,應該很仔細的看官方的文檔,這樣我們會對它的機制更熟悉一些,再參考一些具體的實例,加深理解,
下面的一個實例使用AsyncTask加載一個網頁的內容,並可有進度條顯示加載的進度。實例來源於網絡,作者還沒找到:
XML代碼:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

        android:orientation="vertical" android:layout_width="fill_parent"

        android:layout_height="fill_parent">

        <Button android:id="@+id/execute" android:layout_width="fill_parent"

                android:layout_height="wrap_content" android:text="execute" />

        <Button android:id="@+id/cancel" android:layout_width="fill_parent"

                android:layout_height="wrap_content" android:enabled="false"

                android:text="cancel" />

        <ProgressBar android:id="@+id/progress_bar"

                android:layout_width="fill_parent" android:layout_height="wrap_content"

                android:progress="0" android:max="100"

                style="?android:attr/progressBarStyleHorizontal" />

        <ScrollView android:layout_width="fill_parent"

                android:layout_height="wrap_content">

                <TextView android:id="@+id/text_view" android:layout_width="fill_parent"

                        android:layout_height="wrap_content" />

        </ScrollView>

</LinearLayout>


Java代碼:

public class AsyncTaskTset extends Activity {

        

        private static final String TAG = "ASYNC_TASK";

        private static final String URL = "http://www.google.com.hk/";

        private Button execute;

        private Button cancel;

        private ProgressBar progressBar;

        private TextView textView;

        

        private MyTask mTask;

        

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.asynctask);

        

        execute = (Button) findViewById(R.id.execute);

        execute.setOnClickListener(new View.OnClickListener() {

                        public void onClick(View v) {

                                //注意每次需new一個實例,新建的任務只能執行一次,否則會出現異常

                                mTask = new MyTask();

                                mTask.execute(URL);

                                

                                execute.setEnabled(false);

                                cancel.setEnabled(true);

                        }

                });

        cancel = (Button) findViewById(R.id.cancel);

        cancel.setOnClickListener(new View.OnClickListener() {

                        public void onClick(View v) {

                                //取消一個正在執行的任務,onCancelled方法將會被調用

                                mTask.cancel(true);

                        }

                });

        progressBar = (ProgressBar) findViewById(R.id.progress_bar);

        textView = (TextView) findViewById(R.id.text_view);

        

    }

    //三個參數

    /**

     * 1、Params 啓動任務執行的輸入參數,比如HTTP請求的URL

     * 2、Progress 後臺任務執行的百分比

     * 3、Result 後臺執行任務最終返回的結果,比如String,也可以是一個image

     */

    private class MyTask extends AsyncTask<String, Integer, String> {

            //onPreExecute方法用於在執行後臺任務前做一些UI操作

            protected void onPreExecute() {

                    Log.i(TAG, "onPreExecute() called");

                    textView.setText("loading...");

            }

            

            //doInBackground方法內部執行後臺任務,不可在此方法內修改UI

                protected String doInBackground(String... params) {

                        Log.i(TAG, "doInBackground(Params... params) called");

                        try {

                                HttpClient client = new DefaultHttpClient();

                                HttpGet get = new HttpGet(params[0]);

                                HttpResponse response = client.execute(get);

                                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {

                                        HttpEntity entity = response.getEntity();

                                        InputStream is = entity.getContent();

                                        long total = entity.getContentLength();

                                        ByteArrayOutputStream baos = new ByteArrayOutputStream();

                                        byte[] buf = new byte[1024];

                                        int count = 0;

                                        int length = -1;

                                        while ((length = is.read(buf)) != -1) {

                                                baos.write(buf, 0, length);

                                                count += length;

                                                //調用publishProgress公佈進度,最後onProgressUpdate方法將被執行

                                                publishProgress((int) ((count / (float) total) * 100));

                                                //爲了演示進度,休眠500毫秒

                                                Thread.sleep(500);

                                        }

                                        return new String(baos.toByteArray(), "gb2312");

                                }

                        } catch (Exception e) {

                                Log.e(TAG, e.getMessage());

                        }

                        return null;

                }

                

                //onProgressUpdate方法用於更新進度信息

            protected void onProgressUpdate(Integer... progresses) {

                        Log.i(TAG, "onProgressUpdate(Progress... progresses) called");

                        progressBar.setProgress(progresses[0]);

                        textView.setText("loading..." + progresses[0] + "%");

            }

            

                //onPostExecute方法用於在執行完後臺任務後更新UI,顯示結果

                protected void onPostExecute(String result) {

                        Log.i(TAG, "onPostExecute(Result result) called");

                        textView.setText(result);

                        

                        execute.setEnabled(true);

                        cancel.setEnabled(false);

                }

                

                //onCancelled方法用於在取消執行中的任務時更改UI

                protected void onCancelled() {

                        Log.i(TAG, "onCancelled() called");

                        textView.setText("cancelled");

                        progressBar.setProgress(0);

                        

                        execute.setEnabled(true);

                        cancel.setEnabled(false);

                }

    }

}

 

一、補充知識點:
摘錄於:http://www.android123.com.cn/androidkaifa/422.html
1. 對於線程中的刷新一個View爲基類的界面,可以使用postInvalidate()方法在線程中來處理,其中還提供了一些重寫方法比如postInvalidate(intleft,int top,int right,int bottom) 來刷新一個矩形區域,以及延時執行,比如postInvalidateDelayed(long delayMilliseconds)或postInvalidateDelayed(long delayMilliseconds,intleft,int top,int right,int bottom) 方法,其中第一個參數爲毫秒
2. 當然推薦的方法是通過一個Handler來處理這些,可以在一個線程的run方法中調用handler對象的 postMessage或sendMessage方法來實現,Android程序內部維護着一個消息隊列,會輪訓處理這些,如果你是Win32程序員可以很好理解這些消息處理,不過相對於Android來說沒有提供 PreTranslateMessage這些干涉內部的方法。
3. Looper又是什麼呢? ,其實Android中每一個Thread都跟着一個Looper,Looper可以幫助Thread維護一個消息隊列,但是Looper和Handler沒有什麼關係,我們從開源的代碼可以看到Android還提供了一個Thread繼承類HanderThread可以幫助我們處理,在HandlerThread對象中可以通過getLooper方法獲取一個Looper對象控制句柄,我們可以將其這個Looper對象映射到一個Handler中去來實現一個線程同步機制,Looper對象的執行需要初始化Looper.prepare方法就是昨天我們看到的問題,同時推出時還要釋放資源,使用Looper.release方法。
4.Message 在Android是什麼呢? 對於Android中Handler可以傳遞一些內容,通過Bundle對象可以封裝String、Integer以及Blob二進制對象,我們通過在線程中使用Handler對象的sendEmptyMessage或sendMessage方法來傳遞一個Bundle對象到Handler處理器。對於Handler類提供了重寫方法handleMessage(Message msg) 來判斷,通過msg.what來區分每條信息。將Bundle解包來實現Handler類更新UI線程中的內容實現控件的刷新操作。相關的Handler對象有關消息發送sendXXXX相關方法如下,同時還有postXXXX相關方法,這些和Win32中的道理基本一致,一個爲發送後直接返回,一個爲處理後才返回 .
5. java.util.concurrent對象分析,對於過去從事Java開發的程序員不會對Concurrent對象感到陌生吧,他是JDK 1.5以後新增的重要特性作爲掌上設備,我們不提倡使用該類,考慮到Android爲我們已經設計好的Task機制,這裏不做過多的贅述,相關原因參考下面的介紹:
6. 在Android中還提供了一種有別於線程的處理方式,就是Task以及AsyncTask,從開源代碼中可以看到是針對Concurrent的封裝,開發人員可以方便的處理這些異步任務。

二、可能涉及到得面試題:handler機制的原理
andriod提供了 Handler 和 Looper 來滿足線程間的通信。Handler 先進先出原則。Looper類用來管理特定線程內對象之間的消息交換(Message Exchange)。
1)Looper: 一個線程可以產生一個Looper對象,由它來管理此線程裏的Message Queue(消息隊列)。
2)Handler: 你可以構造Handler對象來與Looper溝通,以便push新消息到Message Queue裏;或者接收Looper從Message Queue取出)所送來的消息。
3) Message Queue(消息隊列):用來存放線程放入的消息。
4)線程:UI thread 通常就是main thread,而Android啓動程序時會替它建立一個Message Queue。
如要要更加深入的瞭解,可以參考Sundy的視頻講解。

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