天天看點

android 深入了解AnsyncTask13.1. 深入了解AnsyncTask13.2.深入了解Handler13.3.Handler.Callback接口13.4. HandlerThread類13.5.清單對話框13.6. 單選清單對話框13.7.複選清單對話框13.8. 常見問題

13.1. 深入了解AnsyncTask

13.1.1. publishProgress()方法傳遞對象參數

13.1.1.1.概述

AnsyncTask.publishProgress方法不僅可以傳遞整形實參,也可以傳遞任意類型的對象

13.1.1.2.操作步驟

步驟1、自定義類,示例代碼:

//自定義類,存放下載下傳進度值和下載下傳提示資訊

private class Info {

private int progress;//下載下傳進度值

private String info;//下載下傳提示資訊

public int getProgress() {

return progress;

}

public String getInfo() {

return info;

}

//帶參數的構造方法

public Info(int progress, String info) {

super();

this.progress = progress;

this.info = info;

}

}

步驟2、在自定義AsyncTask類時,将第二個泛型參數設定為步驟1定義的類,示例代碼:

private class MyAsyncTask extends AsyncTask<URL, Info, String>{

步驟3、在doInBackground方法中,将需要在UI中更新的資料存放在自定義的對象中,然後調用publishProgress方法,在參數中将info類的一個對象發送給onProgressUpdate()方法。示例代碼:

publishProgress(new Info(i+1,"progress="+(i+1)+"%"));

步驟4、在onProressUpdate方法中用獲得的對象更新UI,示例代碼:

//重寫本方法,在UI中顯示下載下傳進度和提示資訊,參數Info類的對象

    @Override

    protected void onProgressUpdate(Info... info) {

    super.onProgressUpdate(info);

    mProgressBar.setProgress(info[0].getProgress());

    mtvProgress.setText(""+info[0].getInfo());

    }

13.1.2. publishProgress()方法中可變參數的用法

13.1.2.1.概述

publishProgress方法可以向onProgressUpdate發送數量任意多的實參。但這些資料必須是同一類型的。

13.1.2.2.操作步驟

步驟1、在doInBackground方法中,定義需要傳遞的參數的數組,示例代碼:

Integer[] values=new Integer[2];

values[0]=i+1;//第一進度值

values[1]=i+1+10;//第二進度值

步驟2、用publishProgress方法發送步驟1定義的values數組,示例代碼:

publishProgress(values);

步驟3、在onProgressUpdate方法中接收,并使用傳遞過來的數組更新UI,示例代碼:

//重寫本方法,更新進度

@Override

protected void onProgressUpdate(Integer... values) {

super.onProgressUpdate(values);

mProgressBar.setProgress(values[0]);//更新進度條中的第一個進度值

mProgressBar.setSecondaryProgress(values[1]);//更新第二個進度值

}

13.2.深入了解Handler

13.2.1.sendEmptyMessage()方法

1、sendEmptyMessage(int what);

2、作用:向Handler對象所工作的線程的消息隊列發送空消息。

3、說明:參數what用于在消息隊列中辨別不同的消息。

4、示例:在視窗中單擊一個按鈕,然後啟動一個工作線程執行一段時間,執行完畢後,向主線程的消息隊列發送一個空消息,主線程在視窗顯示一段文字資訊。

關鍵步驟及代碼如下所示:

步驟1、在Activity.onCreate()方法中建立Handler對象,重寫handleMessage方法,代碼如下所示:

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        mtvResult=(TextView)findViewById(R.id.tvResult);

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

        btnDownload.setOnClickListener(this);//注冊單擊事件

        //在主線程建立mHandler并接收工作線程發送的消息

        mHandler=new Handler(){

        public void handleMessage(android.os.Message msg) {

        //判斷msg.what值

        switch(msg.what){

        case DOWNLOAD_FINISHED://若是該常量

        //在标簽的标題上顯示donwload finished

        mtvResult.setText("donwload finished");

        break;

        }       

        };

        };

    }

步驟2、在按鈕的單擊事件中建立并啟動線程,線上程中模拟耗時操作結束後,向主線程的消息隊列發送一個空消息,代碼如下所示:

    //實作單擊事件

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.btnDownload:

//建立并啟動線程,模拟下載下傳

new Thread(){

public void run() {

for (int i = 0; i < 3; i++) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//循環結束後,向主線程發送一個空消息,并傳遞一個整數值

mHandler.sendEmptyMessage(DOWNLOAD_FINISHED);

};

}.start();

}

}

