概述:每一個Android應用程序都運行在一個dalvik虛擬機進程中,進程開始的時候會啓動一個主線程(MainThread),主線程負責處理和ui相關的事件,所以主線程一般又叫UI線程。而因爲Android採用UI單線程模型,因此只能在主線程中對UI元素進行操做。若是在非UI線程直接對UI進行了操做,則會報錯:html
CalledFromWrongThreadException:only the original thread that created a view hierarchy can touch its viewsjava
。Android爲咱們提供了消息循環的機制,咱們能夠利用這個機制來實現線程間的通訊。那麼,咱們就能夠在非UI線程發送消息到UI線程,最終讓Ui線程來進行ui的操做。android
對於運算量較大的操做和IO操做,咱們須要新開線程來處理這些繁重的工做,以避免阻塞ui線程。網絡
例子:下面咱們以獲取CSDN logo的例子,演示如何使用Thread+Handler的方式實如今非UI線程發送消息通知UI線程更新界面。app
ThradHandlerActivity.java:異步
public class ThreadHandlerActivity extends Activity { /** Called when the activity is first created. */ private static final int MSG_SUCCESS = 0;//獲取圖片成功的標識 private static final int MSG_FAILURE = 1;//獲取圖片失敗的標識 private ImageView mImageView; private Button mButton; private Thread mThread; private Handler mHandler = new Handler() { public void handleMessage (Message msg) {//此方法在ui線程運行 switch(msg.what) { case MSG_SUCCESS: mImageView.setImageBitmap((Bitmap) msg.obj);//imageview顯示從網絡獲取到的logo Toast.makeText(getApplication(), getApplication().getString(R.string.get_pic_success), Toast.LENGTH_LONG).show(); break; case MSG_FAILURE: Toast.makeText(getApplication(), getApplication().getString(R.string.get_pic_failure), Toast.LENGTH_LONG).show(); break; } } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mImageView= (ImageView) findViewById(R.id.imageView);//顯示圖片的ImageView mButton = (Button) findViewById(R.id.button); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if(mThread == null) { mThread = new Thread(runnable); mThread.start();//線程啓動 } else { Toast.makeText(getApplication(), getApplication().getString(R.string.thread_started), Toast.LENGTH_LONG).show(); } } }); } Runnable runnable = new Runnable() { @Override public void run() {//run()在新的線程中運行 HttpClient hc = new DefaultHttpClient(); HttpGet hg = new HttpGet("http://csdnimg.cn/www/images/csdnindex_logo.gif");//獲取csdn的logo final Bitmap bm; try { HttpResponse hr = hc.execute(hg); bm = BitmapFactory.decodeStream(hr.getEntity().getContent()); } catch (Exception e) { mHandler.obtainMessage(MSG_FAILURE).sendToTarget();//獲取圖片失敗 return; } mHandler.obtainMessage(MSG_SUCCESS,bm).sendToTarget();//獲取圖片成功,向ui線程發送MSG_SUCCESS標識和bitmap對象 // mImageView.setImageBitmap(bm); //出錯!不能在非ui線程操做ui元素 // mImageView.post(new Runnable() {//另一種更簡潔的發送消息給ui線程的方法。 // // @Override // public void run() {//run()方法會在ui線程執行 // mImageView.setImageBitmap(bm); // } // }); } }; }
main.xml佈局文件:ide
strings.xmloop
Manifest.xml:佈局
爲了避免阻塞ui線程,咱們使用mThread從網絡獲取了CSDN的LOGOpost
,並用bitmap對象存儲了這個Logo的像素信息。
此時,若是在這個線程的run()方法中調用
mImageView.setImageBitmap(bm)
會出現:CalledFromWrongThreadException:only the original thread that created a view hierarchy can touch its views。緣由是run()方法是在新開的線程中執行的,咱們上面提到不能直接在非ui線程中操做ui元素。
非UI線程發送消息到UI線程分爲兩個步驟
1、發送消息到UI線程的消息隊列
經過使用Handler的
構造一個Message對象,這個對象存儲了是否成功獲取圖片的標識what和bitmap對象,而後經過message.sendToTarget()方法把這條message放到消息隊列中去。
2、處理髮送到UI線程的消息
在ui線程中,咱們覆蓋了handler的
最後,咱們經過
mImageView.setImageBitmap((Bitmap) msg.obj);設置ImageView的bitmap對象,完成UI的更新。
補充:
事實上,咱們還能夠調用
View的post方法來更新ui
從例子中咱們能夠看到handler既有發送消息和處理消息的做用,會誤覺得handler實現了消息循環和消息分發,其實Android爲了讓咱們的代碼看起來更加簡潔,與UI線程的交互只須要使用在UI線程建立的handler對象就能夠了。如需深刻學習,瞭解消息循環機制的具體實現,請關注《Android異步處理三:Handler+Looper+MessageQueue深刻詳解》