當android應用程序運行時,一個主線程被建立(也稱做UI線程),此線程主要負責處理UI相關的事件,因爲Android採用UI單線程模型,因此只能在主線程中對UI元素進行操做,若是在非UI線程直接對UI進行了操做,則會報錯,另外,對於運算量較大的操做和IO操做,咱們須要新開線程來處理這些工做,以避免阻塞UI線程,子線程與主線程之間是怎樣進行通訊的呢?此時就要採用消息循環機制(Looper)與Handler進行處理。java
Looper:每個線程均可以產生一個Looper,用來管理線程的Message,Looper對象會創建一個MessgaeQueue數據結構來存放message。android
Handler:與Looper溝通的對象,能夠push消息或者runnable對象到MessgaeQueue,也能夠從MessageQueue獲得消息。數據結構
查看其構造函數:ide
Handler()函數
Default constructor associates this handler with the queue for the current thread.//如不指定Looper參數則默認利用當前線程的Looper建立oop
Handler(Looper looper)post
Use the provided queue instead of the default one.//使用指定的Looper對象建立Handlerthis
線程A的Handler對象引用能夠傳遞給別的線程,讓別的線程B或C等能送消息來給線程A。spa
線程A的Message Queue裏的消息,只有線程A所屬的對象能夠處理。線程
注意:Android裏沒有global的MessageQueue,不一樣進程(或APK之間)不能經過MessageQueue交換消息。
使用Looper.myLooper能夠取得當前線程的Looper對象。
使用mHandler = new Handler(Looper.myLooper()); 可產生用來處理當前線程的Handler對象。
使用mHandler = new Handler(Looper.getMainLooper()); 可誕生用來處理main線程的Handler對象。
使用Handler傳遞消息對象時將消息封裝到一個Message對象中,Message對象中主要字段以下:
Message對象能夠經過Message類的構造函數得到,但Google推薦使用Message.obtain()方法得到,該方法會從全局的對象池裏返回一個可複用的Messgae實例,API中解釋以下:
Message()
Constructor (but the preferred way to get a Message is to call Message.obtain()).
Handler發出消息時,既能夠指定消息被接受後立刻處理,也能夠指定通過必定時間間隔以後被處理,如sendMessageDelayed(Message msg, long delayMillis),具體請參考API。
Handler消息被髮送出去以後,將由handleMessage(Message msg)方法處理。
注意:在Android裏,新誕生一個線程,並不會自動創建其Message Loop能夠經過調用Looper.prepare()爲該線程創建一個MessageQueue,再調用Looper.loop()進行消息循環。
下面舉例說明:
在main.xml中定義兩個button及一個textview
public class HandlerTestActivity extends Activity implements OnClickListener{ public TextView tv; private myThread myT; Button bt1, bt2; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); bt1 = (Button)findViewById(R.id.a); bt2 = (Button)findViewById(R.id.b); tv = (TextView)findViewById(R.id.tv); bt1.setId(1);//爲兩個button設置ID,此ID用於後面判斷是哪一個button被按下 bt2.setId(2); bt1.setOnClickListener(this);//增長監聽器 bt2.setOnClickListener(this); } @Override public void onClick(View v) { switch(v.getId()){//按鍵事件響應,若是是第一個按鍵將啓動一個新線程 case 1: myT = new myThread(); myT.start(); break; case 2: finish(); break; default: break; } } class myThread extends Thread { private EHandler mHandler; public void run() { Looper myLooper, mainLooper; myLooper = Looper.myLooper();//獲得當前線程的Looper mainLooper = Looper.getMainLooper();//獲得UI線程的Looper String obj; if(myLooper == null) { //判斷當前線程是否有消息循環Looper mHandler = new EHandler(mainLooper); obj = "current thread has no looper!";//當前Looper爲空,EHandler用mainLooper對象構造 } else { mHandler = new EHandler(myLooper);//當前Looper不爲空,EHandler用當前線程的Looper對象構造 obj = "This is from current thread."; } mHandler.removeMessages(0);//清空消息隊列裏的內容 Message m = mHandler.obtainMessage(1, 1, 1, obj); mHandler.sendMessage(m);//發送消息 } } class EHandler extends Handler { public EHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { //消息處理函數 tv.setText((String)msg.obj);//設置TextView內容 } } }
程序運行後點擊TestLooper按鍵,TextView輸出以下,說明新建立的線程裏Looper爲空,也就說明了新建立的線程並不會本身創建Message Looper。
修改myThread類:
class myThread extends Thread { private EHandler mHandler; public void run() { Looper myLooper, mainLooper; Looper.prepare(); myLooper = Looper.myLooper(); mainLooper = Looper.getMainLooper(); ...... ...... mHandler.sendMessage(m); Looper.loop(); } }
Looper.prepare爲當前線程建立一個Message Looper,Looper.loop()開啓消息循環。這樣修改是OK呢?
答案是否認的!運行時Logcat將拋出CalledFromWrongThreadException異常錯誤,提示以下:
意思就是說「只有原始建立這個視圖層次的線程才能修改它的視圖」,本例中的TextView是在UI線程(主線程)中建立,所以,myThread線程不能修改其顯示內容!
通常的作法是在子線程裏獲取主線程裏的Handler對象,而後經過該對象向主線程的消息隊列裏發送消息,進行通訊。
若是子線程想修改主線程的UI,能夠經過發送Message給主線程的消息隊列,主線程經行判斷處理再對UI經行操做,具體能夠參考以前的代碼。
咱們能夠經過Handler的post方法實現線程間的通訊,API中關於post方法說明以下
public final boolean post (Runnable r)
Causes the Runnable r to be added to the message queue. The runnable will be run on the thread to which this handler is attached.
Post方法將安排runnable對象在主線程的某個位置運行,可是並不會開啓一個新的線程,驗證代碼以下:
public class HandlerTestActivity extends Activity { private Handler handlerTest; Runnable runnableTest = new Runnable() { public void run() { String runID = String.valueOf(Thread.currentThread().getId());//輸出Runnable 線程的ID號 Log.v("Debug",runID); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); handlerTest = new Handler(); String mainID = String.valueOf(Thread.currentThread().getId()); Log.v("Debug",mainID);//輸出主線程的ID號 handlerTest.post(runnableTest); } }
Logcat裏輸出以下:
說明只是把runnable裏的run方法放到UI線程裏運行,並不會建立新線程
所以咱們能夠在子線程中將runnable加入到主線程的MessageQueue,而後主線程將調用runnable的方法,能夠在此方法中更新主線程UI。
將以前的代碼修改以下:
public class HandlerTestActivity extends Activity { private Handler handlerTest; private TextView tv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tv = (TextView)findViewById(R.id.tv);//TextView初始化爲空 handlerTest = new Handler(); myThread myT = new myThread(); myT.start();//開啓子線程 } class myThread extends Thread{ public void run(){ handlerTest.post(runnableTest);//子線程將runnable加入消息隊列 } } Runnable runnableTest = new Runnable() { public void run() { tv.setText("此信息由子線程輸出!"); } }; }
至關於在主線程中調用了runnalbe的run方法,更改了TextView的UI!