13.2.2. 巧用Handler.postDelayed()

13.2.2.1.概述

上一章介紹了Handler類中的post和postDelayed方法,這兩個方法通過發送一個消息至主線程的消息隊列(其中該消息的第二參數是一個實作了Runnable接口的對象),主線程執行該對象中的run方法中的代碼,達到讓主線程執行工作線程中發送的代碼片段的效果。

以下介紹不用new線程對象,隻用postDelayed方法,即可實作一個工作線程定時修改UI的效果。

13.2.2.2.示例

單擊圖-1中的run按鈕,将在視窗中以秒為機關顯示一個計器。

單擊stop按鈕,計時器停止計時。

圖-1

步驟1、在Activity.onCreate()方法中建立按鈕控件,建立mHandler對象。

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        //建立控件

        mtvResult=(TextView)findViewById(R.id.tvResult);

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

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

        //注冊按鈕的單擊事件

        btnDownload.setOnClickListener(this);

        btnPause.setOnClickListener(this);

        mHandler=new Handler();

    }

步驟2、在按鈕單擊事件中編寫響應單擊run按鈕和stop按鈕的代碼:

   //實作單擊事件

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.btnDownload://單擊下載下傳按鈕

mHandler.post(callback);

break;

case R.id.btnPause://若是暫停按鈕,将callback所在的消息從消息隊列中移除

mHandler.removeCallbacks(callback);

break;

}

}

說明:

第一個紅框中的代碼将callback對象中封裝的代碼發送至主線程的消息隊列,由主線程執行。

第二個紅框中的removeCallbacks方法的作用是将參數callbakc所在的消息從主線程的消息隊列中删除。

步驟3、建立一個實作了Runnable接口的對象-callback,該類實作了run方法,在run方法中以秒為機關,動态顯示時間。代碼如下所示:

//建立一個Runnable接口的實作對象

private Runnable callback=new Runnable(){

//實作run方法

@Override

public void run() {

//顯示時間(機關:秒)再遞增1

mtvResult.setText(mProgress+++"s");

//一秒後再次向消息隊列發送封裝在callback對象中的代碼片段

mHandler.postDelayed(callback, 1000);

}

};

說明:

紅框中代碼調用mHandler.postDelayed方法将callback對象再次發送至消息隊列由主線程執行。如此循環反複。這種做法部分代替了工作線程的作用,即不用再new一個工作線程來重新整理UI.

但要注意:不要在以上代碼的run方法中執行耗時操作,因為Runnable隻是一個接口,而不是線程,該處的代碼是被發送至主線程中執行,若出現耗時操作同樣要阻塞主線程中其它的使用者輸入操作,同樣可能出現ANR現象。

13.2.3.Looper類

13.2.3.1.概述

Android應用程式是消息驅動的,Android系統提供了消息循環機制。Android通過Looper、Handler來實作消息循環機制,Android消息循環是針對線程的(每個線程都可以有自己的消息隊列和消息循環)。

Android系統中Looper負責管理線程的消息隊列(message queue)和消息循環。

13.2.3.2.重要方法

1、Looper.prepare():

作用:建立Looper對象和MessageQueue(消息隊列)對象。

2、Looper.getMainLooper();

作用:獲得主線程的Looper對象。

3、Looper.myLooper();

作用:獲得目前工作線程的Looper對象。

4、Looper.loop();

作用:進入消息循環。

13.2.3.3.Looper運作機制

Looper負責管理線程的消息隊列和消息循環,通過Loop.myLooper()得到目前線程的Looper對象,通過Loop.getMainLooper()獲得主線程的Looper對象。Looper不能用new來執行個體化,而是要用Looper.prepare()執行個體化。

   Android中每一個線程都可以(但不是必須)跟着一個Looper,Looper可以幫助線程維護一個消息隊列,每個線程可以存在一個消息隊列和一個消息循環(Looper),特定線程的消息隻能分發給本線程,不能進行跨線程,跨程序通訊。但是建立的工作線程預設是沒有消息循環和消息隊列的,如果想讓該線程具有消息隊列和消息循環,需要線上程中首先調用Looper.prepare()來建立消息隊列,然後調用Looper.loop()進入消息循環。如下例所示

Looper.prepare();//建立消息隊列

myLooper=Looper.myLooper();//獲得目前線程中的Looper對象

