線程中更新ui方法彙總

1、爲什麼寫做此文

  你是否是常常看到不少書籍中說:不能在子線程中操做ui,否則會報錯。你是否是也遇到了以下的疑惑(見下面的代碼):html

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.tv); Thread.currentThread().setName("UIThread"); new LooperThread().start(); } private class LooperThread extends Thread { @Override public void run() { Thread.currentThread().setName("OtherThread"); tv.setText("other thread"); } } 

 

  上面確實在子線程中操做ui了,可是他並不會報錯,爲何呢?這不是跟書上的說法恰好相悖嗎?當時本身也是遇到了這個問題,因此有了這篇博客,感謝網絡上的那些前輩們的無私分享,現將本身的整理和思考記錄下來。java

2、引入

  在Android開發過程當中,常須要更新界面的UI。而更新UI是要主線程來更新的,即UI線程更新。若是在主線線程以外的線程中直接更新頁面顯示常會報錯。拋出異常:android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.怎麼解決呢?下面我會詳細列出子線程更新ui的方法:android

3、子線程更新UI的方法

一、用Handler+message

  主線程中定義Handler,子線程發消息,通知Handler完成UI更新。編程

mHandler = new Handler() { @Override public void handleMessage(Message msg) { //操做界面 myText.setText( 來自網絡的信息); super.handleMessage(msg); } }; public class MyThread extends Thread { public void run() { ​ ​ ​ // 耗時操做 ​ ​ ​ ​loadNetWork(); Message msg = new Message(); mHandler.sendMessage(msg);//向Handler發送消息, } }

 

handler的原理圖以下:網絡

這裏寫圖片描述

二、用runOnUiThread更新

  這個最好用, 凡是要刷新頁面的地方,均可以按照以下方式寫。多線程

new Thread() { public void run() { //這兒是耗時操做,完成以後更新UI; runOnUiThread(new Runnable(){ @Override public void run() { //更新UI imageView.setImageBitmap(bitmap); } }); } }.start();

 

  這種方法使用比較靈活,但若是Thread定義在其餘地方,須要傳遞Activity對象(經過構造函數傳遞)。異步

三、View.post(Runnable r)

  方法解釋:從Runnable派生你的子類,重載run()方法。而後調用View.post(myRunnableObj)便可把你的Runnable對象增長到UI線程中運行。socket

public void onClick( View v ) { new Thread( new Runnable() { public void run() { // 耗時操做 ​ ​ ​ ​ ​ ​ loadNetWork(); ​ myText.( new Runnable() { myText.setText( 來自網絡的信息); }); } }).start(); }

 

  這種方法更簡單,但須要傳遞要更新的View過去。注意:post函數,裏面傳遞的是一個runnable 接口(你懂得 runnable 可不是一個線程這個你必定要和thread 區分開) 。ide

四、使用異步任務

//UI線程中執行 new DownloadImageTask().execute( "www.91dota.com" ); private class DownloadImageTask extends AsyncTask { protected String doInBackground( String... url ) { return loadDataFormNetwork( url[0] );//後臺耗時操做 } protected void onPostExecute( String result ) { myText.setText( result ); //獲得來自網絡的信息刷新頁面 } }

 

這裏寫圖片描述

應用場合

  1. 若是是後臺任務,像是下載任務等,就須要使用AsyncTask。
  2. 若是須要傳遞狀態值等信息,像是藍牙編程中的socket鏈接,就須要利用狀態值來提示鏈接狀態以及作相應的處理,就須要使用Handler + Thread的方式;
  3. 須要另開線程處理數據以避免阻塞UI線程,像是IO操做或者是循環,可使用Activity.runOnUiThread();
  4. 若是隻是單純的想要更新UI而不涉及到多線程的話,使用View.post()就能夠了;

4、在子線程中更新了UI的錯覺

  回到開頭的問題,子線程更新ui成功了,其實否則。還有另一種錯誤的方法:在子線程中使用接口回調,在activity中實現該方法來更新ui,其實這個方法也是變相的在子線程中更新了UI。爲何成功了呢?緣由精煉點說就是:這個異常是android源碼中的檢測設定拋出的,若是檢測的方法沒有執行就不會報錯。onCreate方法裏開線程更新UI不報錯,是由於view尚未還出來呢,沒有調用invalidate方法。函數

更深刻的解釋請參考:

http://www.2cto.com/kf/201111/111172.html 
http://blog.csdn.net/imyfriend/article/details/6877959 
http://doc.okbase.net/aigestudio/archive/127460.html 
http://blog.csdn.net/zhaokaiqiang1992/article/details/43410351 
http://blog.csdn.net/aigestudio/article/details/43449123 
http://javapolo.iteye.com/blog/1343583 
http://blog.csdn.net/androidzhaoxiaogang/article/details/8136222

5、綜述

  有的時候使用子線程來直接更新ui,並不會報錯,但並不推薦這麼作,google的android底層代碼中會對更新ui的線程作檢測,緣由就是爲了不咱們在非ui線程中直接更新ui。檢測針對兩個方面:1.是否更新了ui,更新view在android中對應的方法是invalidate。2.更新時當前線程是不是ui線程。雖然咱們鑽空子,能夠不報異常,可是這並非好的方式。google這樣設計的緣由就在於讓UI線程作的事情更純粹一些,都是界面方面的事情,若是在ui線程執行耗時的操做,在作UI操做的時候會有卡頓的感受。即從更新View的角度來講,最好是UI線程,非UI線程也不是不能更新UI。

相關文章
相關標籤/搜索