线程中更新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。

相关文章
相关标签/搜索