Looper.loop();//進入目前線程的消息循環

     一個Activity中可以建立多個工作線程,如果這些線程把它們的消息放入主線程消息隊列,那麼該消息就會在主線程中處理了。因為主線程一般負責界面的更新操作,是以這種方式可以很好的實作Android界面更新。Activity、Looper、Handler的關系如下圖所示

其它線程通過Handle對象将消息放入主線程的消息隊列。隻要Handler對象以主線程的Looper建立,那麼調用Handler的sendMessage等方法,會把消息放入主線程的消息隊列。并且Handler将會在主線程中調用handleMessage方法來處理消息。

13.2.3.4.示例

建立一個Thread類線程,該線程具有消息隊列和處理消息隊列的Looper:

 class LooperThread extends Thread {

   public Handler mHandler;

   public void run() {

    Looper.prepare();

    mHandler = new Handler() {

   public void handleMessage(Message msg) {

  

  };

Looper.loop();

  不管是主線程還是工作線程,隻要有Looper的線程,别的線程就可以向這個線程的消息隊列中發送消息和計劃任務,然後做相應的處理。

13.2.4. Handler、Message、MessageQueue和Looper四者關系

Looper對象由Looper.prepare()方法來建立,該方法同時會建立MessageQueue,該消息隊列中的Message由Handler發送至主線程的消息隊列,由主線程的MainLooper處理消息。

MainLooper從上至下依次從消息隊列中取出消息進行處理。如圖-2所示:

圖-2

Handler對象在建立時,可以在構造方法中帶參數,也可以不帶參數。若用無參構造方法建立,則Handler在所在的線程中工作。

若用帶參構造方法建立,則按參數在指定的線程中工作。

示例1:

//擷取主線程的Looper對象

Looper mainLooper=Looper.getMainLooper();

Handler handler=new Handler(mainLooper);

則handler對象工作在主線程。以上代碼的第二行也可以寫成:

Handler handler=new Handler(Looper.getMainLooper);

示例2:

//擷取目前線程的Looper對象

Looper myLooper=Looper.myLooper();

//handler工作在目前線程

Handler handler=new Handler(myLooper);

可以做以下比喻:

      Message        集裝箱:消息中存放各種類型的資料

      MessageQueue   碼頭:存放許多Message

      Handler        船:将工作線程中的消息發送至主線程的消息隊列中

      Looper         搬運工:處理目前線程的消息隊列中的消息

提示:

Handler不一定都在主線程中建立,也可以在工作線程中建立。如果構造參數為空,那麼在哪個線程中new的Handler,則handlerMessage()方法就在哪個線程中處理該線程中的消息隊列中的消息。

13.3.Handler.Callback接口

13.3.1.概述

Handler在建立時,通常用如下代碼:

mHandler = new Handler(Looper.getMainLooper()) {

public void handleMessage(Message msg) {

//編寫處理消息的代碼

};

以下介紹Handler的另一種建立方法,該方法需要使用Handler類的一個内部接口Callback,該接口源代碼如下所示:

    public interface Callback {

        public boolean handleMessage(Message msg);

    }

通過建立一個實作了該接口的對象,然後将該對象作為建立Handler的參數,也能建立Handler對象。

13.3.2.建立步驟

步驟1、建立Handler.Callback對象:

Handler.Callback callback = new Handler.Callback() {

//實作handleMessage方法

@Override

public boolean handleMessage(Message msg) {

//編寫處理消息的代碼

return false;

}

};

步驟2、将callback作為參數,用來建立Handler對象:

Handler handler=new Handler(callback);

13.4. HandlerThread類

13.4.1.概述

上面所示建立能處理消息的線程類的代碼有些麻煩,Android提供了一個線程類HanderThread類,HanderThread類繼承了Thread類,它封裝了Looper對象,我們不用關心Looper的開啟和釋放的細節問題。HandlerThread對象中可以通過getLooper方法擷取一個Looper對象引用。 

13.4.2.建立HandlerThread對象的步驟

步驟1、建立handlerThread類型的線程對象,如下代碼所示

mThread=new HandlerThread("MyThread");

步驟2、啟動線程,如下代碼所示:

mThread.start();

步驟3、建立與mThread線程相關聯的Handler對象,代碼如下所示:

//重新建立mHandler,此時的mHandler工作在work thread中

mHandler=new Handler(mThread.getLooper()){

public void handleMessage(Message msg) {

Log.i("looper",Thread.currentThread().getName());

};

};

說明:第一行的參數mThread.getLooper()方法獲得mThread線程對象中的Looper對象,通過該參數将mHandler與mThread關聯起來。

13.5.清單對話框

13.5.1.概述

AlertDialog提供了多種風格的對話框,本節介紹清單對話框,清單對話框如圖-3所示:

圖-3

13.5.2.重要方法

setItems((CharSequence[] items, final OnClickListener listener);

1、作用:建立并顯示清單對話框,并傳回AlertDialog.Builder類的對象。

2、參數說明:

(1)第一個參數:存放菜單中各菜單項标題的數組。

(2)第二個參數,菜單項單擊事件。

提示:OnClickListener接口在android.content.DialogInterface包下,與按鈕等控件的OnClickListener接口名相同,但所在包不同。

13.5.3.示例

以下代碼建立并顯示圖-3,并将被單擊的清單項顯示出來

//從數組資源中解析獲得存放星期的數組

     weekDay=getResources().getStringArray(R.array.weekDay);

//建立對話框

Builder dialog=new AlertDialog.Builder(this);

dialog.setIcon(R.drawable.icon);

dialog.setTitle("清單對話框");//設定标題

dialog.setItems(weekDay, new android.content.DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

    Toast.makeText(mContext, weekDay[which], 3000).show();

}

});

dialog.show();//顯示對話框

13.6. 單選清單對話框

13.6.1.概述

AlertDialog提供了單選清單風格的對話框,如圖-4所示:

 圖-4

13.6.2.重要方法

Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, 

final OnClickListener listener)

1、作用:設定單選清單對話框,并傳回AlertDialog.Builder類的對象。

2、參數說明

(1)第一個參數:存放菜單項的字元串數組。

(2)第二個參數:設定預設被選中的菜單項的索引值。如圖-4中第一個菜單項預設被選中,該值設定為0.

3、第三個參數:菜單項單擊事件。

13.6.3.示例

以下代碼建立并顯示圖-4,并将被單擊的清單項顯示出來

weekDay=getResources().getStringArray(R.array.weekDay);

//建立對話框

Builder dialog=new AlertDialog.Builder(this);

dialog.setIcon(R.drawable.icon);

dialog.setTitle("單選清單對話框");

dialog.setSingleChoiceItems(weekDay, 0, 

new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

Toast.makeText(mContext, weekDay[which], 3000).show();

}

});

dialog.show();//顯示對話框

13.7.複選清單對話框

13.7.1.概述

AlertDialog提供了複選清單風格的對話框,如圖-5所示:

圖-5

13.7.2.重要方法

Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, 

final OnMultiChoiceClickListener listener)

1、作用:建立并設定複選清單對話框。

2、參數說明:

(1)第一個參數:存放清單項标題的字元串數組。

(2)第二個參數:預設選中/未選中的布爾數組。例如圖-5預設第一、第三個清單項被選中,則checkedItems數組被定義為如下格式:

Boolean checkedItems={true,false,true,false,false,false,false};

(3)第三個參數:複選清單的單擊事件,實作OnMultiChoiceClickListener接口時,要實作接口中定義的OnClick方法。

(4) OnMultiChoiceClickListener.onClick()方法聲明為如下格式:

onClick(DialogInterface dialog, int which, boolean isChecked)

第一個參數:目前的對話框;

第二個參數:被單擊的清單項的索引值。

第三個參數:該清單項是否被選中,選中值為true,否則為false。

13.7.3.示例

以下代碼建立并顯示圖-5的對話框并響應單擊事件,将被單擊的清單項顯示出來:

weekDay=getResources().getStringArray(R.array.weekDay);

//建立對話框

Builder dialog=new AlertDialog.Builder(this);

dialog.setIcon(R.drawable.icon);

dialog.setTitle("複選清單對話框");

dialog.setMultiChoiceItems(weekDay, checkedItems, 

new DialogInterface.OnMultiChoiceClickListener() {

@Override

public void onClick(DialogInterface dialog, int which, 

boolean isChecked) {

Toast.makeText(mContext, weekDay[which], 3000).show();

}

});

dialog.show();//顯示對話框

13.8. 常見問題

現象:在成員變量中初始化UI,得到的元件引用為空,運作時出現空指針異常的提示。

原因:成員變量初始化在構造方法之前,

例如:

Button button = (Button) findViewById(R.id.button1);

setContentView(R.layout.main);

這時還沒有執行setContentView()方法,是以得不到元件的引用,button 為空

 解決方法:    

       findViewById()方法要放在setContentView()之後執行    

繼續閱讀